Skip to main content

Theming

Customize the Stacks Design System to match your brand while maintaining consistency and accessibility.

Theme Structure

The design system uses CSS custom properties (variables) for theming. This allows you to customize colors, spacing, and other values without modifying component code.

Core Variables

Override these variables in your CSS to customize the theme:

:root {
/* Base radius for all rounded elements */
--radius: 0.5rem;

/* Brand colors */
--primary: oklch(0.7 0.18 45);
--primary-foreground: oklch(1 0 0);

/* Backgrounds */
--background: oklch(0.99 0 0);
--foreground: oklch(0.15 0 0);

/* Component backgrounds */
--card: oklch(1 0 0);
--card-foreground: oklch(0.15 0 0);

/* Muted elements */
--muted: oklch(0.96 0.005 45);
--muted-foreground: oklch(0.5 0 0);

/* Borders and inputs */
--border: oklch(0.9 0.01 45);
--input: oklch(0.92 0.01 45);
--ring: oklch(0.7 0.18 45);

/* Semantic colors */
--destructive: oklch(0.6 0.22 25);
}

Dark Mode

The design system includes built-in dark mode support. Dark mode is activated by adding the dark class to the HTML element.

.dark {
--background: oklch(0.13 0.01 45);
--foreground: oklch(0.95 0 0);
--primary: oklch(0.72 0.19 45);
--card: oklch(0.18 0.015 45);
--muted: oklch(0.22 0.015 45);
--border: oklch(0.28 0.02 45);
/* ... other dark mode values */
}

Implementing Theme Toggle

function useTheme() {
const [isDark, setIsDark] = useState(false)

useEffect(() => {
const prefersDark = window.matchMedia(
'(prefers-color-scheme: dark)'
).matches
setIsDark(prefersDark)
document.documentElement.classList.toggle('dark', prefersDark)
}, [])

const toggle = () => {
setIsDark(!isDark)
document.documentElement.classList.toggle('dark')
}

return { isDark, toggle }
}

Custom Brand Colors

To use your own brand color instead of the Stacks orange, update the primary color variable. Use the OKLCH color space for perceptually uniform colors.

Accessibility

When customizing colors, always verify contrast ratios meet WCAG AA standards (4.5:1 for normal text, 3:1 for large text).

Example: Blue Theme

:root {
--primary: oklch(0.55 0.2 250);
--primary-foreground: oklch(1 0 0);
--ring: oklch(0.55 0.2 250);
--accent: oklch(0.95 0.03 250);
--accent-foreground: oklch(0.4 0.2 250);
}

.dark {
--primary: oklch(0.65 0.2 250);
--accent: oklch(0.25 0.05 250);
}

Border Radius

Control the overall roundness of your UI by adjusting the base radius:

ValueStyleDescription
--radius: 0SharpSquare corners
--radius: 0.5remDefaultSlightly rounded
--radius: 1remRoundedMore rounded corners

Typography

The default font stack uses Space Grotesk for headings and body text. Override via CSS or the Next.js font configuration:

import { Inter } from 'next/font/google'

const inter = Inter({
subsets: ['latin'],
variable: '--font-geist-sans',
})

// Apply to layout
<html className={inter.variable}>
...
</html>

Component-Level Customization

Components accept className props for one-off customizations:

<Button className="bg-gradient-to-r from-primary to-orange-400">
Gradient Button
</Button>

<Card className="border-primary/50 shadow-lg shadow-primary/10">
Custom card styling
</Card>

Best Practices

  • Always test both light and dark modes
  • Verify contrast ratios when changing colors
  • Use semantic color names (primary, destructive) over raw values
  • Keep customizations consistent across your application
  • Document any theme overrides for your team

For a complete list of design tokens, see the Design Tokens documentation.