Theming
Customize HeroUI's design system with CSS variables and global styles
HeroUI uses CSS variables and BEM classes for theming. Customize everything from colors to component styles using standard CSS.
Want to create your own theme? Try the Theme Builder 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'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
-foregroundare 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">Switch themes programmatically with next-themes (For Next.js):
First, wrap your app with ThemeProvider:
// app/providers.tsx
"use client";
import { ThemeProvider } from "next-themes";
export function Providers({ children }: { children: React.ReactNode }) {
return (
<ThemeProvider attribute="class" defaultTheme="light">
{children}
</ThemeProvider>
);
}// app/layout.tsx
import { Providers } from "./providers";
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en" suppressHydrationWarning>
<body className="bg-background text-foreground">
<Providers>{children}</Providers>
</body>
</html>
);
}Then use useTheme to toggle between themes:
"use client";
import { useTheme } from "next-themes";
export function ThemeSwitch() {
const { theme, setTheme } = useTheme();
return (
<button onClick={() => setTheme(theme === "dark" ? "light" : "dark")}>
Toggle {theme === "dark" ? "Light" : "Dark"} Mode
</button>
);
}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;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, modifiers, elements, states). 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" 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 theirindex.cssfile. Use explicit file paths (e.g.,@heroui/styles/components/button.css) to import individual component styles.
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
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 in variables.css:
- Base Variables — Non-changing values like
--white,--black, spacing, and typography - Theme Variables — Colors that change between light/dark themes, plus scrollbar tokens (
--scrollbar-thumb,--scrollbar-width, etc.) - Calculated Variables — Hover states, soft variants, and border/separator levels (the Calculated Colors block in each light/dark theme, using
color-mix())
For a complete reference, see: Colors Documentation, Default Theme Variables, Shared Theme Utilities
Tailwind theme bridge (@theme inline):
themes/shared/theme.css maps semantic variables to Tailwind tokens (--color-*, --radius-*, --ease-*). Calculated colors reference underlying vars (e.g. --surface-hover, --accent-soft) from variables.css — they are not inlined with color-mix() in this file:
@theme inline {
--color-background: var(--background);
--color-foreground: var(--foreground);
--color-surface: var(--surface);Form controls rely on --field-* theme variables. Hover, focus, and border variants are defined under Calculated Colors in variables.css and mapped to Tailwind in theme.css (e.g. --color-field-hover: var(--field-hover)). Override --field-background, --field-hover, and related tokens in your theme to restyle inputs, checkboxes, radios, and OTP slots without affecting surfaces like buttons or cards.
Scrollbars
HeroUI applies a shared scrollbar style to component scroll areas (tables, popovers, drawers, and similar). Scrollbars use standard CSS properties (scrollbar-width, scrollbar-color, scrollbar-gutter) — no ::-webkit-scrollbar overrides.
Modes — set data-scrollbar on <html>, a component root, or a scroll slot:
| Mode | data-scrollbar | Behavior |
|---|---|---|
| HeroUI thin | (unset) or thin | Thin thumb from theme tokens |
| OS / browser | default | Native scrollbars (auto) |
| Hidden | none | No visible scrollbar (scrollbar-width: none) |
<!-- Native scrollbars everywhere -->
<html data-scrollbar="default">
<!-- Restore HeroUI scrollbars in one subtree -->
<main data-scrollbar="thin">
...
</main>
<!-- Hide scrollbars in one subtree (e.g. a popover) -->
<div data-scrollbar="none">
...
</div>Theme variables — defined in light and dark theme blocks in variables.css:
| Variable | Description |
|---|---|
--scrollbar-thumb | Thumb color (default: 15% --foreground via color-mix) |
--scrollbar-track | Track color (default: transparent) |
--scrollbar-gutter | Gutter (default: auto) |
--scrollbar-width | scrollbar-width (default: thin) |
--scrollbar-color | scrollbar-color (default: thumb + track) |
--scrollbar | Legacy alias of --scrollbar-thumb |
Customize globally:
/* app/globals.css */
:root {
--scrollbar-thumb: color-mix(in oklch, var(--accent) 30%, transparent);
--scrollbar-gutter: auto;
}Per scroll slot — pass data-scrollbar on a component or override tokens on a wrapper:
<Table data-scrollbar="default">
...
</Table>
<Drawer.Content data-scrollbar="thin" />
<Calendar.YearPickerGrid data-scrollbar="none" />Custom overflow areas — use the scrollbar, scrollbar-thin, scrollbar-default, or scrollbar-none utilities from @heroui/styles on your own elements. See Styling for class-based overrides.
Note: Some components hide scrollbars by default (date picker popovers, color picker, secondary tabs). Nested scroll slots (e.g. the calendar year picker inside a date picker) keep HeroUI scrollbars because
scrollbar-noneonly affects the element it is applied to, not descendants using@apply scrollbar.
Vibrant Palette
By default, HeroUI uses accessible soft foreground colors that mix the semantic color with the foreground for better contrast. If you prefer more saturated, vibrant soft foreground colors, add the data-vibrant-palette attribute to your root element:
<html data-vibrant-palette="true">This switches all *-soft-foreground variables (accent, success, warning, danger) to use 92% of the semantic color with only 8% foreground mixed in — closer to the raw color but with a slight contrast boost.
| Mode | Accessible (default) | Vibrant |
|---|---|---|
| Soft foreground | color-mix(color 70-80%, foreground 30-40%) | color-mix(color 92%, foreground 8%) |
The vibrant palette prioritizes visual saturation over contrast. It may not meet WCAG accessibility guidelines for some color combinations, especially with lighter accent colors.
You can also enable vibrant palette in the Theme Builder via the "Vibrant palette" toggle in the theme popover.