Colors
Color palette and theming system for HeroUI v3
HeroUI uses CSS variables for colors that automatically switch between light and dark themes. All colors use the oklch
color space for better color transitions.
How It Works
HeroUI's color 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-*
) and maps them to semantic variables.
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>
We follow 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
)
// This gives you the right background and text colors
<div className="bg-accent text-accent-foreground">Hello</div>
Available Colors
Base Colors
These four colors stay the same in all themes:
Background & Surface
Primary Colors
- Accent: Your main brand color (used for primary actions)
- Accent Soft: A lighter version for secondary actions
Status Colors
For alerts, validation, and status messages:
Other Colors
How to Use Colors
In Your Components
// Use Tailwind classes
<div className="bg-background text-foreground">
<button className="bg-accent text-accent-foreground hover:bg-accent-hover">
Click me
</button>
</div>
In CSS Files
/* Direct CSS variables */
.my-component {
background: var(--accent);
color: var(--accent-foreground);
border: 1px solid var(--border);
}
/* With @apply and @layer */
@layer components {
.button {
@apply bg-accent text-accent-foreground;
&:hover,
&[data-hover="true"] {
@apply bg-accent-hover;
}
&:active,
&[data-pressed="true"] {
@apply bg-accent-hover;
transform: scale(0.97);
}
}
}
Default Theme
The complete theme definition can be found in (default.css). This theme automatically switches between light and dark modes based on the class="dark"
or data-theme="dark"
attributes.
@layer base {
/* HeroUI Default Theme */
:root {
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.2221 0 0);
/* Spacing scale */
--spacing: 0.25rem;
/* Border */
--border-width: 1px;
--disabled-opacity: 0.5;
/* Panel specific radius */
--radius-panel: 0.5rem;
--radius-panel-inner: calc(var(--radius-panel) * 0.5);
/* Typography */
--font-size-base: 1rem;
--font-size-scale-desktop: 0.875;
--font-weight-medium: 500;
--font-weight-semibold: 600;
/* Cursor */
--cursor-interactive: pointer;
--cursor-disabled: not-allowed;
/* Base Colors */
--background: var(--white);
--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: var(--color-neutral-100);
--surface-3: var(--color-neutral-200);
--accent: var(--color-neutral-950);
--accent-foreground: var(--snow);
--accent-soft: var(--color-neutral-200);
--accent-soft-foreground: var(--color-neutral-900);
/* Status Colors */
--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);
/* Misc Colors */
--border: oklch(0 0 0 / 15%);
--focus: oklch(0% 0 0 / 20%);
--link: var(--foreground);
/* Shadow */
--shadow-border:
0 0px 5px 0px rgba(0, 0, 0, 0.02), 0 2px 10px 0px rgba(0, 0, 0, 0.06),
0 0px 1px 0px rgba(0, 0, 0, 0.3);
}
.dark,
[data-theme="dark"] {
color-scheme: dark;
/* Base Colors */
--background: var(--black);
--foreground: var(--snow);
--panel: var(--eclipse);
--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: var(--color-neutral-900);
--surface-3: var(--color-neutral-800);
--accent: var(--color-neutral-50);
--accent-foreground: var(--eclipse);
--accent-soft: var(--color-neutral-800);
--accent-soft-foreground: var(--color-neutral-200);
/* Status Colors */
--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);
/* Misc Colors */
--border: oklch(1 0 0 / 12%);
--focus: oklch(100% 0 0 / 50%);
--link: var(--foreground);
/* Shadow */
--shadow-border:
0 0 0 1px var(--border), 0 5px 10px -3px rgba(0, 0, 0, 0.3), 0 3px 4px -2px rgba(0, 0, 0, 0.1);
}
}
Note: Colors like
--color-neutral-*
,--color-white
, and--color-black
come from Tailwind CSS v4's built-in theme.
Customizing Colors
Add Your Own Colors
: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 you can use it:
<div className="bg-info text-info-foreground">Info message</div>
Note: To learn more about theme variables and how they work in Tailwind CSS v4, see the Tailwind CSS Theme documentation.
Override Existing Colors
:root {
/* Override default colors */
--accent: oklch(0.7 0.15 250);
--success: oklch(0.65 0.15 155);
}
[data-theme="dark"] {
/* Override dark theme colors */
--accent: oklch(0.8 0.12 250);
--success: oklch(0.75 0.12 155);
}
Tip: Convert colors at oklch.com
Quick Tips
- Always use color variables, not hard-coded values
- Use foreground/background pairs for good contrast
- Test in both light and dark modes
- The system respects user's theme preference automatically
Related
- Theming - Learn about the theming system
- Styling - Styling components with CSS
- Design Principles - Understanding HeroUI's design philosophy