Browse the docs
Getting started
Theming
All design decisions live in one CSS file of tokens. Components read the tokens, so changing a value restyles everything in both frameworks.
How it works
- 1:root holds the light theme, .dark the dark theme. Add the dark class to <html> to switch.
- 2Tailwind maps each token to utilities: --primary becomes bg-primary, text-primary and border-primary.
- 3Derived shades are computed with color-mix, so a new primary updates hover, edge and glow automatically.
- 4The type scale and fonts live in @theme inline, which creates text-display through text-caption.
- 5In the Stardust repo, edit theme/stardust.css and run bun run registry to publish the change.
Change the primary color
:root {
--primary: oklch(0.38 0.152 18.6); /* #800020 */
--primary-highlight: oklch(0.8 0.12 19.4 / 50%);
}
.dark {
--primary: oklch(0.519 0.203 19.5); /* #C20E35 */
}Add a badge color
Each colored badge sets one variable; the shared recipe derives border, tint, text and icon color from it.
info: `[--badge:var(--chart-5)] ${TINT}`,The full theme file
/* Stardust tokens. Single source for the React and Vue registries; edit here, then run `bun run registry`. */
@theme inline {
--color-success: var(--success);
--color-warning: var(--warning);
/* Load Geist in the app (next/font or a <link>); the stack falls back to the system font. */
--font-sans: "Geist", ui-sans-serif, system-ui, sans-serif;
--font-heading: "Geist", ui-sans-serif, system-ui, sans-serif;
/* Type scale: text-display, text-title, text-heading, text-body, text-caption. */
--text-display: 2.25rem;
--text-display--line-height: 2.75rem;
--text-display--letter-spacing: -0.025em;
--text-display--font-weight: 600;
--text-title: 1.5rem;
--text-title--line-height: 2rem;
--text-title--letter-spacing: -0.02em;
--text-title--font-weight: 600;
--text-heading: 1.0625rem;
--text-heading--line-height: 1.5rem;
--text-heading--letter-spacing: -0.01em;
--text-heading--font-weight: 600;
--text-body: 0.875rem;
--text-body--line-height: 1.5rem;
--text-caption: 0.75rem;
--text-caption--line-height: 1rem;
--text-caption--letter-spacing: 0.01em;
}
/* Light: rose-tinted neutrals, burgundy primary (#800020). White on primary is 10.8:1.
Palette: burgundy #800020 (brightened from #621C36), crimson #DA2049, salmon #FB7A7F, coral #F25151. */
:root {
--radius: 0.75rem;
--background: oklch(0.995 0.002 15);
--foreground: oklch(0.2 0.012 15);
--card: oklch(1 0 15);
--card-foreground: oklch(0.2 0.012 15);
--popover: oklch(1 0 15);
--popover-foreground: oklch(0.2 0.012 15);
--primary: oklch(0.38 0.152 18.6);
--primary-foreground: oklch(1 0 15);
/* Top-edge highlight on primary surfaces (button inner shadow): the palette's salmon, a step lighter. */
--primary-highlight: oklch(0.8 0.12 19.4 / 50%);
--secondary: oklch(0.965 0.008 15);
--secondary-foreground: oklch(0.25 0.015 15);
--muted: oklch(0.965 0.008 15);
--muted-foreground: oklch(0.5 0.015 15);
--accent: oklch(0.96 0.03 18);
--accent-foreground: oklch(0.347 0.104 1.4);
--destructive: oklch(0.55 0.22 27);
--success: oklch(0.55 0.14 155);
--warning: oklch(0.62 0.15 70);
--border: oklch(0.915 0.008 15);
--input: oklch(0.89 0.01 15);
--ring: oklch(0.38 0.152 18.6);
--chart-1: oklch(0.38 0.152 18.6);
--chart-2: oklch(0.73 0.158 19.4);
--chart-3: oklch(0.575 0.215 16.9);
--chart-4: oklch(0.657 0.197 24.4);
--chart-5: oklch(0.6 0.1 200);
--sidebar: oklch(0.98 0.004 15);
--sidebar-foreground: oklch(0.2 0.012 15);
--sidebar-primary: oklch(0.38 0.152 18.6);
--sidebar-primary-foreground: oklch(1 0 15);
--sidebar-accent: oklch(0.955 0.012 15);
--sidebar-accent-foreground: oklch(0.25 0.015 15);
--sidebar-border: oklch(0.915 0.008 15);
--sidebar-ring: oklch(0.38 0.152 18.6);
}
/* Dark: neutral surfaces on #0A0A0A (sidebar #0F0F0F) with a crimson-burgundy primary (#C20E35); white on it is 6.2:1.
The focus ring uses a lighter red so it clears 3:1 against the background. */
.dark {
--background: oklch(0.145 0 0);
--foreground: oklch(0.96 0 0);
--card: oklch(0.2 0 0);
--card-foreground: oklch(0.96 0 0);
--popover: oklch(0.215 0 0);
--popover-foreground: oklch(0.96 0 0);
--primary: oklch(0.519 0.203 19.5);
--primary-foreground: oklch(1 0 0);
--primary-highlight: oklch(0.8 0.12 19.4 / 50%);
--secondary: oklch(0.255 0 0);
--secondary-foreground: oklch(0.96 0 0);
--muted: oklch(0.255 0 0);
--muted-foreground: oklch(0.7 0 0);
--accent: oklch(0.29 0.085 4);
--accent-foreground: oklch(0.84 0.1 19);
--destructive: oklch(0.7 0.19 22);
--success: oklch(0.74 0.15 155);
--warning: oklch(0.8 0.15 75);
--border: oklch(1 0 0 / 10%);
--input: oklch(1 0 0 / 14%);
--ring: oklch(0.6 0.2 19.5);
--chart-1: oklch(0.519 0.203 19.5);
--chart-2: oklch(0.73 0.158 19.4);
--chart-3: oklch(0.52 0.17 2);
--chart-4: oklch(0.657 0.197 24.4);
--chart-5: oklch(0.7 0.1 200);
--sidebar: oklch(0.17 0 0);
--sidebar-foreground: oklch(0.96 0 0);
--sidebar-primary: oklch(0.519 0.203 19.5);
--sidebar-primary-foreground: oklch(1 0 0);
--sidebar-accent: oklch(0.255 0 0);
--sidebar-accent-foreground: oklch(0.96 0 0);
--sidebar-border: oklch(1 0 0 / 10%);
--sidebar-ring: oklch(0.6 0.2 19.5);
}
/* Tailwind v4 dropped the pointer cursor on buttons; Stardust restores it for every enabled button.
Radix switches, checkboxes, tabs and select triggers render as <button>, so they get it too. */
@layer base {
button:not(:disabled), [role="button"]:not([aria-disabled="true"]) {
cursor: pointer;
}
}