27.5k

Styling & Theming

Complete guide to styling changes and theming system migration from HeroUI v2 to v3

This guide covers all styling-related changes between HeroUI v2 and v3, including utility classes, component styles, theme system architecture, and visual differences. For component-specific API changes, see individual component migration guides.

Overview

HeroUI v3 introduces significant changes to the styling system:

  • CSS-First Architecture: Replaces Tailwind plugin with pure CSS files
  • Standard Tailwind Utilities: Custom utilities replaced with standard Tailwind classes
  • CSS Variables: New CSS variable naming and structure
  • Component Styles: Updated default sizes, spacing, and visual appearance
  • No Plugin Required: Removed dependency on Tailwind plugin configuration

Quick Reference

Utility Classes Mapping

v2 Utilityv3 EquivalentNotes
text-tinytext-xsFont size: 0.75rem → 0.75rem (same)
text-smalltext-smFont size: 0.875rem → 0.875rem (same)
text-mediumtext-baseFont size: 1rem → 1rem (same)
text-largetext-lgFont size: 1.125rem → 1.125rem (same)
rounded-smallrounded-smBorder radius: 8px → 4px (different)
rounded-mediumrounded-mdBorder radius: 12px → 6px (different)
rounded-largerounded-lgBorder radius: 14px → 8px (different)
border-smallborderBorder width: 1px → 1px (use standard Tailwind)
border-mediumborder-2Border width: 2px → 2px (use standard Tailwind)
border-largeborder-[3px]Border width: 3px → 3px (use arbitrary value)
.transition-backgroundStandard CSS transitionsRemoved utility
.transition-colors-opacityStandard CSS transitionsRemoved utility

Component Size Comparison

ComponentSizev2 Heightv3 Heightv2 Paddingv3 Padding
Buttonsmh-8 (32px)h-9 md:h-8px-3px-3
Buttonmdh-10 (40px)h-10 md:h-9px-4px-4
Buttonlgh-12 (48px)h-11 md:h-10px-6px-6
Inputsmh-8 (32px)-px-2-
Inputmdh-10 (40px)h-9px-3px-3 py-2
Inputlgh-12 (48px)-px-4-
Chipsmh-6 (24px)-px-1px-1 py-0
Chipmdh-7 (28px)-px-2px-2 py-0.5
Chiplg---px-3 py-1

Utility Classes Migration

Text Utilities

HeroUI v2 provided custom text size utilities that mapped to CSS variables. v3 uses standard Tailwind text size classes.

v2 Text Utilities:

// v2 - Custom utilities with CSS variables
<div className="text-tiny">Tiny text</div>
<div className="text-small">Small text</div>
<div className="text-medium">Medium text</div>
<div className="text-large">Large text</div>

v3 Text Utilities:

// v3 - Standard Tailwind classes
<div className="text-xs">Tiny text</div>
<div className="text-sm">Small text</div>
<div className="text-base">Medium text</div>
<div className="text-lg">Large text</div>

Mapping Details:

v2 ClassFont SizeLine Heightv3 ClassFont SizeLine Height
text-tiny0.75rem (12px)1rem (16px)text-xs0.75rem (12px)1rem (16px)
text-small0.875rem (14px)1.25rem (20px)text-sm0.875rem (14px)1.25rem (20px)
text-medium1rem (16px)1.5rem (24px)text-base1rem (16px)1.5rem (24px)
text-large1.125rem (18px)1.75rem (28px)text-lg1.125rem (18px)1.75rem (28px)

Migration Example:

<Button size="sm" className="text-tiny">
  Small Button
</Button>
<p className="text-small text-foreground-400">
  Helper text
</p>
<Button size="sm" className="text-xs">
  Small Button
</Button>
<p className="text-sm text-muted">
  Helper text
</p>

Border Radius Utilities

v2 used custom border radius utilities (rounded-small, rounded-medium, rounded-large) that mapped to CSS variables. v3 uses standard Tailwind border radius classes, but the actual values differ.

v2 Border Radius:

// v2 - Custom utilities
<div className="rounded-small">Small radius</div>
<div className="rounded-medium">Medium radius</div>
<div className="rounded-large">Large radius</div>

v3 Border Radius:

// v3 - Standard Tailwind classes
<div className="rounded-sm">Small radius</div>
<div className="rounded-md">Medium radius</div>
<div className="rounded-lg">Large radius</div>

Value Comparison:

