Theming

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

HeroUI Native uses CSS variables for theming. Customize everything from colors to component styles using standard CSS.

How It Works

HeroUI Native's theming system is built on top of Tailwind CSS v4's theme via Uniwind. When you import heroui-native/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 colors in your components:

import { View, Text } from 'react-native';

<View className="bg-background flex-1">
  <Text className="text-foreground">Your app content</Text>
</View>

Switch themes:

HeroUI Native automatically supports dark mode through Uniwind. The theme switches between light and dark variants based on system preferences or manual selection:

import { Uniwind, useUniwind } from 'uniwind';
import { Button } from 'heroui-native';

function ThemeToggle() {
  const { theme } = useUniwind();

  return (
    <Button
      onPress={() => Uniwind.setTheme(theme === 'light' ? 'dark' : 'light')}
    >
      <Button.Label>Toggle {theme === 'light' ? 'Dark' : 'Light'} Mode</Button.Label>
    </Button>
  );
}

Override colors:

/* global.css */
@layer theme {
  @variant light {
    /* Override any color variable */
    --accent: oklch(0.65 0.25 270); /* Custom indigo accent */
    --success: oklch(0.65 0.15 155);
  }

  @variant dark {
    --accent: oklch(0.65 0.25 270);
    --success: oklch(0.75 0.12 155);
  }
}

Note: See Colors for the complete color palette and visual reference.

Create your own theme:

Create multiple themes using Uniwind's variant system. For complete custom theme documentation, see the Uniwind Custom Themes Guide.

Important: All themes must define the same variables. See the Default Theme section for a complete list of all required variables.

/* global.css */
@layer theme {
  :root {
    @variant ocean-light {
      /* Base Colors */
      --background: oklch(0.95 0.02 230);
      --foreground: oklch(0.25 0.04 230);

      /* Surface: Used for non-overlay components (cards, accordions, disclosure groups) */
      --surface: oklch(0.98 0.01 230);
      --surface-foreground: oklch(0.3 0.045 230);

      /* Overlay: Used for floating/overlay components (dialogs, popovers, modals, menus) */
      --overlay: oklch(0.998 0.003 230);
      --overlay-foreground: oklch(0.3 0.045 230);

      --muted: oklch(0.55 0.035 230);

      --default: oklch(0.94 0.018 230);
      --default-foreground: oklch(0.4 0.05 230);

      /* Accent */
      --accent: oklch(0.6 0.2 230);
      --accent-foreground: oklch(0.98 0.005 230);

      /* Form Field Defaults - Colors */
      --field-background: oklch(0.98 0.01 230);
      --field-foreground: oklch(0.25 0.04 230);
      --field-placeholder: var(--muted);
      --field-border: transparent;

      /* Status Colors */
      --success: oklch(0.72 0.14 165);
      --success-foreground: oklch(0.25 0.08 165);

      --warning: oklch(0.78 0.12 85);
      --warning-foreground: oklch(0.3 0.08 85);

      --danger: oklch(0.68 0.18 15);
      --danger-foreground: oklch(0.98 0.005 15);

      /* Component Colors */
      --segment: oklch(0.98 0.01 230);
      --segment-foreground: oklch(0.25 0.04 230);

      /* Misc Colors */
      --border: oklch(0 0 0 / 0%);
      --divider: oklch(0.91 0.015 230);
      --link: oklch(0.62 0.17 230);
    }

    @variant ocean-dark {
      /* Base Colors */
      --background: oklch(0.15 0.04 230);
      --foreground: oklch(0.94 0.01 230);

      /* Surface: Used for non-overlay components (cards, accordions, disclosure groups) */
      --surface: oklch(0.2 0.048 230);
      --surface-foreground: oklch(0.9 0.015 230);

      /* Overlay: Used for floating/overlay components (dialogs, popovers, modals, menus) */
      --overlay: oklch(0.23 0.045 230);
      --overlay-foreground: oklch(0.9 0.015 230);

      --muted: oklch(0.5 0.04 230);

      --default: oklch(0.25 0.05 230);
      --default-foreground: oklch(0.88 0.018 230);

      /* Accent */
      --accent: oklch(0.72 0.21 230);
      --accent-foreground: oklch(0.15 0.04 230);

      /* Form Field Defaults - Colors */
      --field-background: var(--default);
      --field-foreground: var(--foreground);
      --field-placeholder: var(--muted);
      --field-border: transparent;

      /* Status Colors */
      --success: oklch(0.68 0.16 165);
      --success-foreground: oklch(0.95 0.008 165);

      --warning: oklch(0.75 0.14 90);
      --warning-foreground: oklch(0.2 0.04 90);

      --danger: oklch(0.65 0.2 20);
      --danger-foreground: oklch(0.95 0.008 20);

      /* Component Colors */
      --segment: oklch(0.22 0.046 230);
      --segment-foreground: oklch(0.9 0.015 230);

      /* Misc Colors */
      --border: oklch(0 0 0 / 0%);
      --divider: oklch(0.28 0.045 230);
      --link: oklch(0.75 0.18 230);
    }
  }
}

