Styling | Koze | Primitives Docs

Styling

Global CSS, Tailwind, and static assets in Koze

Koze is Vite-first. CSS uses plain Vite plugins — Tailwind, PostCSS, CSS Modules, Lightning CSS — you install them, wire them into vite.config.ts, and they run on the global stylesheet end-to-end. No Koze-specific CSS config.

The two CSS paths

You want... Put it here What happens
A stylesheet Vite should process (Tailwind, PostCSS, CSS Modules, imports from node_modules…) src/app.css Koze registers it as a Rollup input. Every Vite CSS plugin runs on it. Emitted as a hashed asset. <link> auto-injected into the shell.
A static file Vite should not touch (prebuilt CSS, favicons, images, fonts, JSON, PDFs…) src/assets/ Served verbatim by Wrangler's ASSETS binding. Never processed. Reference it with a plain absolute URL.

One convention, zero config.

src/app.css — global stylesheet

Drop a file at src/app.css. Koze wires it up automatically:

  1. Registered as a client Rollup input, so every Vite CSS plugin processes it.
  2. Emitted as a content-hashed asset (e.g. /assets/koze-global-css-BLFtAlQt.css) in production.
  3. <link rel="stylesheet" href={...}> is injected right before </head> in your app shell. You don't write the <link> tag.

In development, the same file is served through Vite's module graph, so plugin transforms (Tailwind, PostCSS, CSS Modules, live reload) run on every edit.

/* src/app.css */
body {
  margin: 0;
  font-family: system-ui, sans-serif;
  background: #0a0e17;
  color: #e6e9ef;
}

That's it. No build step, no Koze config.

Note: You can still author <link rel="stylesheet"> tags in src/app.koze for third-party stylesheets (Google Fonts, CDN libraries). Koze only auto-injects the one pointing at src/app.css.

Tailwind CSS

Tailwind v4 is a Vite plugin. Install it alongside any companion plugins:

npm install -D tailwindcss @tailwindcss/vite daisyui @tailwindcss/forms

Wire it into Vite:

// vite.config.ts
import { defineConfig } from 'vite';
import { koze } from '@kuratchi/koze/vite';
import tailwindcss from '@tailwindcss/vite';

export default defineConfig({
  plugins: [koze(), tailwindcss()],
});

Declare Tailwind + plugins + theme in your global stylesheet:

/* src/app.css */
@import "tailwindcss";
@plugin "daisyui";
@plugin "@tailwindcss/forms";

/* Tailwind v4 default source detection skips the .koze extension.
   Tell it explicitly to scan route files for utility usage. */
@source "./routes/**/*.koze";

@theme {
  --color-primary: #0ea5e9;
  --font-display: 'Inter', sans-serif;
}

Use utilities in any route:

<button class="btn btn-primary">Click me</button>

That's the full integration. No kuratchi.config.ts entries, no CLI step, no watch process.

Warning: When scanning route files with Tailwind, the @source "./routes/**/*.koze" directive is required. Without it Tailwind won't see utility classes used in your templates and will purge them from the output.

Static assets — src/assets/

Anything in src/assets/ is served verbatim. It's Wrangler's ASSETS binding in production and Vite's publicDir in dev. No plugin ever transforms these files.

Use this for:

  • Prebuilt CSS from a CDN-style library (bootstrap.min.css, pico.min.css)
  • Favicons and app icons
  • Static images, fonts, JSON, PDFs
  • Anything you want served byte-for-byte
src/
├─ app.css                 → processed by Vite
└─ assets/
   ├─ favicon.ico          → /favicon.ico
   ├─ hero.png             → /hero.png
   ├─ fonts/inter.woff2    → /fonts/inter.woff2
   └─ bootstrap.min.css    → /bootstrap.min.css  (served as-is)

Reference them with absolute URLs:

<!-- src/app.koze -->
<link rel="icon" href="/favicon.ico" />
<link rel="stylesheet" href="/bootstrap.min.css" />

Tip: Rule of thumb: processed → src/app.css. Verbatim → src/assets/. If Tailwind or PostCSS needs to see it, it goes in src/app.css (or is imported from there). If it's a finished file you want at /<path>, drop it in src/assets/.

CSS Modules

Vite supports CSS Modules out of the box. Import them from server-side modules:

/* src/server/styles/card.module.css */
.card {
  padding: 1rem;
  border-radius: 8px;
}
// src/server/card.ts
import styles from './styles/card.module.css';
export { styles };

PostCSS

PostCSS runs automatically when a postcss.config.js exists at the project root:

// postcss.config.js
export default {
  plugins: {
    autoprefixer: {},
  },
};

Lightning CSS

Vite can use Lightning CSS as its CSS transformer:

// vite.config.ts
import { defineConfig } from 'vite';
import { koze } from '@kuratchi/koze/vite';

export default defineConfig({
  plugins: [koze()],
  css: {
    transformer: 'lightningcss',
  },
});

CSS frameworks (Bootstrap, Pico, Bulma, …)

Two options. Pick one per library.

Option A — process via @import (recommended when installed as a package):

/* src/app.css */
@import "bootstrap/dist/css/bootstrap.min.css";

.my-customizations { /* ... */ }

Vite resolves the import through node_modules. The final bundle is a single hashed CSS asset.

Option B — drop prebuilt CSS in src/assets/ (recommended when you want a stable URL):

cp node_modules/@picocss/pico/css/pico.min.css src/assets/pico.css
<!-- src/app.koze -->
<link rel="stylesheet" href="/pico.css" />

Migrating from the old css config

Older Koze projects used a css.tailwind / css.plugins option in kuratchi.config.ts. That config file no longer exists. The migration:

  1. Install @tailwindcss/vite and add it to vite.config.ts.
  2. Move src/assets/app.csssrc/app.css.
  3. Delete kuratchi.config.ts (every block has a new home — see Configuration).
  4. Remove <link rel="stylesheet" href="/assets/app.css" /> from your shell — Koze injects it now.

Best practices

One global stylesheet src/app.css is the single Vite-processed entry. Import everything else from it. Assets stay verbatim src/assets/ is for finished files served at stable URLs. Never put Vite-processed CSS here. Use CSS custom properties Define colors, spacing, and typography as variables for theming and consistency. Add plugins at Vite layer Tailwind, PostCSS, Lightning CSS — all plain Vite plugins. No framework config needed.