v2 Classv2 Valuev3 Classv3 ValueDifference
rounded-small8px (0.5rem)rounded-sm4px (0.25rem)Smaller
rounded-medium12px (0.75rem)rounded-md6px (0.375rem)Smaller
rounded-large14px (0.875rem)rounded-lg8px (0.5rem)Smaller

Note: v3 uses smaller default border radius values. If you need the exact v2 values, use arbitrary values:

// Match v2 rounded-small (8px)
<div className="rounded-[8px]">Custom radius</div>

// Match v2 rounded-medium (12px)
<div className="rounded-[12px]">Custom radius</div>

// Match v2 rounded-large (14px)
<div className="rounded-[14px]">Custom radius</div>

Migration Example:

<Button radius="sm" color="primary">
  Button
</Button>
<Card radius="md">
  <CardBody>Content</CardBody>
</Card>
<Button className="rounded-sm" variant="primary">
  Button
</Button>
<Card className="rounded-md">
  <Card.Content>Content</Card.Content>
</Card>

Border Width Utilities

v2 provided custom border width utilities (border-small, border-medium, border-large). v3 uses standard Tailwind border width classes.

v2 Border Width:

// v2 - Custom utilities
<div className="border-small border-default">1px border</div>
<div className="border-medium border-primary">2px border</div>
<div className="border-large border-danger">3px border</div>

v3 Border Width:

// v3 - Standard Tailwind classes
<div className="border border-border">1px border</div>
<div className="border-2 border-accent">2px border</div>
<div className="border-[3px] border-danger">3px border</div>

Mapping:

v2 ClassWidthv3 ClassWidth
border-small1pxborder1px
border-medium2pxborder-22px
border-large3pxborder-[3px]3px (arbitrary)

Transition Utilities

v2 provided custom transition utilities for common animation patterns. v3 removes these utilities in favor of standard CSS transitions.

v2 Transition Utilities:

// v2 - Custom transition utilities
<div className="transition-background">
  Background transitions
</div>
<div className="transition-colors-opacity">
  Colors and opacity transitions
</div>
<div className="transition-transform-colors-opacity">
  Transform, colors, and opacity
</div>

v3 Standard Transitions:

// v3 - Standard CSS transitions
<div className="transition-colors duration-150">
  Background transitions
</div>
<div className="transition-[colors,opacity] duration-150">
  Colors and opacity transitions
</div>
<div className="transition-[transform,colors,opacity] duration-150">
  Transform, colors, and opacity
</div>

Removed Utilities:

The following transition utilities are no longer available in v3:

  • .transition-background → Use transition-colors
  • .transition-colors-opacity → Use transition-[colors,opacity]
  • .transition-width → Use transition-[width]
  • .transition-height → Use transition-[height]
  • .transition-size → Use transition-[width,height]
  • .transition-left → Use transition-[left]
  • .transition-transform-opacity → Use transition-[transform,opacity]
  • .transition-transform-background → Use transition-[transform,background-color]
  • .transition-transform-colors → Use transition-[transform,colors]
  • .transition-transform-colors-opacity → Use transition-[transform,colors,opacity]

Migration Example:

<Button className="transition-transform-colors-opacity">
  Animated Button
</Button>
<Button className="transition-[transform,colors,opacity] duration-150">
  Animated Button
</Button>

Other Utilities

Scrollbar Utilities:

v2 provided .scrollbar-hide and .scrollbar-default utilities. These are not included in v3 by default, but you can use Tailwind's scrollbar plugin or custom CSS.

Animation Utilities:

v2 provided spinner animation utilities (.spinner-bar-animation, .spinner-dot-animation, etc.). These are handled internally by v3 components and are not exposed as utilities.

Custom Utilities:

v2 included utilities like:

  • .leading-inherit → Use leading-[inherit]
  • .tap-highlight-transparent → Use [-webkit-tap-highlight-color:transparent]
  • .input-search-cancel-button-none → Use custom CSS if needed

Component Styling Changes

Button

Size Changes:

Sizev2 Heightv3 Heightv2 Paddingv3 Paddingv2 Fontv3 Font
smh-8 (32px)h-9 md:h-8px-3px-3text-tinytext-sm
mdh-10 (40px)h-10 md:h-9px-4px-4text-smalltext-sm
lgh-12 (48px)h-11 md:h-10px-6px-6text-mediumtext-base

