Skip to content
brust

Styling in brust is convention-driven: an app.css next to your entry is the global stylesheet (with first-class Tailwind v4 support), and component-level CSS — plain imports or CSS Modules — is extracted into per-route chunks. All compiled CSS is served by the Rust side under /_brust/css/.

app.css and Tailwind v4

If <scanRoot>/app.css exists (scanRoot defaults to your entry's directory), brust compiles it at boot in dev and during brust build, serves the result at /_brust/css/app.css, and injects the <link> into every page — React routes get it spliced before </head>, and the <BrustPage> native document shell includes it in its head. There is no separate CSS build step to wire up.

Tailwind v4 is CSS-first — opt in inside app.css itself:

@import "tailwindcss";
@source "./**/*.{tsx,ts}";

@theme {
  --color-brand-500: oklch(0.65 0.2 255);
}

The compiler is @tailwindcss/node with the oxide scanner: @source globs resolve relative to app.css, and class candidates are scanned from the files they match. tailwindcss is a dependency of your project (the scaffold declares it), not of brust core. An app.css without the Tailwind import is also fine — it passes through as ordinary CSS.

Component CSS and CSS Modules

Components can import their own styles, in either form:

import './Card.css'                       // plain — global selectors
import styles from './Card.module.css'   // CSS Module — hashed local class names

export default function Card() {
  return <div className={styles.card}>…</div>
}

At build time brust scans the import graph, processes each CSS file (class-name hashing for .module.css; Tailwind @apply resolves when available), and emits one chunk per file at /_brust/css/components/<hash>.css. A per-route manifest maps each route to the chunks it needs: the import scan walks transitively from each route's component sources, so importing the CSS in the component that uses it is enough — routes.tsx never mentions stylesheets.

This works on native routes too: the Rust-rendered document head carries a framework-owned slot that the worker fills with the route's component-CSS <link> tags, so a native page gets its module styles in the initial HTML.

Typing comes in two layers: an ambient *.module.css declaration ships with the framework (imports resolve as Record<string, string> out of the box), and the build also emits a precise per-module .d.ts (one readonly key per exported class) under the build output's types/ directory — generated artifacts, not files in your source tree.

The /_brust/css/* URLs

URL Contents
/_brust/css/app.css The compiled global stylesheet.
/_brust/css/components/<hash>.css One chunk per imported component CSS file. The hash is derived from the file's relative path, so the URL is stable across content edits.

The Rust server serves both with Cache-Control: public, max-age=3600 in production and no-store in dev (so hot reload always sees fresh CSS). Filenames are strictly validated server-side; only .css files from the build output are reachable.

Self-hosted fonts

Fonts follow the public/ pattern: put the .woff2 files under public/fonts/ (served at /fonts/...) and declare the @font-face rules in app.css. This site does exactly that:

@font-face {
  font-family: "Schibsted Grotesk";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("/fonts/schibsted-grotesk-latin-400-normal.woff2") format("woff2");
}

No third-party font CDN, no render-blocking cross-origin request — the font ships with the app and is cached under the same static-asset policy as the rest of public/.

Next

Applied walkthroughs, including the markdown pipeline this site is built on, live in the Guides section.