Theming
Dark mode, CSS variables, and styling with Tailwind CSS v4.
Overview
ZeroStarter uses Tailwind CSS v4 with CSS custom properties for theming, next-themes for dark/light mode switching, and the Fumadocs UI theme for documentation pages.
Dark Mode
Dark mode is handled by next-themes with system preference detection:
// web/next/src/app/providers.tsx
<ThemeProvider attribute="class" defaultTheme="system" enableSystem disableTransitionOnChange>
{children}
</ThemeProvider>The ModeToggle component at web/next/src/components/mode-toggle.tsx is a smart toggle: from system it switches to the opposite of the OS preference, it flips between light and dark while they diverge from the OS preference, and it returns to system once the explicit theme matches the OS preference again.
CSS Variables
Theme colors are defined in web/next/src/app/globals.css using OKLch color space:
:root {
--background: oklch(1 0 0);
--foreground: oklch(0.145 0 0);
--primary: oklch(0.205 0 0);
--muted: oklch(0.97 0 0);
--border: oklch(0.922 0 0);
/* ... */
}
.dark {
--background: oklch(0.145 0 0);
--foreground: oklch(0.985 0 0);
--primary: oklch(0.922 0 0);
/* ... */
}These variables are consumed by Tailwind utility classes like bg-background, text-foreground, border-border, etc.
Tailwind Configuration
Tailwind v4 is configured via PostCSS in web/next/postcss.config.mjs:
export default { plugins: { "@tailwindcss/postcss": {} } }web/next/src/app/globals.css imports Tailwind, animations, and the Fumadocs UI and shadcn presets:
@import "tailwindcss";
@import "tw-animate-css";
@import "fumadocs-ui/css/neutral.css";
@import "fumadocs-ui/css/preset.css";
@import "shadcn/tailwind.css";It then wires the design tokens to Tailwind v4 via an @theme inline block that maps --color-* utilities to the --* custom properties (background, foreground, primary, card, popover, chart, sidebar, radius, and so on).
Fonts
Fonts are loaded with next/font/local from .woff2 files in web/next/src/fonts/, defined in web/next/src/lib/fonts.ts (not via @fontsource). Four variable fonts are included:
- DM Sans: Primary UI font (body text, headings)
- JetBrains Mono: Monospace font (code blocks)
- Caveat: Handwriting accent font
- Newsreader: Serif accent font
Each exposes a CSS variable (e.g. --font-dm-sans) that the @theme inline block maps onto Tailwind's font utilities.
Component Styling
Components use the shadcn/ui base-nova preset with Base UI primitives. The configuration is in web/next/components.json:
{
"style": "base-nova",
"iconLibrary": "remixicon",
"tailwind": {
"baseColor": "neutral",
"cssVariables": true
}
}Utility Functions
import { cn } from "@/lib/utils"
// Merge Tailwind classes with conflict resolution
<div className={cn("text-sm", isActive && "font-bold")} />Customizing the Theme
- Edit CSS variables in
web/next/src/app/globals.cssfor global color changes - Use Tailwind utility classes that reference the CSS variables
- Update
components.jsonand runbun run shadcn:updateto regenerate components with a different preset