zfb

Type to search...

to open search from anywhere

Styling

CreatedJun 1, 2026Takeshi Takatsudo

Global CSS, Tailwind v4, and where component-scoped styling fits in zfb.

Styling in zfb has two layers — global CSS and Tailwind v4 — and one well-supported pattern for everything else: utility classes on the markup itself.

Global CSS

The default template ships with styles/global.css. This is plain CSS, processed by zfb’s CSS pipeline, and made available to every page. Use it for design tokens, resets, base typography, and anything else that should apply site-wide:

:root {
  --color-text: #1a1a1a;
  --color-bg: #ffffff;
  --font-body: system-ui, sans-serif;
}

body {
  color: var(--color-text);
  background: var(--color-bg);
  font-family: var(--font-body);
}

Imports inside CSS work as you would expect — split your styles across files and pull them together from global.css.

Tailwind v4

Tailwind v4 is opt-in via zfb.config.{ts,json}:

{
  "tailwind": {
    "enabled": true
  }
}

When enabled, the zfb-css crate runs the bundled tailwindcss-v4 binary as part of the build. There is no per-project Tailwind install — you do not add tailwindcss to package.json and you do not maintain a tailwind.config.js. The compiler is built into zfb itself.

Once Tailwind is on, utility classes work in any .tsx file:

export default function Hero() {
  return (
    <section className="mx-auto max-w-2xl px-6 py-12">
      <h1 className="text-3xl font-bold">Hello</h1>
    </section>
  );
}

Tailwind v4’s CSS-first configuration is supported through @theme directives in global.css — you customise tokens by editing CSS, not a JS config file.

Component-scoped styling

There are two well-supported patterns for component-level styling: Tailwind utility classes, and CSS Modules.

Tailwind utility classes

The simplest pattern is global CSS for site-wide concerns plus Tailwind utility classes for component-level styling. This keeps the build fast and the runtime trivial, and it maps cleanly onto Tailwind v4’s design-token model.

CSS Modules

For genuinely component-scoped CSS — class names that must not collide across components — zfb supports CSS Modules. Any file named *.module.css is a CSS Module: its class names are rewritten to scoped, file-stable identifiers at build time, so two components can both define a .button class without clashing.

Author the styles in a .module.css file:

.card {
  border: 1px solid var(--color-border);
  border-radius: 8px;
  padding: 1rem;
}

.title {
  font-weight: 700;
}

Import the module with a default import and read class names off the imported object:

import styles from "./card.module.css";

export default function Card() {
  return (
    <div className={styles.card}>
      <h3 className={styles.title}>Hello</h3>
    </div>
  );
}

At build time zfb resolves styles.card to the scoped class name (e.g. KdPA9G_card) — the rendered HTML carries that scoped class, and the scoped CSS is folded into the same hashed dist/assets/styles-<hash>.css stylesheet as the rest of your CSS. There is no separate .css file per module and no runtime cost: the lookup is resolved during the build.

How it works:

  • import styles from "./x.module.css" must be a default import. styles is a plain object mapping your original class names to the scoped ones.
  • Access a class with member access — styles.card or styles["card"]. Both work; computed access with a dynamic key does not, because the rewrite happens at build time.
  • Plain .css imports (a file not ending in .module.css) are still treated as global CSS — only the .module.css suffix opts a file into scoping.
  • The .module.css file is discovered when it is imported from a .tsx/.ts/.jsx/.js file under pages/, components/, layouts/, or content/.

Limitations:

  • CSS Modules imported by bare specifier from node_modules (e.g. import s from "@org/pkg/x.module.css") are not scoped — only project-relative ./ / ../ imports are.
  • The :export block and composes directive are not supported; use plain class selectors.

What lands in dist/

The build pipeline runs Tailwind and PostCSS, writes a hashed stylesheet to dist/assets/, and injects a <link rel="stylesheet"> into each rendered HTML page. The stylesheet reference is stable so CDN caches can hold it across deployments until the content changes.

Revision History