Docs

Styling & Theming

The DS resolves every color, space, and radius through a --yl-* CSS custom property. That's what lets primitives render on the server and lets a theme switch be one attribute write. This guide covers how the stylesheets are shipped in 0.2.0 and the one rule that trips people up: the 10px root.

#Two required sheets, the rest opt-in

In 0.2.0, component styles moved out of themeCss() and into shipped files. Always import the tokens and the base layer; add a component's sheet when you render it:

ts
import "@yoltra/ds/styles/tokens.css"; // --yl-* variables, light + dark
import "@yoltra/ds/styles/base.css";   // html 62.5% root, .yl-root, .yl-container, .yl-visually-hidden
import "@yoltra/ds/styles/button.css";
import "@yoltra/ds/styles/modal.css";  // Dialog + Drawer

This is deliberate. The JavaScript tree-shakes, but a single stylesheet carrying every component's rules cannot — it becomes a cost every app pays regardless of what it renders. For a docs site or a prototype, @yoltra/ds/styles/all.css carries everything in one import.

Migrating from 0.1.0? themeCss() used to emit variables and every component's rules. It now emits variables only. Import the component sheets (or all.css) or your components will render unstyled.

#themeCss() — for a server render

themeCss() is still exported and emits the same custom properties, for when you need them inlined before first paint rather than linked:

tsx
<style dangerouslySetInnerHTML={{ __html: themeCss() }} />

It gives you the variables only — pair it with the component sheets (or link tokens.css instead). A file-based app doesn't need it at all.

#1rem is 10px

base.css sets html { font-size: 62.5% }, making the root 10px so every length reads as its pixel value over ten — 1.6rem is 16px, 0.4rem is 4px. Tokens are authored in pixels (because spacing[4] being 16 beats 1.6) and only the emitted value carries the unit. rem rather than px so a reader's font-size preference still scales the interface.

Three things follow, and they're easy to get wrong:

  • Set your body font-size. .yl-root carries no font-size, so inherited text resolves against the 10px root. Pin a base (e.g. body { font-size: 1.6rem }).
  • The root declaration is global. It affects the whole document. An app that can't accept that imports @yoltra/ds/styles/base-no-root.css and sets its own root — at which point every --yl-* length is relative to whatever it chooses.
  • Breakpoints and hairlines stay in pixels. A rem breakpoint resolves against the initial root, not the 62.5% one, so it would silently be 1.6× its value; a 1px border should be 1px.

#Theming

Theming is data-theme on the document root — "light" or "dark":

tsx
import { ThemeProvider, useTheme, applyTheme } from "@yoltra/ds/client";

ThemeProvider holds the theme and reflects it onto the root; useTheme() reads and sets it; applyTheme("dark") writes the attribute directly (handy in a pre-hydration script to avoid a flash of the wrong theme). Consumers that own their state — like this site, which drives the theme through a Yoltra store — skip the provider and set data-theme themselves. The DOM contract is identical.

See the API reference for foundationTokens, lightTheme / darkTheme, and every --yl-* name.