zudo-doc
GitHub repository

Type to search...

to open search from anywhere

Component First Strategy

Created Mar 16, 2026Updated Jul 13, 2026Takeshi Takatsudo

Why zudo-doc uses components with utility classes instead of custom CSS class names.

zudo-doc follows a component-first strategy: always express UI as components with Tailwind utility classes. Never create custom CSS class names with separate stylesheets.

The Problem

When projects use a utility CSS framework alongside a component framework, developers frequently fall back to traditional CSS patterns. Instead of composing utility classes inside components, they create custom CSS class names — .profile-card, .btn-primary, .sidebar-nav — with separate stylesheets or CSS modules.

This creates a fragmented codebase:

  • Some components use Tailwind utilities inline

  • Others introduce custom CSS classes with BEM naming or CSS modules

  • Some mix both approaches in the same file

For AI agents, this is a particularly common failure mode. Given a task like "build a profile card," an agent will often generate a .profile-card class with a CSS module — the pattern seen most often in training data. Over time, the codebase becomes a patchwork of conflicting styling approaches.

The Rule

The component itself is the abstraction. CSS class names like .card or .btn-primary are unnecessary — the component handles encapsulation, and utility classes handle styling.

  • Need a card? Create a <Card> component with utility classes

  • Need a button variant? Create a <Button variant="primary"> component

  • Need a layout pattern? Create a <PageLayout> component

In zudo-doc

zudo-doc uses Preact components (.tsx) on top of zfb, with Tailwind CSS v4 utilities. In project-owned .tsx files, write className, not class: the scaffold maps react to preact/compat, so authored JSX uses React-compatible types and class fails zfb check. The same component-first rule applies whether a component renders only on the server or also hydrates on the client:

Server-rendered components

Most UI is server-rendered — zero JavaScript, utility classes inline. The package's packages/zudo-doc/src/footer/footer.tsx is one server-rendered example; write project-owned components in this shape:

export function Footer({ copyright }: Props) {
  return (
    <footer className="border-t border-muted bg-surface px-hsp-xl py-vsp-xl">
      <div
        className="text-center text-caption text-muted"
        dangerouslySetInnerHTML={{ __html: copyright }}
      />
    </footer>
  );
}

No .footer class. No footer.module.css. The component is the abstraction.

Client-hydrated islands

An interactive module starts with "use client"; the host then wraps the component with Island() from @takazudo/zfb. Island({ when, children }) chooses when hydration begins ("load", "idle", or "visible"), and ssrFallback supplies markup for the server-rendered state when needed. The package's packages/zudo-doc/src/header-with-defaults/index.tsx uses this call form for its existing SidebarToggle island. Follow the complete interactive-islands recipe for the required static import path and experimental scanner constraints.

import { Island } from "@takazudo/zfb";

// Counter is imported from a module that starts with "use client".
const CounterIsland = () =>
  Island({
    when: "visible",
    ssrFallback: <span className="text-caption">Loading counter...</span>,
    children: <Counter />,
  });

Anti-Pattern

Do not create CSS class names in a zudo-doc project:

/* WRONG — don't create custom CSS classes */
.profile-card {
  display: flex;
  gap: 1rem;
  padding: 1.5rem;
}
.profile-card__name {
  font-size: 1.25rem;
  font-weight: 600;
}
// WRONG — custom class names bypass the design system
<div className="profile-card">
  <h3 className="profile-card__name">{name}</h3>
</div>

Instead:

// RIGHT — utility classes, the component is the abstraction
<div className="flex gap-hsp-md p-hsp-lg">
  <h3 className="text-body font-semibold">{name}</h3>
</div>

Component Variants via Props

Instead of CSS modifier classes (.btn--primary, .btn--secondary), use component props:

function Button({ variant = "primary", children }) {
  const styles = {
    primary: "bg-accent text-bg hover:bg-accent-hover",
    secondary: "bg-surface text-fg border border-muted",
  };
  return (
    <button className={`${styles[variant]} font-semibold py-vsp-xs px-hsp-md rounded`}>
      {children}
    </button>
  );
}

Usage:

<Button variant="primary">Save</Button>
<Button variant="secondary">Cancel</Button>

No .btn-primary class to maintain. The variant prop is type-safe, auto-completable, and self-documenting.

Component Composition

Complex layouts are built by composing smaller components — not by adding more CSS:

<div className="divide-y divide-muted">
  {users.map((user) => (
    <div className="flex items-center gap-hsp-md py-vsp-sm">
      <Avatar src={user.avatar} size="sm" />
      <div className="flex-1 min-w-0">
        <p className="text-small font-medium text-fg truncate">{user.name}</p>
        <p className="text-caption text-muted truncate">{user.email}</p>
      </div>
    </div>
  ))}
</div>

Each piece — <Avatar>, the list layout — is a component. No .user-list__item or .user-list__avatar class names needed.

Using zudo-doc's Design Tokens

Always use project tokens instead of arbitrary values:

// WRONG — arbitrary values bypass the design system
<div className="p-[1.2rem] text-[0.875rem] text-[#6b7280]">

// RIGHT — use design tokens
<div className="p-hsp-md text-small text-muted">

See Design System for available spacing, typography, and color tokens.

When Custom CSS Is Acceptable

Keep project-level CSS in src/styles/global.css, but do not duplicate package-owned styles there:

  • Content typography.zd-content is defined once in packages/zudo-doc/src/content.css and shipped as @takazudo/zudo-doc/content.css; the scaffold imports that package stylesheet instead of copying its MDX typography rules

  • Project customization — keep @theme overrides, project-specific feature styles, and styles for explicit component slots in src/styles/global.css

Everything else — every project component, layout, and ordinary UI element — uses utility classes directly.

Rules Summary

  1. Always create components — not CSS classes

  2. Use utility classes directly in component markup

  3. Never create CSS module files or custom class names

  4. Use props for variants — not CSS modifiers

  5. Compose components — build complex UI from smaller components, not from more CSS

  6. Use project tokenstext-fg, bg-surface, p-hsp-md, not arbitrary values

Revision History

Takeshi TakatsudoCreated: 2026-03-17T01:50:11+09:00Updated: 2026-07-14T00:01:01+09:00

AI Assistant

Ask a question about the documentation.

Preview theme

Loading theme previews…