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 
@themedirective for organization 
The system follows a simple 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">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;
    /* 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: 0px;
    --field-border-width: var(--border-width);
    --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%);
    --divider: 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(1 0 0 / 0%);
    --divider: 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:
/* 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-hovered="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/shared/theme.css" layer(theme);
@import "@heroui/styles/themes/default" 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 Variables - Complete theme variables
 - Shared Theme Utilities - Calculated variables and utilities
 
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:
@theme inline {
  --color-background: var(--background);
  --color-foreground: var(--foreground);
  --color-surface: var(--surface);
  --color-surface-foreground: var(--surface-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-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-surface: var(--surface-shadow);
  --shadow-overlay: var(--overlay-shadow);
  --shadow-field: var(--field-shadow);
  /* Form Field Tokens */
  --color-field: var(--field-background, var(--color-default));
  --color-field-hover: color-mix(
    in oklab,
    var(--field-background, var(--color-default)) 90%,
    var(--field-foreground, var(--color-default-foreground)) 10%
  );
  --color-field-foreground: var(--field-foreground, var(--color-foreground));
  --color-field-placeholder: var(--field-placeholder, var(--color-muted));
  --color-field-border: var(--field-border, var(--color-border));
  --radius-field: var(--field-radius, var(--radius-xl));
  --border-width-field: var(--field-border-width, var(--border-width));
  /* Calculated Variables */
  /* --- background shades --- */
  --color-background-secondary: color-mix(
    in oklab,
    var(--color-background) 96%,
    var(--color-foreground) 4%
  );
  --color-background-tertiary: color-mix(
    in oklab,
    var(--color-background) 92%,
    var(--color-foreground) 8%
  );
  --color-background-quaternary: color-mix(
    in oklab,
    var(--color-background) 86%,
    var(--color-foreground) 14%
  );
  --color-background-inverse: var(--color-foreground);
  /* Hover states */
  --color-default-hover: color-mix(in oklab, var(--color-default) 80%, transparent);
  --color-accent-hover: color-mix(
    in oklab,
    var(--color-accent) 90%,
    var(--color-accent-foreground) 10%
  );
  --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%
  );
  /* Form Field Colors */
  --color-field-hover: color-mix(
    in oklab,
    var(--color-field) 90%,
    var(--color-field-foreground) 2%
  );
  --color-field-focus: var(--color-field);
  --color-field-border-hover: color-mix(
    in oklab,
    var(--color-field-border) 88%,
    var(--color-field-foreground) 10%
  );
  --color-field-border-focus: color-mix(
    in oklab,
    var(--color-field-border) 74%,
    var(--color-field-foreground) 22%
  );
  /* Soft Colors */
  --color-accent-soft: color-mix(in oklab, var(--color-accent) 15%, transparent);
  --color-accent-soft-foreground: var(--color-accent);
  --color-accent-soft-hover: color-mix(in oklab, var(--color-accent) 20%, transparent);
  --color-danger-soft: color-mix(in oklab, var(--color-danger) 15%, transparent);
  --color-danger-soft-foreground: var(--color-danger);
  --color-danger-soft-hover: color-mix(in oklab, var(--color-danger) 20%, transparent);
  --color-warning-soft: color-mix(in oklab, var(--color-warning) 15%, transparent);
  --color-warning-soft-foreground: var(--color-warning);
  --color-warning-soft-hover: color-mix(in oklab, var(--color-warning) 20%, transparent);
  --color-success-soft: color-mix(in oklab, var(--color-success) 15%, transparent);
  --color-success-soft-foreground: var(--color-success);
  --color-success-soft-hover: color-mix(in oklab, var(--color-success) 20%, transparent);
  /* Surface Levels */
  --color-surface-secondary: color-mix(in oklab, var(--surface) 94%, var(--surface-foreground) 6%);
  --color-surface-tertiary: color-mix(in oklab, var(--surface) 92%, var(--surface-foreground) 8%);
  --color-surface-quaternary: color-mix(
    in oklab,
    var(--surface) 86%,
    var(--default-foreground) 14%
  );
  /* On Surface Colors */
  --color-on-surface: color-mix(
    in oklab,
    var(--color-surface) 93%,
    var(--color-surface-foreground) 7%
  );
  --color-on-surface-foreground: var(--color-surface-foreground);
  --color-on-surface-hover: color-mix(
    in oklab,
    var(--color-surface) 91%,
    var(--color-surface-foreground) 9%
  );
  --color-on-surface-focus: var(--color-on-surface);
  /* Radius scale */
  --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;
  --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-fluid-out: cubic-bezier(0.32, 0.72, 0, 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);
  --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.