LibraryGet StartedTheming
Customization

Theming

Kaeyros UI uses CSS variables for theming. Change a few HSL values in app/globals.css to rebrand the entire library — light mode, dark mode, focus rings, borders, all of it.

Quick start

Add these blocks to your app/globals.css:

css
@tailwind base;
@tailwind components;
@tailwind utilities;

@layer base {
  :root {
    --background: 0 0% 100%;
    --foreground: 240 10% 3.9%;
    --card: 0 0% 100%;
    --card-foreground: 240 10% 3.9%;
    --primary: 240 5.9% 10%;
    --primary-foreground: 0 0% 98%;
    --muted: 240 4.8% 95.9%;
    --muted-foreground: 240 3.8% 46.1%;
    --border: 240 5.9% 90%;
    --ring: 240 5.9% 10%;
    --radius: 0.5rem;
  }

  .dark {
    --background: 240 10% 3.9%;
    --foreground: 0 0% 98%;
    --card: 240 10% 3.9%;
    --card-foreground: 0 0% 98%;
    --primary: 0 0% 98%;
    --primary-foreground: 240 5.9% 10%;
    --muted: 240 3.7% 15.9%;
    --muted-foreground: 240 5% 64.9%;
    --border: 240 3.7% 15.9%;
    --ring: 240 4.9% 83.9%;
  }
}

Note: CSS-variable values use the HSL channels (no hsl() wrapper). Tailwind wraps them automatically via hsl(var(--primary)) in the config.

Color presets

Pick a preset and replace the --primary values in :root:

Slate (default)

--primary: 240 5.9% 10%;
--primary-foreground: 0 0% 98%;

Blue

--primary: 221.2 83.2% 53.3%;
--primary-foreground: 210 40% 98%;

Violet

--primary: 262.1 83.3% 57.8%;
--primary-foreground: 210 20% 98%;

Emerald

--primary: 142.1 76.2% 36.3%;
--primary-foreground: 355.7 100% 97.3%;

Rose

--primary: 346.8 77.2% 49.8%;
--primary-foreground: 355.7 100% 97.3%;

Amber

--primary: 32.1 94.6% 43.7%;
--primary-foreground: 60 9.1% 97.8%;

All CSS variables

VariablePurposeLightDark
--backgroundPage background0 0% 100%240 10% 3.9%
--foregroundDefault text color240 10% 3.9%0 0% 98%
--cardCard / surface background0 0% 100%240 10% 3.9%
--card-foregroundText on card surfaces240 10% 3.9%0 0% 98%
--popoverPopover / dropdown background0 0% 100%240 10% 3.9%
--popover-foregroundText on popovers240 10% 3.9%0 0% 98%
--primaryPrimary brand color240 5.9% 10%0 0% 98%
--primary-foregroundText on primary surfaces0 0% 98%240 5.9% 10%
--secondarySecondary surfaces240 4.8% 95.9%240 3.7% 15.9%
--secondary-foregroundText on secondary240 5.9% 10%0 0% 98%
--mutedMuted backgrounds (skeleton, hover)240 4.8% 95.9%240 3.7% 15.9%
--muted-foregroundMuted text (descriptions, hints)240 3.8% 46.1%240 5% 64.9%
--accentAccent (hover states)240 4.8% 95.9%240 3.7% 15.9%
--accent-foregroundText on accent240 5.9% 10%0 0% 98%
--destructiveDestructive / danger0 72% 51%0 62.8% 30.6%
--destructive-foregroundText on destructive0 0% 98%0 0% 98%
--borderDefault border color240 5.9% 90%240 3.7% 15.9%
--inputInput border color240 5.9% 90%240 3.7% 15.9%
--ringFocus ring color240 5.9% 10%240 4.9% 83.9%
--radiusDefault border radius0.5rem0.5rem

Dark mode

Kaeyros UI uses Tailwind's class dark mode strategy. Toggle the .dark class on <html> to switch.

Recommended: use next-themes for system preference + persisted choice.

tsx
// app/layout.tsx
import { ThemeProvider } from 'next-themes'

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en" suppressHydrationWarning>
      <body>
        <ThemeProvider attribute="class" defaultTheme="system" enableSystem>
          {children}
        </ThemeProvider>
      </body>
    </html>
  )
}
tsx
// Theme toggle button
'use client'
import { useTheme } from 'next-themes'
import { Sun, Moon } from 'lucide-react'

export function ThemeToggle() {
  const { theme, setTheme } = useTheme()
  return (
    <button onClick={() => setTheme(theme === 'dark' ? 'light' : 'dark')}>
      {theme === 'dark' ? <Sun /> : <Moon />}
    </button>
  )
}

Wiring CSS variables to Tailwind

In your tailwind.config.ts:

ts
// tailwind.config.ts
import type { Config } from 'tailwindcss'

const config: Config = {
  darkMode: ['class'],
  content: ['./app/**/*.{ts,tsx}', './components/**/*.{ts,tsx}'],
  theme: {
    extend: {
      colors: {
        border: 'hsl(var(--border))',
        input: 'hsl(var(--input))',
        ring: 'hsl(var(--ring))',
        background: 'hsl(var(--background))',
        foreground: 'hsl(var(--foreground))',
        primary: {
          DEFAULT: 'hsl(var(--primary))',
          foreground: 'hsl(var(--primary-foreground))',
        },
        secondary: {
          DEFAULT: 'hsl(var(--secondary))',
          foreground: 'hsl(var(--secondary-foreground))',
        },
        destructive: {
          DEFAULT: 'hsl(var(--destructive))',
          foreground: 'hsl(var(--destructive-foreground))',
        },
        muted: {
          DEFAULT: 'hsl(var(--muted))',
          foreground: 'hsl(var(--muted-foreground))',
        },
        accent: {
          DEFAULT: 'hsl(var(--accent))',
          foreground: 'hsl(var(--accent-foreground))',
        },
        popover: {
          DEFAULT: 'hsl(var(--popover))',
          foreground: 'hsl(var(--popover-foreground))',
        },
        card: {
          DEFAULT: 'hsl(var(--card))',
          foreground: 'hsl(var(--card-foreground))',
        },
      },
      borderRadius: {
        lg: 'var(--radius)',
        md: 'calc(var(--radius) - 2px)',
        sm: 'calc(var(--radius) - 4px)',
      },
    },
  },
}

export default config

Severity colors

The Kaeyros button (and many other components) uses 8 semantic severity colors mapped to Tailwind palettes — they aren't CSS variables but live directly in the component:

primary

severity

secondary

severity

success

severity

info

severity

warning

severity

help

severity

danger

severity

contrast

severity

To rebrand these, edit the sevColors object inside components/kaeyros/button.tsx.

Ready to ship?