# Theming **Category**: react **URL**: https://www.heroui.com/docs/react/getting-started/theming **Source**: https://raw.githubusercontent.com/heroui-inc/heroui/refs/heads/v3/apps/docs/content/docs/react/getting-started/(handbook)/theming.mdx > Customize HeroUI's design system with CSS variables and global styles *** HeroUI uses CSS variables and [BEM](https://getbem.com/) classes for theming. Customize everything from colors to component styles using standard CSS. **Want to create your own theme?** Try the [Theme Builder](/themes) to visually customize colors, radius, fonts, and more — then export the CSS to use in your project. ## How It Works HeroUI's theming system is built on top of [Tailwind CSS v4](https://tailwindcss.com/docs/theme)'s theme. When you import `@heroui/styles`, it uses Tailwind's built-in color palettes, maps them to semantic variables, automatically switches between light and dark themes, and uses CSS layers and the `@theme` directive for organization. **Naming pattern:** - Colors without a suffix are backgrounds (e.g., `--accent`) - Colors with `-foreground` are for text on that background (e.g., `--accent-foreground`) ## Quick Start **Apply a theme:** Add a theme class to your HTML and apply colors to the body: ```html ``` **Switch themes:** ```html ``` **Switch themes programmatically with [next-themes](https://github.com/pacocoursey/next-themes) (For Next.js):** First, wrap your app with `ThemeProvider`: ```tsx // app/providers.tsx "use client"; import { ThemeProvider } from "next-themes"; export function Providers({ children }: { children: React.ReactNode }) { return ( {children} ); } ``` ```tsx // app/layout.tsx import { Providers } from "./providers"; export default function RootLayout({ children }: { children: React.ReactNode }) { return ( {children} ); } ``` Then use `useTheme` to toggle between themes: ```tsx "use client"; import { useTheme } from "next-themes"; export function ThemeSwitch() { const { theme, setTheme } = useTheme(); return ( ); } ``` **Override colors:** ```css /* app/globals.css */ @import "tailwindcss"; @import "@heroui/styles"; :root { /* Override any color variable */ --accent: oklch(0.7 0.25 260); --success: oklch(0.65 0.15 155); } ``` > **Note**: See [Colors](/docs/handbook/colors) for the complete color palette and visual reference. **Create your own theme:** ```css /* src/themes/ocean.css */ @layer base { /* Ocean Light */ [data-theme="ocean"] { color-scheme: light; /* Primitive Colors (Do not change between light and dark) */ --white: oklch(100% 0 0); --black: oklch(0% 0 0); --snow: oklch(0.9911 0 0); --eclipse: oklch(0.2103 0.0059 285.89); /* Spacing & Layout */ --spacing: 0.25rem; --border-width: 1px; --field-border-width: 0px; --disabled-opacity: 0.5; --ring-offset-width: 2px; --cursor-interactive: pointer; --cursor-disabled: not-allowed; /* Radius */ --radius: 0.75rem; --field-radius: calc(var(--radius) * 1.5); /* Base Colors */ --background: oklch(0.985 0.015 225); --foreground: var(--eclipse); /* Surface: Used for non-overlay components */ --surface: var(--white); --surface-foreground: var(--foreground); /* Overlay: Used for floating/overlay components */ --overlay: var(--white); --overlay-foreground: var(--foreground); --muted: oklch(0.5517 0.0138 285.94); --scrollbar: oklch(87.1% 0.006 286.286); --default: oklch(94% 0.001 286.375); --default-foreground: var(--eclipse); /* Ocean accent */ --accent: oklch(0.450 0.150 230); --accent-foreground: var(--snow); /* Form Field Defaults */ --field-background: var(--white); --field-foreground: oklch(0.2103 0.0059 285.89); --field-placeholder: var(--muted); --field-border: transparent; /* Status (kept compatible) */ --success: oklch(0.7329 0.1935 150.81); --success-foreground: var(--eclipse); --warning: oklch(0.7819 0.1585 72.33); --warning-foreground: var(--eclipse); --danger: oklch(0.6532 0.2328 25.74); --danger-foreground: var(--snow); /* Component Colors */ --segment: var(--white); --segment-foreground: var(--foreground); /* Misc */ --border: oklch(0.50 0.060 230 / 22%); --separator: oklch(92% 0.004 286.32); --focus: var(--accent); --link: var(--accent); /* Shadows */ --surface-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.04), 0 1px 2px 0 rgba(0, 0, 0, 0.06), 0 0 1px 0 rgba(0, 0, 0, 0.06); --overlay-shadow: 0 4px 16px 0 rgba(24, 24, 27, 0.08), 0 8px 24px 0 rgba(24, 24, 27, 0.09); --field-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.04), 0 1px 2px 0 rgba(0, 0, 0, 0.06), 0 0 1px 0 rgba(0, 0, 0, 0.06); /* Skeleton Default Global Animation */ --skeleton-animation: shimmer; /* Possible values: shimmer, pulse, none */ } /* Ocean Dark */ [data-theme="ocean-dark"] { color-scheme: dark; /* Base Colors */ --background: oklch(0.140 0.020 230); --foreground: var(--snow); /* Surface: Used for non-overlay components */ --surface: oklch(0.2103 0.0059 285.89); --surface-foreground: var(--foreground); /* Overlay: Used for floating/overlay components */ --overlay: oklch(0.22 0.0059 285.89); --overlay-foreground: var(--foreground); --muted: oklch(70.5% 0.015 286.067); --scrollbar: oklch(70.5% 0.015 286.067); --default: oklch(27.4% 0.006 286.033); --default-foreground: var(--snow); /* Form Field Defaults */ --field-background: var(--default); --field-foreground: var(--foreground); /* Ocean accent */ --accent: oklch(0.860 0.080 230); --accent-foreground: var(--eclipse); /* Status */ --success: oklch(0.7329 0.1935 150.81); --success-foreground: var(--eclipse); --warning: oklch(0.8203 0.1388 76.34); --warning-foreground: var(--eclipse); --danger: oklch(0.594 0.1967 24.63); --danger-foreground: var(--snow); /* Component Colors */ --segment: oklch(0.3964 0.01 285.93); --segment-foreground: var(--foreground); /* Misc */ --border: oklch(22% 0.006 286.033); --separator: oklch(22% 0.006 286.033); --focus: var(--accent); --link: var(--accent); /* Shadows */ --surface-shadow: 0 0 0 0 transparent inset; --overlay-shadow: 0 0 0 0 transparent inset; --field-shadow: 0 0 0 0 transparent inset; } } ``` Use your theme: ```css /* app/globals.css */ @layer theme, base, components, utilities; @import "tailwindcss"; @import "@heroui/styles"; @import "./src/themes/ocean.css" layer(theme); /* [!code highlight]*/ ``` Apply your theme: ```html ``` ## Customize Components **Global component styles:** Override any component using BEM classes: ```css @layer components { /* Customize buttons */ .button { @apply font-semibold tracking-wide; } .button--primary { @apply bg-blue-600 hover:bg-blue-700; } /* Customize accordions */ .accordion__trigger { @apply text-lg font-bold; } } ``` > **Note**: See [Styling](/docs/handbook/styling) for the complete styling reference. **Find component classes:** Each component docs page lists all available classes (base classes, modifiers, elements, states). Example: [Button classes](/docs/components/button#css-classes) ## Import Strategies **Full import (recommended):** Get everything with two lines: ```css @import "tailwindcss"; @import "@heroui/styles"; ``` **Selective import:** Import only what you need: ```css /* Define layers */ @layer theme, base, components, utilities; /* Base requirements */ @import "tailwindcss"; @import "@heroui/styles/base" layer(base); /* OR specific base file */ @import "@heroui/styles/base/base.css" layer(base); /* Theme variables */ @import "@heroui/styles/themes/shared/theme.css" layer(theme); @import "@heroui/styles/themes/default" layer(theme); /* OR specific theme files */ @import "@heroui/styles/themes/default/index.css" layer(theme); @import "@heroui/styles/themes/default/variables.css" layer(theme); /* Components (all components) */ @import "@heroui/styles/components" layer(components); /* OR specific component files */ @import "@heroui/styles/components/index.css" layer(components); @import "@heroui/styles/components/button.css" layer(components); @import "@heroui/styles/components/accordion.css" layer(components); /* Utilities (optional) */ @import "@heroui/styles/utilities" layer(utilities); /* Variants (optional) */ @import "@heroui/styles/variants" layer(utilities); ``` > **Note**: Directory imports (e.g., `@heroui/styles/components`) automatically resolve to their `index.css` file. Use explicit file paths (e.g., `@heroui/styles/components/button.css`) to import individual component styles. **Headless mode:** Build your own styles from scratch: ```css @import "tailwindcss"; @import "@heroui/styles/base/base.css"; /* Your custom styles */ .button { /* Your button styles */ } ``` ## Adding Custom Colors Add your own semantic colors to the theme: ```css /* Define in both light and dark themes */ :root, [data-theme="light"] { --info: oklch(0.6 0.15 210); --info-foreground: oklch(0.98 0 0); } .dark, [data-theme="dark"] { --info: oklch(0.7 0.12 210); --info-foreground: oklch(0.15 0 0); } /* Make the color available to Tailwind */ @theme inline { --color-info: var(--info); --color-info-foreground: var(--info-foreground); } ``` Now use it in your components: ```tsx
Info message
``` ## Variables Reference HeroUI defines three types of variables: 1. **Base Variables** — Non-changing values like `--white`, `--black`, spacing, and typography 2. **Theme Variables** — Colors that change between light/dark themes 3. **Calculated Variables** — Automatically generated hover states and size variants For a complete reference, see: [Colors Documentation](/docs/handbook/colors), [Default Theme Variables](https://github.com/heroui-inc/heroui/blob/v3/packages/styles/themes/default/variables.css), [Shared Theme Utilities](https://github.com/heroui-inc/heroui/blob/v3/packages/styles/themes/shared/theme.css) **Calculated variables (Tailwind):** We use Tailwind's `@theme` directive to automatically create calculated variables for hover states and radius variants. These are defined in `themes/shared/theme.css`: ```css @theme inline { --color-background: var(--background); --color-foreground: var(--foreground); --color-surface: var(--surface); --color-surface-foreground: var(--surface-foreground); --color-surface-hover: color-mix(in oklab, var(--surface) 92%, var(--surface-foreground) 8%); --color-surface-secondary: var(--surface-secondary); --color-surface-secondary-foreground: var(--surface-secondary-foreground); --color-surface-tertiary: var(--surface-tertiary); --color-surface-tertiary-foreground: var(--surface-tertiary-foreground); --color-overlay: var(--overlay); --color-overlay-foreground: var(--overlay-foreground); --color-muted: var(--muted); --color-accent: var(--accent); --color-accent-foreground: var(--accent-foreground); --color-segment: var(--segment); --color-segment-foreground: var(--segment-foreground); --color-border: var(--border); --color-separator: var(--separator); --color-focus: var(--focus); --color-link: var(--link); --color-default: var(--default); --color-default-foreground: var(--default-foreground); --color-success: var(--success); --color-success-foreground: var(--success-foreground); --color-warning: var(--warning); --color-warning-foreground: var(--warning-foreground); --color-danger: var(--danger); --color-danger-foreground: var(--danger-foreground); --color-backdrop: var(--backdrop); --shadow-surface: var(--surface-shadow); --shadow-overlay: var(--overlay-shadow); --shadow-field: var(--field-shadow); /* Form Field Tokens */ --color-field: var(--field-background, var(--default)); --color-field-foreground: var(--field-foreground, var(--foreground)); --color-field-placeholder: var(--field-placeholder, var(--muted)); --color-field-border: var(--field-border, var(--border)); --radius-field: var(--field-radius, calc(var(--radius) * 1.5)); --border-width-field: var(--field-border-width, var(--border-width)); /* Calculated Variables */ /* --- background shades --- */ --color-background-secondary: color-mix(in oklab, var(--background) 96%, var(--foreground) 4%); --color-background-tertiary: color-mix(in oklab, var(--background) 92%, var(--foreground) 8%); --color-background-inverse: var(--foreground); /* ------------------------- */ --color-default-hover: color-mix(in oklab, var(--default) 96%, var(--default-foreground) 4%); --color-accent-hover: color-mix(in oklab, var(--accent) 90%, var(--accent-foreground) 10%); --color-success-hover: color-mix(in oklab, var(--success) 90%, var(--success-foreground) 10%); --color-warning-hover: color-mix(in oklab, var(--warning) 90%, var(--warning-foreground) 10%); --color-danger-hover: color-mix(in oklab, var(--danger) 90%, var(--danger-foreground) 10%); /* Form Field Colors */ --color-field-hover: color-mix( in oklab, var(--field-background, var(--default)) 90%, var(--field-foreground, var(--foreground)) 2% ); --color-field-focus: var(--field-background, var(--default)); --color-field-border-hover: color-mix( in oklab, var(--field-border, var(--border)) 88%, var(--field-foreground, var(--foreground)) 10% ); --color-field-border-focus: color-mix( in oklab, var(--field-border, var(--border)) 74%, var(--field-foreground, var(--foreground)) 22% ); /* Soft Colors */ --color-accent-soft: color-mix(in oklab, var(--accent) 15%, transparent); --color-accent-soft-foreground: var(--accent); --color-accent-soft-hover: color-mix(in oklab, var(--accent) 20%, transparent); --color-danger-soft: color-mix(in oklab, var(--danger) 15%, transparent); --color-danger-soft-foreground: var(--danger); --color-danger-soft-hover: color-mix(in oklab, var(--danger) 20%, transparent); --color-warning-soft: color-mix(in oklab, var(--warning) 15%, transparent); --color-warning-soft-foreground: var(--warning); --color-warning-soft-hover: color-mix(in oklab, var(--warning) 20%, transparent); --color-success-soft: color-mix(in oklab, var(--success) 15%, transparent); --color-success-soft-foreground: var(--success); --color-success-soft-hover: color-mix(in oklab, var(--success) 20%, transparent); /* Separator Colors - Levels */ --color-separator-secondary: color-mix( in oklab, var(--surface) 85%, var(--surface-foreground) 15% ); --color-separator-tertiary: color-mix( in oklab, var(--surface) 81%, var(--surface-foreground) 19% ); /* Border Colors - Levels (progressive contrast: default → secondary → tertiary) */ /* Light mode: lighter → darker | Dark mode: darker → lighter */ --color-border-secondary: color-mix(in oklab, var(--surface) 78%, var(--surface-foreground) 22%); --color-border-tertiary: color-mix(in oklab, var(--surface) 66%, var(--surface-foreground) 34%); /* Radius and default sizes - defaults can change by just changing the --radius */ --radius-xs: calc(var(--radius) * 0.25); /* 0.125rem (2px) */ --radius-sm: calc(var(--radius) * 0.5); /* 0.25rem (4px) */ --radius-md: calc(var(--radius) * 0.75); /* 0.375rem (6px) */ --radius-lg: calc(var(--radius) * 1); /* 0.5rem (8px) */ --radius-xl: calc(var(--radius) * 1.5); /* 0.75rem (12px) */ --radius-2xl: calc(var(--radius) * 2); /* 1rem (16px) */ --radius-3xl: calc(var(--radius) * 3); /* 1.5rem (24px) */ --radius-4xl: calc(var(--radius) * 4); /* 2rem (32px) */ /* Transition Timing Functions */ --ease-smooth: ease; /* same as transition: ease; */ /* These custom curves are made by https://twitter.com/bdc */ /* From smoother to faster */ --ease-in-quad: cubic-bezier(0.55, 0.085, 0.68, 0.53); --ease-in-cubic: cubic-bezier(0.55, 0.055, 0.675, 0.19); --ease-in-quart: cubic-bezier(0.895, 0.03, 0.685, 0.22); --ease-in-quint: cubic-bezier(0.755, 0.05, 0.855, 0.06); --ease-in-expo: cubic-bezier(0.95, 0.05, 0.795, 0.035); --ease-in-circ: cubic-bezier(0.6, 0.04, 0.98, 0.335); /* From slower to faster */ --ease-out-quad: cubic-bezier(0.25, 0.46, 0.45, 0.94); --ease-out-cubic: cubic-bezier(0.215, 0.61, 0.355, 1); --ease-out-quart: cubic-bezier(0.165, 0.84, 0.44, 1); --ease-out-quint: cubic-bezier(0.23, 1, 0.32, 1); --ease-out-expo: cubic-bezier(0.19, 1, 0.22, 1); --ease-out-circ: cubic-bezier(0.075, 0.82, 0.165, 1); /* Custom smooth-out curve: fast start, smooth stop - Apple style */ --ease-out-fluid: cubic-bezier(0.32, 0.72, 0, 1); /* From slower to faster */ --ease-in-out-quad: cubic-bezier(0.455, 0.03, 0.515, 0.955); --ease-in-out-cubic: cubic-bezier(0.645, 0.045, 0.355, 1); --ease-in-out-quart: cubic-bezier(0.77, 0, 0.175, 1); --ease-in-out-quint: cubic-bezier(0.86, 0, 0.07, 1); --ease-in-out-expo: cubic-bezier(1, 0, 0, 1); --ease-in-out-circ: cubic-bezier(0.785, 0.135, 0.15, 0.86); /* Linear */ --ease-linear: linear; /* Animations */ --animate-spin-fast: spin 0.75s linear infinite; --animate-skeleton: skeleton 2s linear infinite; --animate-caret-blink: caret-blink 1.2s ease-out infinite; @keyframes skeleton { 100% { transform: translateX(200%); } } @keyframes caret-blink { 0%, 70%, 100% { opacity: 1; } 20%, 50% { opacity: 0; } } } ``` Form controls now rely on the `--field-*` variables and their calculated hover/focus variants. Update them in your theme to restyle inputs, checkboxes, radios, and OTP slots without impacting surfaces like buttons or cards. ## Resources - [Colors Documentation](/docs/handbook/colors) - [Styling Guide](/docs/handbook/styling) - [Tailwind CSS v4 Theming](https://tailwindcss.com/docs/theme) - [BEM Methodology](https://getbem.com/) - [OKLCH Color Tool](https://oklch.com)