Important: When adding custom themes, you must register them in your Metro config:

// metro.config.js
const { withUniwindConfig } = require('uniwind/metro');
const { wrapWithReanimatedMetroConfig } = require('react-native-reanimated/metro-config');

const config = {
  // ... your existing config
};

module.exports = withUniwindConfig(wrapWithReanimatedMetroConfig(config), {
  cssEntryFile: './global.css',
  dtsFile: './src/uniwind.d.ts',
  extraThemes: [
    'ocean-light',
    'ocean-dark',
  ],
});

Apply themes in your app:

import { Uniwind } from 'uniwind';
import { Button } from 'heroui-native';

function App() {
  return (
    <Button onPress={() => Uniwind.setTheme('ocean-light')}>
      <Button.Label>Ocean Theme</Button.Label>
    </Button>
  );
}

Adding Custom Colors

Add your own semantic colors to the theme:

@layer theme {
  @variant light {
    --info: oklch(0.6 0.15 210);
    --info-foreground: oklch(0.98 0 0);
  }

  @variant 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:

import { View, Text } from 'react-native';

<View className="bg-info p-4 rounded-lg">
  <Text className="text-info-foreground">Info message</Text>
</View>

Custom Fonts

To use a custom font family in your app, you need to load the fonts and then override the font CSS variables.

1. Load Fonts in Your App

First, load your custom fonts (using Expo's useFonts hook for example):

import { useFonts } from 'expo-font';
import { HeroUINativeProvider } from 'heroui-native';
import { YourFont_400Regular, YourFont_500Medium, YourFont_600SemiBold } from '@expo-google-fonts/your-font';

export default function App() {
  const [fontsLoaded] = useFonts({
    YourFont_400Regular,
    YourFont_500Medium,
    YourFont_600SemiBold,
  });

  if (!fontsLoaded) {
    return null; // Or return a loading screen
  }

  return (
    <HeroUINativeProvider>
      {/* Your app content */}
    </HeroUINativeProvider>
  );
}

2. Configure Font CSS Variables

After loading the fonts, override the font CSS variables in your global.css file:

@theme {
  --font-normal: 'YourFont-Regular';
  --font-medium: 'YourFont-Medium';
  --font-semibold: 'YourFont-SemiBold';
}

Note: The font names in CSS variables should match the PostScript names of your loaded fonts. Check your font package documentation or use the font names exactly as they appear in your useFonts hook.

All HeroUI Native components automatically use these font variables, ensuring consistent typography throughout your app.

Variables Reference

HeroUI defines three types of variables:

  1. Base Variables — Non-changing values like --white, --black
  2. Theme Variables — Colors that change between light/dark themes
  3. Calculated Variables — Automatically generated hover (pressed) states and size variants

For a complete reference, see: Colors Documentation, Default Theme Variables, Shared Theme Utilities

Calculated variables (Tailwind):

We use Tailwind's @theme directive to automatically create calculated variables for hover (pressed) states and radius variants. These are defined in theme.css:

@theme inline static {
  --color-background: var(--background);
  --color-foreground: var(--foreground);

  --color-surface: var(--surface);

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.

Resources

On this page