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:
- Registered as a client Rollup input, so every Vite CSS plugin processes it.
- Emitted as a content-hashed asset (e.g.
/assets/koze-global-css-BLFtAlQt.css) in production. <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 insrc/app.kozefor third-party stylesheets (Google Fonts, CDN libraries). Koze only auto-injects the one pointing atsrc/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 insrc/app.css(or is imported from there). If it's a finished file you want at/<path>, drop it insrc/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:
- Install
@tailwindcss/viteand add it tovite.config.ts. - Move
src/assets/app.css→src/app.css. - Delete
kuratchi.config.ts(every block has a new home — see Configuration). - 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.