Skip to content

Theme documentation

Theme

Every component uses the light/dark tokens in registry/ninaero/theme.mjs. The workshop and installable registry use this same source.

Shared surfaces

Background

Card

Primary

Secondary

Muted

Accent

Use the theme selector in the header to compare light and dark surfaces.

Accent presets

Choose an accent with the Accent selector. Accent and color mode are independent. Primary actions, selected controls, links, and focus rings follow the palette. Paper, ink, structural borders, and status colors stay unchanged. The site remembers your choice when browser storage is available.

Installed themes support the same presets without a React provider. Set data-accent on the root element and use the dark class for dark mode. Edit preset colors in registry/ninaero/accents.mjs, then regenerate the registry.

Lavender accent in dark mode
<html data-accent="lavender" class="dark">
Compare accent buttons in the workshop

Site-wide overlays

Site-wide overlays add an optional static texture behind page content and controls, including menus and dialogs. See Add a site-wide overlay for setup and tuning.

Typography

Google Sans Flex handles interface text and headings, with its ROND axis set to 100. Google Sans Code handles code and other monospaced text. Pair a font size with a type-* role to set weight, line height, and letter spacing. Headings use tighter spacing; body text, controls, and captions keep normal spacing.

Typography roles
<h2 className="text-xl type-heading">Account settings</h2><p className="text-sm type-body">Manage your profile and preferences.</p>

Compact controls keep a 16px line height. Dialog titles and ruled prose keep a 24px line height. Role utilities leave font sizes unchanged, including responsive input sizing.

View the typography specimen

Elevation

Choose a shadow by the element's distance from the paper beneath it. Elevation does not change stacking order. Use z-index separately for overlays.

Flat
Page content
shadow-flat
Control
Raised buttons
shadow-control
Floating
Menus and popovers
shadow-floating
Dialog
Modal panels
shadow-dialog

Groups made entirely of raised buttons share one control shadow; their buttons keep their pressed states. The header uses the paper shadow-color context so the cast blends with its neutral background.

Semantic elevation utility
<Card className="shadow-floating">...</Card>

Update the theme

  1. Edit the tokens and typography in registry/ninaero/theme.mjs.
  2. Regenerate the CSS, registry, and documentation with the command below.
  3. Review the updated theme across the component documentation pages before publishing.
Regenerate theme and registry
pnpm registry:build

Do not edit app/globals.css or registry.json directly. They are generated.

Light and dark tokens

