# Theming · HeroUI

> Customize HeroUI's design system with CSS variables and global styles

- Page: https://jiguang132.com/systems/heroui/docs/getting-started/theming
- Based on: HeroUI documentation (Apache-2.0) @ 60edd8ed751b1d2a270623fbb182bbcfa8baa9af

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](https://heroui.com/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
<html class="light" data-theme="light">
  <body class="bg-background text-foreground">
    <!-- Your app -->
  </body>
</html>
```

**Switch themes:**
```html
<!-- Light theme -->
<html class="light" data-theme="light">

<!-- Dark theme -->
<html class="dark" data-theme="dark">
```

**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 (
    <ThemeProvider attribute="class" defaultTheme="light">
      {children}
    </ThemeProvider>
  );
}
```

```tsx
// 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:

```tsx
"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:**

```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](/systems/heroui/docs/getting-started/colors) for the complete color palette and visual reference.

> **Dark mode**: For a complete setup guide with `next-themes` and HeroUI's `useTheme` hook, see [Dark Mode](/systems/heroui/docs/getting-started/dark-mode).

**Create your own theme:**


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
<!-- 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:

```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](/systems/heroui/docs/getting-started/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](/systems/heroui/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
<div className="bg-info text-info-foreground">Info message</div>
```

## Variables Reference

HeroUI defines three types of variables in [`variables.css`](https://github.com/heroui-inc/heroui/blob/v3/packages/styles/themes/default/variables.css):

1. **Base Variables** — Non-changing values like `--white`, `--black`, spacing, and typography
2. **Theme Variables** — Colors that change between light/dark themes, plus scrollbar tokens (`--scrollbar-thumb`, `--scrollbar-width`, etc.)
3. **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](/systems/heroui/docs/getting-started/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)

**Tailwind theme bridge (`@theme inline`):**

[`themes/shared/theme.css`](https://github.com/heroui-inc/heroui/blob/v3/packages/styles/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:


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

```html
<!-- 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`](https://github.com/heroui-inc/heroui/blob/v3/packages/styles/themes/default/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:**

```css
/* 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:

```tsx
<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](/systems/heroui/docs/getting-started/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-none` only 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
<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](https://heroui.com/themes) via the "Vibrant palette" toggle in the theme popover.

## Resources

- [Colors Documentation](/systems/heroui/docs/getting-started/colors)
- [Styling Guide](/systems/heroui/docs/getting-started/styling)
- [Tailwind CSS v4 Theming](https://tailwindcss.com/docs/theme)
- [BEM Methodology](https://getbem.com/)
- [OKLCH Color Tool](https://oklch.com)

---

Index of all JGuang resources: https://jiguang132.com/llms.txt
