Skip to content
Component installs need the registry setup— the Base UI shadcn project, the @vegastack namespace and the Cloudflare Access service token.
VegaStack Design

Theming, dark mode & tokens

The one-file override model, the base a11y layer you must not skip, and consuming the token contract outside React.

Last updated

Everything visual routes through semantic OKLCH tokens — CSS variables defined by @vegastack/design-tokens, bridged into Tailwind utilities (bg-primary, text-muted-foreground) via the @theme inline block. Components never use raw palettes, so re-theming is a variable override, not a component change.

The import you already have

@import '@vegastack/design/preset.css' (from the Quickstart) bundles, in order: Tailwind, tw-animate-css, theme.css (the tokens, light + dark), base.css (the a11y layer), utilities.css (shimmer / scroll-fade / scrollbar). You rarely need anything else on this page.

Override: your brand in one file

Redefine variables after the preset import — that's the entire theming API:

app/globals.css
@import "@vegastack/design/preset.css";

:root {
  --primary: oklch(0.55 0.2 260); /* your brand */
  --primary-foreground: oklch(0.98 0.005 260);
  --radius: 0.5rem; /* every rounded-* follows */
}
.dark {
  --primary: oklch(0.7 0.18 260);
}

Every component repaints — buttons, focus rings, charts, badges — because nothing references anything but the tokens. The full variable list with live values is on Colors; the override contract is identical for spacing, radius, typography and motion tokens (Foundations).

Scope overrides to a subtree for per-client theming: any selector ([data-theme='acme'] { --primary: … }) works — the tokens cascade.

Dark mode

Class-based (.dark on <html>), owned by the provider — see Provider setup. You never toggle classes yourself; call setTheme('dark' | 'light' | 'system') from useVegaStackTheme().

Authoring rule for your own code: style with tokens and you get dark mode for free (bg-card is different in each theme). Reach for the dark: variant only for genuinely theme-specific art direction.

The base layer is load-bearing — don't bypass it

If you ever import pieces individually instead of the preset, know that base.css ships accessibility-critical globals:

  • the global :focus-visible ring — components deliberately carry no focus ring of their own; skip base.css and keyboard users lose focus visibility entirely (a WCAG 2.4.7 failure)
  • the prefers-reduced-motion reset
  • pointer cursors on interactive controls
  • body { isolation: isolate } — the stacking context that keeps portalled overlays (dialogs, menus, tooltips) above app chrome

Granular imports, only if you know why you're doing it:

@import "tailwindcss";
@import "tw-animate-css"; /* the preset includes it — don't lose it */
@import "@vegastack/design/theme.css";
@import "@vegastack/design/base.css"; /* NOT optional in practice */
@import "@vegastack/design/utilities.css"; /* shimmer / scroll-fade / scrollbar */

These subpaths are re-exports through @vegastack/design, resolvable under strict pnpm without declaring the tokens package yourself.

Fonts

Tokens define the families as variables (--font-family-sans → Geist, mono → Geist Mono); the app supplies the actual font files. In Next:

app/layout.tsx (fragment)
import { Geist, Geist_Mono } from "next/font/google";
const geistSans = Geist({ variable: "--font-geist-sans", subsets: ["latin"] });
const geistMono = Geist_Mono({
  variable: "--font-geist-mono",
  subsets: ["latin"],
});
// <html className={`${geistSans.variable} ${geistMono.variable}`}>

Loading alone is NOT enough — next/font registers hashed family names, and the tokens reference literal Geist. Point the token variables at the loaded fonts or you'll silently render system fonts:

app/globals.css (append)
:root {
  --font-family-sans:
    var(--font-geist-sans), ui-sans-serif, system-ui, sans-serif;
  --font-family-mono: var(--font-geist-mono), ui-monospace, monospace;
}

Details and non-Next guidance: Typography.

Tokens without React (design-tokens standalone)

@vegastack/design-tokens has zero dependencies — it's the portable design contract for surfaces that aren't React/Tailwind at all:

npm i @vegastack/design-tokens
ArtifactUse
theme.css / base.css / utilities.cssany web page, no Tailwind required
tokens.jsonDTCG-format source — pipe into Figma sync, native platforms, codegen
the JS export (import { tokens } from '@vegastack/design-tokens')typed token values in Node tooling

App-builders never install it directly (it arrives via @vegastack/design); token-pipeline and native consumers install only it.

On this page