Key Changes:

  1. Responsive Heights: v3 buttons have responsive heights (larger on mobile, smaller on desktop)
  2. Font Sizes: v3 uses standard Tailwind text sizes instead of custom utilities
  3. Border Radius: Default radius changed from rounded-medium (12px) to rounded-3xl (24px)
  4. Gap: Consistent gap-2 spacing between icon and text

Migration Example:

<Button 
  size="sm" 
  color="primary" 
  radius="md"
  className="text-tiny"
>
  Small Button
</Button>
<Button 
  size="sm" 
  variant="primary"
  className="rounded-md text-sm"
>
  Small Button
</Button>

Input

Size Changes:

Sizev2 Heightv3 Heightv2 Paddingv3 Padding
smh-8 (32px)Not availablepx-2Not available
mdh-10 (40px)h-9px-3px-3 py-2
lgh-12 (48px)Not availablepx-4Not available

Key Changes:

  1. Fewer Sizes: v3 only provides medium size by default
  2. Border Radius: Uses rounded-field CSS variable (typically rounded-xl or rounded-2xl)
  3. Padding: Vertical padding added (py-2)
  4. Font Size: Uses text-sm instead of text-small

Migration Example:

<Input 
  size="md"
  radius="md"
  placeholder="Enter text"
/>
<Input 
  placeholder="Enter text"
  className="rounded-md"
/>

Card

Spacing Changes:

Elementv2 Paddingv3 Padding
Basep-3 (12px)p-4 (16px) + gap-3
Headerp-3 (12px)Inherits from base
Bodyp-3 (12px)Inherits from base
Footerp-3 (12px)Inherits from base

Key Changes:

  1. Increased Padding: Base padding increased from 12px to 16px
  2. Gap System: Uses gap-3 for spacing between elements
  3. Border Radius: Default changed to rounded-3xl (24px)
  4. Shadow: Uses shadow-surface CSS variable

Migration Example:

<Card radius="md" shadow="md">
  <CardHeader>Header</CardHeader>
  <CardBody>Body</CardBody>
  <CardFooter>Footer</CardFooter>
</Card>
<Card className="rounded-md shadow-md">
  <Card.Header>Header</Card.Header>
  <Card.Content>Body</Card.Content>
  <Card.Footer>Footer</Card.Footer>
</Card>

Chip

Size Changes:

Sizev2 Heightv3 Heightv2 Paddingv3 Paddingv2 Fontv3 Font
smh-6 (24px)Autopx-1px-1 py-0text-tinytext-xs
mdh-7 (28px)Autopx-2px-2 py-0.5text-smalltext-xs
lgNot availableAuto-px-3 py-1-text-sm

Key Changes:

  1. Border Radius: Default changed to rounded-2xl (16px)
  2. Font Sizes: Uses standard Tailwind text sizes
  3. Padding: Added vertical padding for better visual balance
  4. Height: Height is now auto-based on content instead of fixed

Migration Example:

<Chip 
  size="md"
  variant="solid"
  color="primary"
  radius="md"
>
  Chip
</Chip>
<Chip 
  size="md"
  variant="primary"
  className="rounded-md"
>
  Chip
</Chip>

Select / Dropdown

Key Changes:

  1. Trigger Height: Default height is h-9 (36px) instead of h-10 (40px)
  2. Border Radius: Uses rounded-field CSS variable
  3. Content Padding: Popover content uses p-0 with scroll-py-1 for item padding
  4. Font Size: Uses text-sm for items

Migration Example:

<Select 
  size="md"
  radius="md"
  placeholder="Select option"
>
  <SelectItem key="1">Option 1</SelectItem>
</Select>
<Select placeholder="Select option">
  <Select.Item id="1">Option 1</Select.Item>
</Select>

Theme System Architecture

v2: Plugin-Based System

v2 used a Tailwind CSS plugin that:

  1. Generated Utilities: Created custom utility classes via JavaScript
  2. CSS Variables: Injected CSS variables through the plugin
  3. Theme Configuration: Required configuration in tailwind.config.js
  4. Runtime Generation: Utilities generated at build time

v2 Configuration:

// tailwind.config.js
const {heroui} = require("@heroui/react");

module.exports = {
  plugins: [
    heroui({
      layout: {
        fontSize: {
          tiny: "0.75rem",
          small: "0.875rem",
          medium: "1rem",
          large: "1.125rem",
        },
        radius: {
          small: "8px",
          medium: "12px",
          large: "14px",
        },
      },
      themes: {
        light: {
          colors: {
            primary: {
              // color definitions
            },
          },
        },
      },
    }),
  ],
};

