Style pages with CSS Modules, a Go-native Tailwind, and a built-in CSS pipeline.
#Styling
Krate ships a CSS pipeline built into the compiler: CSS Modules, Go-native Tailwind, rule-level deduplication, minification, and @import inlining. No PostCSS or extra tooling is required.
#CSS Modules
Files named *.module.css are scoped automatically:
// Card.module.css
.card {
padding: 1rem;
border-radius: 8px;
}import styles from './Card.module.css';
export default function Card() {
return <div class={styles.card}>…</div>;
}- Class names are hashed:
className→className_<fnv32a_hash>. - The hash is FNV-32a of the absolute file path (6-char base36), so it's deterministic per file and stable across builds.
- Scoping is tokenizer-based: strings, comments,
url(...), attribute selectors, and:global(...)are handled correctly.:global(.x)stays unscoped (the wrapper is removed), andcomposes:local names are mapped.
#Tailwind CSS
Krate's Tailwind is Go-native — no PostCSS, no Node at build time — and follows the Tailwind v4 CSS-first model.
tailwind: {
enabled: true,
scanDirs: ["src"], // or: content: ["./src/**/*.{tsx,mdx}"]
preflight: false, // opt-in base reset
strict: false, // warn on classes that produce no rule
darkMode: "media", // "media" | "class" | "selector"
}#CSS-first @theme
Define design tokens directly in CSS; Krate reads @theme { --… } blocks from your stylesheets and generates the matching utilities (no tailwind.config.ts needed for these):
@theme {
--color-brand-500: #ff4d4d;
--spacing-7: 1.75rem;
--radius-xl: 1rem;
--breakpoint-3xl: 120rem;
}
/* now: bg-brand-500, p-7, rounded-xl, 3xl:… all work */Supported namespaces: --color- (incl. DEFAULT and name-<shade>), --spacing-, --radius-, --shadow-, --opacity-, --leading-, --breakpoint-, --font-.
#@apply
@apply is supported in your CSS. Plain utilities are expanded in place; variant/descendant/unknown utilities cannot be represented as plain declarations and are reported as build warnings. @tailwind directives are stripped and @layer { … } blocks are unwrapped (Krate owns the layer order).
- A candidate scanner extracts Tailwind tokens from every string and template literal in the scanned files, so classes inside
class={cond ? "a" : "b"},clsx()/cn()/cva()arguments, and arrays are all found. - The CSS generator maps classes to rules from a built-in rule set. Output is deterministic — identical inputs always produce the same stylesheet.
- Theme configuration lives in
tailwind.config.ts. It is statically parsed by default (no Node required); settailwind.executeConfig: trueto execute it vianpx tsxinstead.theme.extendmerges onto the defaults; top-levelthemekeys replace them.
<div class="p-4 hover:bg-zinc-100 dark:bg-zinc-900 w-[100px] -mt-2 bg-blue-500/50">
Responsive, dark-mode aware, arbitrary values, negatives, color opacity.
</div>Supported features:
- Variants: state (
hover:,focus:,active:,disabled:, …),group-/peer-(incl. named),data-/aria-/has-, responsive breakpoints fromtheme.screens(sm:,md:, …, plusmin-/max-),dark:,motion-safe:/motion-reduce:,print:, capability queries (pointer-coarse:,noscript:,forced-colors:, …), positional (nth-,first-line:),not-, child (:,**:), container queries (@sm:,@[400px]:), and arbitrary variants ([&:nth-child(3)]:). - Arbitrary values for nearly every property, with type disambiguation (
w-[100px],bg-[url(/a.png)],bg-[length:8px],text-[14px]→ font-size vstext-[#fff]→ color,grid-cols-[repeat(3,_1fr)]), including typed hints (text-[color:var(--fg)]). - Negatives (
-mt-4), color opacity modifiers (bg-blue-500/50,from-indigo-400/50), and arbitrary spacing multiples (p-13,p-13.5). - Filters (
blur-,brightness-,grayscale,hue-rotate-,invert,saturate-,sepia,drop-shadow-, andbackdrop-), animation (animate-*), blend modes, plus layout, typography, interactivity, 3D-transform, border, and background families. - Preflight: opt in with
tailwind.preflight: true.
#Differences from Tailwind
Krate implements Tailwind's utility and variant syntax but is not byte-identical to the Tailwind CLI. JS plugins and the @tailwindcss/* plugin ecosystem remain out of scope. Unrecognized classes produce no rule (enable tailwind.strict to surface them).
#Global CSS & code splitting
Plain CSS imported or referenced in pages is collected and split at the rule level: rules used by two or more pages are written once to a shared styles.<hash>.css chunk, and page-unique rules go into that page's own hashed chunk. A component library imported across many pages therefore ships once, not per page. Chunk order is deterministic, so hashes are stable across builds. See Contributing → Architecture for the full processing steps.
#Custom properties & theming
There's nothing special needed to use CSS custom properties — they pass through the pipeline unchanged. The docs site uses a :root[data-theme="dark"] scheme toggled at runtime for light/dark theming.
Last updated: 2026-10-16
Contributors: Noinkin