# 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)