v3: CSS-First System

v3 uses a pure CSS approach:

  1. CSS Files: Styles defined in CSS files (packages/styles/)
  2. CSS Variables: Variables defined in CSS, not generated
  3. No Plugin: No Tailwind plugin required
  4. Import-Based: Styles imported via CSS imports

v3 Configuration:

/* globals.css */
@import "tailwindcss";
@import "@heroui/styles";

No Tailwind Config Required:

If you only use HeroUI, you can remove tailwind.config.js entirely. If you have custom Tailwind config, keep it but remove the HeroUI plugin.

Architecture Comparison

Aspectv2v3
Styling MethodTailwind plugin (JavaScript)CSS files
Utility GenerationRuntime via pluginPre-defined CSS
CSS VariablesGenerated by pluginDefined in CSS
Configurationtailwind.config.jsCSS imports
CustomizationPlugin configCSS variable overrides
Build DependencyRequires pluginNo plugin needed

CSS Variables & Design Tokens

Variable Naming Changes

v2 used the pattern --heroui-{property}-{scale} while v3 uses --{property} or --color-{property}.

v2 CSS Variables:

--heroui-font-size-tiny: 0.75rem;
--heroui-font-size-small: 0.875rem;
--heroui-radius-small: 8px;
--heroui-radius-medium: 12px;
--heroui-border-width-medium: 2px;
--heroui-disabled-opacity: 0.5;

v3 CSS Variables:

/* Typography - handled by Tailwind */
/* No custom font-size variables */

/* 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);

/* Colors */
--color-background: var(--background);
--color-foreground: var(--foreground);
--color-accent: var(--accent);
--color-muted: var(--muted);

/* Opacity */
--disabled-opacity: 0.5;

Color System Changes

v2 Color Structure:

--heroui-primary: 210 100% 50%;
--heroui-primary-50: 210 100% 95%;
--heroui-primary-100: 210 100% 90%;
/* ... more shades ... */

v3 Color Structure:

--accent: oklch(0.6204 0.195 253.83);
--accent-foreground: var(--snow);
--accent-hover: color-mix(in oklab, var(--accent) 90%, var(--accent-foreground) 10%);

Key Differences:

  1. Color Format: v2 used HSL, v3 uses OKLCH
  2. Naming: v2 used numbered shades (50-900), v3 uses semantic names
  3. Calculated Colors: v3 uses color-mix() for hover states
  4. Foreground Colors: v3 explicitly defines foreground colors

Spacing & Layout Tokens

v2 Layout Tokens:

--heroui-divider-weight: 1px;
--heroui-disabled-opacity: 0.5;
--heroui-hover-opacity: 0.8;

v3 Layout Tokens:

--border-width: 0px;
--field-border-width: var(--border-width);
--disabled-opacity: 0.5;
--cursor-interactive: pointer;
--cursor-disabled: not-allowed;
--radius: 0.5rem;
--field-radius: calc(var(--radius) * 1.5);

Shadow Tokens

v2 Shadows:

--heroui-box-shadow-small: 0px 0px 5px 0px rgb(0 0 0 / 0.02), ...;
--heroui-box-shadow-medium: 0px 0px 15px 0px rgb(0 0 0 / 0.03), ...;
--heroui-box-shadow-large: 0px 0px 30px 0px rgb(0 0 0 / 0.04), ...;

v3 Shadows:

--surface-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.04), ...;
--overlay-shadow: 0 4px 16px 0 rgba(24, 24, 27, 0.08), ...;
--field-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.04), ...;

Key Changes:

  1. Semantic Naming: v3 uses semantic names (surface-shadow, overlay-shadow) instead of size-based names
  2. Component-Specific: Shadows are tied to component types (surface, overlay, field)
  3. Dark Mode: Dark mode shadows are transparent in v3

Visual Differences

Alignment Changes

Button Alignment:

  • v2: Icons and text aligned with items-center justify-center
  • v3: Same alignment, but with responsive height adjustments

Input Alignment:

  • v2: Text aligned with text-left
  • v3: Same alignment, but padding adjustments may affect visual balance

Spacing Changes

Component Padding:

Most components have increased padding in v3:

  • Card: 12px → 16px
  • Button: Similar padding, but responsive heights
  • Input: Added vertical padding (py-2)

Gap Spacing:

v3 uses more consistent gap spacing:

  • Card: gap-3 between header, content, footer
  • Button: gap-2 between icon and text
  • Chip: gap-1.5 between elements

