Theming
Customize HeroUI's design system with CSS variables and global styles
Overview
HeroUI uses CSS variables and BEM classes for theming. You can customize everything from colors to component styles using standard CSS.
How It Works
HeroUI's theming system is built on top of Tailwind CSS v4's theme. When you import @heroui/styles
, it:
- Uses Tailwind's built-in color palettes (like
--color-neutral-*
) - Maps them to semantic variables for easier use
- Automatically switches between light and dark themes
- Uses CSS layers and the
@theme
directive for organization
The system follows a simple 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 class="light" data-theme="light">
<body class="bg-background text-foreground">
<!-- Your app -->
</body>
</html>
Switch Themes
<!-- Light theme -->
<html class="light" data-theme="light">
<!-- Dark theme -->
<html class="dark" data-theme="dark">
Override Colors
/* 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 for the complete color palette and visual reference.
Create your own theme
/* src/themes/ocean.css */
@layer base {
/* Ocean Light */
[data-theme="ocean"] {
color-scheme: light;
/* Base */
--background: oklch(0.985 0.015 225);
--foreground: var(--eclipse);
--panel: var(--white);
--panel-foreground: var(--foreground);
--radius: 0.75rem;
--muted: var(--color-neutral-500);
--scrollbar: var(--color-neutral-300);
--default: var(--color-white);
--default-foreground: var(--color-neutral-600);
--surface-1: var(--background);
--surface-2: oklch(0.965 0.020 225);
--surface-3: oklch(0.930 0.030 225);
/* Ocean accent */
--accent: oklch(0.450 0.150 230);
--accent-foreground: var(--snow);
--accent-soft: oklch(0.920 0.040 230);
--accent-soft-foreground: oklch(0.280 0.080 230);
/* Status (kept compatible) */
--success: oklch(0.5503 0.1244 153.56);
--success-foreground: var(--snow);
--warning: oklch(0.7186 0.1521 64.85);
--warning-foreground: var(--eclipse);
--danger: oklch(0.6259 0.1908 29.19);
--danger-foreground: var(--snow);
/* Component Colors */
--segment: var(--white);
--segment-foreground: var(--foreground);
/* Misc */
--border: oklch(0.50 0.060 230 / 22%);
--divider: var(--color-neutral-200);
--focus: oklch(0.60 0.150 230 / 35%);
--link: var(--accent);
--shadow-border:
0 0px 5px 0px rgba(0, 10, 40, 0.03),
0 2px 10px 0px rgba(0, 10, 40, 0.07),
0 0px 1px 0px rgba(0, 0, 0, 0.25);
}
/* Ocean Dark */
[data-theme="ocean-dark"] {
color-scheme: dark;
/* Base */
--background: oklch(0.140 0.020 230);
--foreground: var(--snow);
--panel: oklch(0.180 0.020 230);
--muted: var(--color-neutral-400);
--scrollbar: var(--color-neutral-800);
--default: var(--color-neutral-900);
--default-foreground: var(--color-neutral-200);
--surface-1: var(--background);
--surface-2: oklch(0.220 0.030 230);
--surface-3: oklch(0.280 0.030 230);
/* Ocean accent */
--accent: oklch(0.860 0.080 230);
--accent-foreground: var(--eclipse);
--accent-soft: oklch(0.300 0.050 230);
--accent-soft-foreground: var(--color-neutral-200);
/* Status */
--success: oklch(80% 0.1561 154);
--success-foreground: var(--eclipse);
--warning: oklch(85% 0.1414 77.88);
--warning-foreground: var(--eclipse);
--danger: oklch(0.5798 0.1635 30.83);
--danger-foreground: var(--snow);
/* Component Colors */
--segment: oklch(1 0 0 / 16%);
--segment-foreground: var(--foreground);
/* Misc */
--border: oklch(1 0 0 / 12%);
--divider: var(--color-neutral-800);
--focus: oklch(0.860 0.080 230 / 40%);
--link: var(--accent);
--shadow-border:
0 0 0 1px var(--border),
0 5px 10px -3px rgba(0, 10, 40, 0.35),
0 3px 4px -2px rgba(0, 0, 0, 0.12);
}
}
Use your theme:
/* app/globals.css */
@layer theme, base, components, utilities;
@import "tailwindcss";
@import "@heroui/styles";
@import "./src/themes/ocean.css" layer(theme);
Apply your theme:
<!-- index.html -->
<!-- Light ocean -->
<html data-theme="ocean">
<!-- Dark ocean -->
<html data-theme="ocean-dark">
Customize Components
Global Component Styles
Override any component using BEM classes:
@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 for the complete styling reference.
Find Component Classes
Each component docs page lists all available classes:
- Base classes:
.button
,.accordion
- Modifiers:
.button--primary
,.button--icon-only
- Elements:
.accordion__trigger
,.accordion__panel
- States:
[data-hover="true"]
,[aria-expanded="true"]
Example: Button classes
Import Strategies
Full Import (Recommended)
Get everything with two lines:
@import "tailwindcss";
@import "@heroui/styles";
Selective Import
Import only what you need:
/* Define layers */
@layer theme, base, components, utilities;
/* Base requirements */
@import "tailwindcss";
@import "@heroui/styles/base/base.css" layer(base);
@import "@heroui/styles/themes/theme.css" layer(theme);
@import "@heroui/styles/themes/default.css" layer(theme);
/* Components (all components) */
@import "@heroui/styles/components/index.css" layer(components);
/* OR specific components */
@import "@heroui/styles/components/button.css" layer(components);
@import "@heroui/styles/components/accordion.css" layer(components);
Headless Mode
Build your own styles from scratch:
@import "tailwindcss";
@import "@heroui/styles/base/base.css";
/* Your custom styles */
.button {
/* Your button styles */
}
Adding Custom Colors
You can add your own semantic colors to the theme:
/* 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:
<div className="bg-info text-info-foreground">Info message</div>
Variables Reference
HeroUI defines three types of variables:
- Base Variables: Non-changing values like
--white
,--black
, spacing, and typography - Theme Variables: Colors that change between light/dark themes
- Calculated Variables: Automatically generated hover states and size variants
For a complete reference of all variables and their values, see:
- Colors Documentation - Visual color palette and base variables
- Default Theme Source - Complete theme implementation
Calculated Variables (Tailwind)
We use Tailwind's @theme
directive to automatically create calculated variables for hover states and radius variants:
@theme inline {
--color-background: var(--background);
--color-foreground: var(--foreground);
--color-panel: var(--panel);
--color-panel-foreground: var(--panel-foreground);
--color-muted: var(--muted);
--color-surface: var(--surface-1);
--color-surface-foreground: var(--foreground);
--color-accent: var(--accent);
--color-accent-foreground: var(--accent-foreground);
--color-accent-soft: var(--accent-soft);
--color-accent-soft-foreground: var(--accent-soft-foreground);
--color-segment: var(--segment);
--color-segment-foreground: var(--segment-foreground);
--color-border: var(--border);
--color-divider: var(--divider);
--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);
--shadow-border: var(--shadow-border);
/* Calculated Variables */
/* Colors */
--color-panel-hover: color-mix(
in oklab,
var(--color-panel) 90%,
var(--color-panel-foreground) 5%
);
--color-default-hover: color-mix(
in oklab,
var(--color-default) 90%,
var(--color-default-foreground) 5%
);
--color-accent-hover: color-mix(
in oklab,
var(--color-accent) 90%,
var(--color-accent-foreground) 20%
);
--color-accent-soft-hover: color-mix(
in oklab,
var(--color-accent-soft) 10%,
var(--color-accent-soft-foreground) 18%
);
--color-success-hover: color-mix(
in oklab,
var(--color-success) 90%,
var(--color-success-foreground) 10%
);
--color-warning-hover: color-mix(
in oklab,
var(--color-warning) 90%,
var(--color-warning-foreground) 10%
);
--color-danger-hover: color-mix(
in oklab,
var(--color-danger) 90%,
var(--color-danger-foreground) 10%
);
/* Surface Colors */
--color-surface-1: var(--surface-1);
--color-surface-1-foreground: color-mix(
in oklab,
var(--color-surface-1) 80%,
var(--color-foreground) 3.5%
);
--color-surface-2: var(--surface-2);
--color-surface-2-foreground: color-mix(
in oklab,
var(--color-surface-2) 80%,
var(--color-foreground) 3.5%
);
--color-surface-3: var(--surface-3);
--color-surface-3-foreground: color-mix(
in oklab,
var(--color-surface-3) 80%,
var(--color-foreground) 3.5%
);
/* Radius */
--radius-xs: calc(var(--radius) * 0.25);
--radius-sm: calc(var(--radius) * 0.5);
--radius-md: calc(var(--radius) * 0.75);
--radius-lg: calc(var(--radius) * 1);
--radius-xl: calc(var(--radius) * 1.5);
--radius-2xl: calc(var(--radius) * 2);
--radius-3xl: calc(var(--radius) * 3);
--radius-4xl: calc(var(--radius) * 4);
--radius-panel: var(--radius);
--radius-panel-inner: calc(var(--radius) * 0.5);
/* Transition Timing Functions */
--ease-smooth: ease; /* same as transition: ease; */
/* From smoother to faster quad->circ @made by https://twitter.com/bdc */
--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);
--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);
--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);
/* Animations */
--animate-spin-fast: spin 0.75s linear infinite;
--animate-skeleton: skeleton 2s linear infinite;
}