Theme token source
{  "light": {    "background": "oklch(1 0 0)",    "neutral-border": "#e8e8e8",    "neutral-input": "#b8b8b8",    "input-edge": "#c8c8c8",    "input-fill": "oklch(0.975 0 0)",    "input-placeholder": "oklch(0.5 0 0)",    "input-disabled-fill": "var(--muted)",    "tabs-track-fill": "#ededed",    "tabs-active-fill": "#f6f6f6",    "tabs-active-top": "#ffffff",    "tabs-active-bottom": "#e1e1e1",    "tabs-active-edge": "#777777",    "tabs-active-highlight": "#ffffff80",    "tabs-hover-fill": "#e3e3e3",    "tabs-ink": "#575757",    "recess-shade-weight": "6%",    "recess-highlight-weight": "35%",    "foreground": "oklch(0.145 0 0)",    "display-ink-top": "oklch(0.32 0 0)",    "display-ink-bottom": "oklch(0.22 0 0)",    "display-ink-shadow": "#00000014",    "card": "oklch(1 0 0)",    "card-foreground": "oklch(0.145 0 0)",    "popover": "var(--card)",    "dialog-fill-shade": "color-mix(in oklab, var(--popover) 98%, black)",    "dialog-highlight": "#ffffffb3",    "dialog-shade": "#0000000d",    "tooltip-scanline-ink": "color-mix(in oklab, var(--background) 7%, transparent)",    "popover-foreground": "var(--card-foreground)",    "secondary": "oklch(0.97 0 0)",    "secondary-foreground": "oklch(0.205 0 0)",    "muted": "oklch(0.97 0 0)",    "muted-foreground": "oklch(0.556 0 0)",    "destructive": "#b02f1e",    "chart-1": "oklch(0.87 0 0)",    "chart-2": "oklch(0.556 0 0)",    "chart-3": "oklch(0.439 0 0)",    "chart-4": "oklch(0.371 0 0)",    "chart-5": "oklch(0.269 0 0)",    "radius": "0.625rem",    "field-height": "2.25rem",    "field-padding-inline": "1rem",    "sidebar": "oklch(0.985 0 0)",    "sidebar-foreground": "oklch(0.145 0 0)",    "button-primary-fill": "#b9e9f5",    "button-primary-hover": "#a2dfef",    "button-primary-pressed": "#8dd4e6",    "button-primary-ink": "#123846",    "button-primary-edge": "#3a8197",    "button-link-ink": "#17677d",    "button-focus": "#006d89",    "accent-soft": "#e6f4f8",    "glass-mark-rim-0": "#3a8197",    "glass-mark-rim-50": "#367a8f",    "glass-mark-rim-100": "#337387",    "glass-mark-face-0": "#a2dfef",    "glass-mark-face-50": "#98d9ea",    "glass-mark-face-100": "#8dd4e6",    "primary": "var(--button-primary-fill)",    "primary-foreground": "var(--button-primary-ink)",    "accent": "var(--accent-soft)",    "accent-foreground": "var(--button-link-ink)",    "ring": "var(--button-focus)",    "border": "var(--neutral-border)",    "input": "var(--neutral-input)",    "sidebar-border": "var(--neutral-border)",    "sidebar-primary": "var(--button-primary-fill)",    "sidebar-primary-foreground": "var(--button-primary-ink)",    "sidebar-accent": "var(--accent-soft)",    "sidebar-accent-foreground": "var(--button-link-ink)",    "sidebar-ring": "var(--button-focus)",    "button-paper-fill": "#f8f8f8",    "button-paper-edge": "#c5c5c5",    "button-outline-edge": "#c8c8c8",    "button-paper-hover": "#eeeeee",    "button-paper-ink": "#383838",    "button-paper-text-shadow": "0 1px 0 #ffffffb3",    "control-label-text-shadow": "0 0.5px 0 #ffffff80",    "button-paper-pressed": "#e3e3e3",    "button-paper-highlight": "#ffffffa6",    "button-paper-shade": "#3333330d",    "button-destructive-fill": "#ffd9cd",    "button-destructive-hover": "#ffcab9",    "button-destructive-pressed": "#f5b3a0",    "button-destructive-ink": "#7e291b",    "button-destructive-edge": "#c25038",    "button-destructive-highlight": "#ffffff66",    "button-destructive-shade": "#5a1b0a14",    "button-highlight": "#ffffff80",    "shadow-surface": "var(--background)",    "shadow-color": "from color-mix(in oklab, var(--shadow-surface) 40%, black) h s l",    "paper-fill": "#f9f9f9",    "paper-rule": "#79797914",    "paper-dot": "#79797929",    "paper-rule-strong": "#79797940",    "paper-notebook-mask": "url(\"data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%22120%22%20height%3D%22120%22%20viewBox%3D%220%200%20120%20120%22%20fill%3D%22none%22%3E%3Cpath%20d%3D%22M0%206H120%20M0%2012H120%20M0%2018H120%20M0%2024H120%20M0%2030H120%20M0%2036H120%20M0%2042H120%20M0%2048H120%20M0%2054H120%20M0%2060H120%20M0%2066H120%20M0%2072H120%20M0%2078H120%20M0%2084H120%20M0%2090H120%20M0%2096H120%20M0%20102H120%20M0%20108H120%20M0%20114H120%22%20stroke%3D%22white%22%20stroke-opacity%3D%220.3125%22%20stroke-width%3D%221%22%20vector-effect%3D%22non-scaling-stroke%22%2F%3E%3Cpath%20d%3D%22M0%200H120V120H0Z%22%20stroke%3D%22white%22%20stroke-width%3D%222%22%20vector-effect%3D%22non-scaling-stroke%22%2F%3E%3Cpath%20d%3D%22M0%200h0%20M120%200h0%20M0%20120h0%20M120%20120h0%22%20stroke%3D%22white%22%20stroke-width%3D%226%22%20stroke-linecap%3D%22round%22%20vector-effect%3D%22non-scaling-stroke%22%2F%3E%3C%2Fsvg%3E\")",    "dither-mask": "url(\"data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%228%22%20height%3D%228%22%20viewBox%3D%220%200%208%208%22%20fill%3D%22none%22%20shape-rendering%3D%22crispEdges%22%3E%3Cpath%20d%3D%22M0%200L8%208M8%200L0%208%22%20stroke%3D%22white%22%20stroke-width%3D%221%22%20vector-effect%3D%22non-scaling-stroke%22%2F%3E%3C%2Fsvg%3E\")",    "overlay-flipnote-mask": "url(\"data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%228%22%20height%3D%228%22%20viewBox%3D%220%200%208%208%22%20fill%3D%22white%22%20shape-rendering%3D%22crispEdges%22%3E%3Cpath%20d%3D%22M0%200h1v1H0z%20M4%201h1v1H4z%20M2%203h1v1H2z%20M6%203h1v1H6z%20M1%205h1v1H1z%20M5%205h1v1H5z%20M3%207h1v1H3z%20M7%207h1v1H7z%22%2F%3E%3C%2Fsvg%3E\")",    "overlay-dots-mask": "url(\"data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%22100%25%22%20height%3D%22100%25%22%20fill%3D%22white%22%3E%3Ccircle%20cx%3D%2250%25%22%20cy%3D%2250%25%22%20r%3D%220.75%22%2F%3E%3C%2Fsvg%3E\")",    "overlay-checker-mask": "conic-gradient(black 25%, transparent 0 50%, black 0 75%, transparent 0)",    "ninaero-overlay-blend-mode": "multiply",    "ninaero-overlay-opacity": "0.06",    "ninaero-overlay-coverage": "0.5",    "ninaero-overlay-compensation": "1"  },  "dark": {    "background": "oklch(0.145 0 0)",    "neutral-border": "#303030",    "neutral-input": "#555555",    "input-edge": "#454545",    "input-fill": "oklch(0.18 0 0)",    "input-placeholder": "var(--muted-foreground)",    "input-disabled-fill": "var(--input-fill)",    "tabs-track-fill": "#242424",    "tabs-active-fill": "#3a3a3a",    "tabs-active-top": "#454545",    "tabs-active-bottom": "#2e2e2e",    "tabs-active-edge": "#aaaaaa",    "tabs-active-highlight": "#ffffff1a",    "tabs-hover-fill": "#303030",    "tabs-ink": "#b5b5b5",    "recess-shade-weight": "12%",    "recess-highlight-weight": "7%",    "foreground": "oklch(0.985 0 0)",    "display-ink-top": "oklch(0.985 0 0)",    "display-ink-bottom": "oklch(0.88 0 0)",    "display-ink-shadow": "#00000040",    "card": "oklch(0.205 0 0)",    "card-foreground": "oklch(0.985 0 0)",    "popover": "var(--card)",    "dialog-fill-shade": "color-mix(in oklab, var(--popover) 92%, black)",    "dialog-highlight": "#ffffff0d",    "dialog-shade": "#00000040",    "tooltip-scanline-ink": "color-mix(in oklab, var(--background) 3%, transparent)",    "popover-foreground": "var(--card-foreground)",    "secondary": "oklch(0.269 0 0)",    "secondary-foreground": "oklch(0.985 0 0)",    "muted": "oklch(0.269 0 0)",    "muted-foreground": "oklch(0.708 0 0)",    "destructive": "#ff9b83",    "chart-1": "oklch(0.87 0 0)",    "chart-2": "oklch(0.556 0 0)",    "chart-3": "oklch(0.439 0 0)",    "chart-4": "oklch(0.371 0 0)",    "chart-5": "oklch(0.269 0 0)",    "sidebar": "oklch(0.205 0 0)",    "sidebar-foreground": "oklch(0.985 0 0)",    "button-primary-fill": "#79cfe5",    "button-primary-hover": "#96def0",    "button-primary-pressed": "#62b9d0",    "button-primary-ink": "#102d3a",    "button-primary-edge": "#a0e3f2",    "button-link-ink": "#8adef2",    "button-focus": "#7ce1f6",    "accent-soft": "#203e48",    "glass-mark-rim-0": "#a0e3f2",    "glass-mark-rim-50": "#97d7e5",    "glass-mark-rim-100": "#8fcbd9",    "glass-mark-face-0": "#96def0",    "glass-mark-face-50": "#7ccbe0",    "glass-mark-face-100": "#62b9d0",    "primary": "var(--button-primary-fill)",    "primary-foreground": "var(--button-primary-ink)",    "accent": "var(--accent-soft)",    "accent-foreground": "var(--button-link-ink)",    "ring": "var(--button-focus)",    "border": "var(--neutral-border)",    "input": "var(--neutral-input)",    "sidebar-border": "var(--neutral-border)",    "sidebar-primary": "var(--button-primary-fill)",    "sidebar-primary-foreground": "var(--button-primary-ink)",    "sidebar-accent": "var(--accent-soft)",    "sidebar-accent-foreground": "var(--button-link-ink)",    "sidebar-ring": "var(--button-focus)",    "button-paper-fill": "#343434",    "button-paper-edge": "#555555",    "button-outline-edge": "#454545",    "button-paper-hover": "#454545",    "button-paper-ink": "#efefef",    "button-paper-text-shadow": "0 1px 1px #00000066",    "control-label-text-shadow": "0 0.5px 0.5px #00000059",    "button-paper-pressed": "#2e2e2e",    "button-paper-highlight": "#ffffff0d",    "button-paper-shade": "#00000040",    "button-destructive-fill": "#ffa088",    "button-destructive-hover": "#ffb39d",    "button-destructive-pressed": "#ed8b73",    "button-destructive-ink": "#35160f",    "button-destructive-edge": "#ffc5b5",    "button-destructive-highlight": "#ffffff1a",    "button-destructive-shade": "#00000040",    "button-highlight": "#ffffff33",    "shadow-surface": "var(--background)",    "shadow-color": "from color-mix(in oklab, var(--shadow-surface) 40%, black) h s l",    "paper-fill": "#232323",    "paper-rule": "#cecece0f",    "paper-dot": "#cecece24",    "paper-rule-strong": "#cecece30",    "ninaero-overlay-blend-mode": "screen"  }}
Theme registry JSON