Size Changes

Button Heights:

  • Small: 32px → 36px (mobile) / 32px (desktop)
  • Medium: 40px → 40px (mobile) / 36px (desktop)
  • Large: 48px → 44px (mobile) / 40px (desktop)

Input Heights:

  • Medium: 40px → 36px (default, only size available)

Border Radius Changes

Default Radius:

  • v2: Components used rounded-medium (12px) by default
  • v3: Components use larger radius values:
    • Button: rounded-3xl (24px)
    • Card: rounded-3xl (24px)
    • Chip: rounded-2xl (16px)
    • Input: rounded-field (typically 12-16px)

Color Appearance Changes

Color System:

  • v2: HSL color format
  • v3: OKLCH color format (more perceptually uniform)

Default Colors:

  • v2: primary, secondary, success, warning, danger
  • v3: accent (replaces primary), success, warning, danger

Muted Colors:

  • v2: foreground-400, foreground-500 for muted text
  • v3: muted color token for muted text

Migration Examples

Complete Component Migration

Example: Button with Custom Styling

<Button
  color="primary"
  variant="solid"
  size="md"
  radius="md"
  className="text-small transition-transform-colors-opacity"
>
  <Icon icon="gravity-ui:check" />
  Submit
</Button>
<Button
  variant="primary"
  size="md"
  className="rounded-md text-sm transition-[transform,colors,opacity] duration-150"
>
  <Icon icon="gravity-ui:check" />
  Submit
</Button>

Utility Class Migration

Example: Text Utilities

<div>
  <h1 className="text-large font-bold">Title</h1>
  <p className="text-small text-foreground-400">Description</p>
  <span className="text-tiny text-foreground-500">Helper</span>
</div>
<div>
  <h1 className="text-lg font-bold">Title</h1>
  <p className="text-sm text-muted">Description</p>
  <span className="text-xs text-muted">Helper</span>
</div>

Border Radius Migration

Example: Matching v2 Radius Values

<Card radius="md">
  <CardBody>Content</CardBody>
</Card>
{/* Option 1: Use standard Tailwind (smaller radius) */}
<Card className="rounded-md">
  <Card.Content>Content</Card.Content>
</Card>

{/* Option 2: Match exact v2 value (12px) */}
<Card className="rounded-[12px]">
  <Card.Content>Content</Card.Content>
</Card>

Theme Customization Migration

Example: Custom Colors

// tailwind.config.js
const {heroui} = require("@heroui/react");

module.exports = {
  plugins: [
    heroui({
      themes: {
        light: {
          colors: {
            primary: {
              DEFAULT: "#006FEE",
              50: "#E6F1FE",
              // ... more shades
            },
          },
        },
      },
    }),
  ],
};
/* globals.css */
@import "tailwindcss";
@import "@heroui/styles";

:root {
  --accent: oklch(0.6204 0.195 253.83);
  --accent-foreground: oklch(0.9911 0 0);
}

Common Issues & Solutions

Issue: Components look different after migration

Solution: Check for:

  1. Border radius differences - v3 uses larger default radius
  2. Height differences - v3 has responsive heights
  3. Padding differences - v3 may have different padding
  4. Font size differences - v3 uses standard Tailwind text sizes

Issue: Custom utilities not working

Solution: Replace custom utilities with standard Tailwind classes:

  • text-tinytext-xs
  • rounded-smallrounded-sm (or rounded-[8px] for exact match)
  • border-mediumborder-2

Issue: Colors look different

Solution:

  1. v3 uses OKLCH color format which may appear slightly different
  2. Update color references: primaryaccent
  3. Update muted colors: foreground-400muted

Issue: Border radius too small

Solution: v3 default radius values are smaller. Use arbitrary values to match v2:

  • rounded-[8px] for rounded-small
  • rounded-[12px] for rounded-medium
  • rounded-[14px] for rounded-large

Issue: Transitions not working

Solution: Replace custom transition utilities with standard Tailwind:

  • .transition-backgroundtransition-colors
  • .transition-colors-opacitytransition-[colors,opacity]

Best Practices

  1. Use Standard Tailwind: Prefer standard Tailwind utilities over custom ones
  2. Match v2 Values: If exact v2 appearance is needed, use arbitrary values
  3. Test Responsively: v3 has responsive sizing - test on multiple screen sizes
  4. Update CSS Variables: If customizing, update CSS variables instead of Tailwind config
  5. Check Component Docs: Refer to individual component migration guides for API changes