Styling
Pracht optimizes style loading for CSS that exists at build time. Prefer CSS Modules, Tailwind, or plain stylesheets over runtime CSS-in-JS — especially on server-rendered routes.
Recommended Approaches
These all produce real CSS files that Vite tracks. Pracht uses that module graph to link only the stylesheets a route needs, in the initial HTML.
- CSS Modules — co-located, automatically scoped per file
- Tailwind CSS via
@tailwindcss/vite— utility-first, single generated stylesheet - Plain
.css/.scssimports — global or module-scoped by convention - PostCSS pipelines (Open Props, Pico, etc.) — anything emitted as a static stylesheet
import styles from "./home.module.css";
export default function Home() {
return <h1 class={styles.title}>Hello</h1>;
}import { defineConfig } from "vite";
import { pracht } from "@pracht/vite-plugin";
import tailwindcss from "@tailwindcss/vite";
export default defineConfig({
plugins: [pracht(), tailwindcss()],
});See Performance → CSS Per Page for how pracht maps routes to their transitive CSS dependencies.
Route CSS works the same in every hydration mode. A hydration: "none" or "islands" route still gets the CSS it imports, plus the images, fonts, and @imported stylesheets that CSS references.
Assets those routes import directly are published too, like import dots from "./dots.svg" or a ?pracht image. An asset that only an API route or a loader reads stays server-side.
Each page also links the CSS of the islands it rendered, including deferred islands (client="visible", client="idle"), so island markup does not paint unstyled first.
pracht build writes the route-to-stylesheet mapping to dist/server/css-manifest.json, which pracht inspect build reads. Nothing reads it at runtime.
pracht dev links the same CSS in the initial HTML. Import CSS from a route or shell; you do not need a development-only <link> in head().
Production links route-scoped stylesheets by default. For small stylesheets,
pracht({ inlineCss: true }) puts the matched route and shell CSS in the
document instead. That removes a render-blocking request but repeats shared CSS
in every HTML response, so measure the trade-off.
It tends to win on static or content sites that visitors enter cold from search.
build.cssCodeSplit: false is not supported: it merges all CSS into one file
that pracht's per-route documents never link, so the build refuses it.
CSS-in-JS — Use With Care
Runtime CSS-in-JS libraries like styled-components, Emotion, and goober work in a pracht app, but pracht cannot collect their runtime-generated styles into the server-rendered HTML.
| Route mode | CSS-in-JS support |
|---|---|
spa (CSR only) |
✅ Works — styles are injected on the client after mount |
ssr / ssg / isg |
⚠️ Flash of unstyled content until hydration catches up |
On server-rendered routes:
- The server renders HTML without the matching
<style>tags. - The browser paints the unstyled HTML.
- Client JavaScript runs and injects the styles.
- The browser repaints: a visible flash of unstyled content that hurts Core Web Vitals.
Guidance: use a build-time approach (CSS Modules, Tailwind, plain CSS) for any route that runs on the server. Keep CSS-in-JS for SPA-only routes if you really want it.
First-class CSS-in-JS support, with styles extracted during SSR, depends on upstream work tracked in pracht#30.
CSS Modules Walkthrough
CSS Modules scope class names to their file by default. Import the module and reference classes from the resulting object:
.hero {
padding: 4rem 2rem;
text-align: center;
}
.title {
font-size: 2.5rem;
font-weight: 700;
}import styles from "./home.module.css";
export default function Home() {
return (
<section class={styles.hero}>
<h1 class={styles.title}>Welcome</h1>
</section>
);
}Vite generates unique class names at build time (e.g. _hero_1a2b3), so styles never collide across routes. The framework automatically injects only the CSS files used by the current route, its shell, and the islands it rendered.
Tailwind CSS Setup
Install Tailwind's Vite plugin and add it alongside the pracht plugin:
pnpm add -D @tailwindcss/vite tailwindcssimport { defineConfig } from "vite";
import { pracht } from "@pracht/vite-plugin";
import tailwindcss from "@tailwindcss/vite";
export default defineConfig({
plugins: [pracht({ /* ... */ }), tailwindcss()],
});Import Tailwind in your global CSS or shell:
@import "tailwindcss";Tailwind classes work in any route regardless of render mode — the generated stylesheet is a static asset that the framework includes in the HTML.