ProComponents, templates & AI tooling
2.3k

主题

使用 CSS 变量与全局样式定制 HeroUI Native 设计系统

HeroUI Native 使用 CSS 变量实现主题化,可用标准 CSS 覆盖从颜色到组件样式的一切。

工作原理

主题系统建立在 Tailwind CSS v4 的主题能力之上,并通过 Uniwind 接入 React Native。导入 heroui-native/styles 后,会使用 Tailwind 内置色板并映射到语义变量,自动在明暗主题间切换,并借助 CSS 层与 @theme 进行组织。

命名约定:

  • 无后缀的颜色一般用作背景(如 --accent
  • -foreground 后缀的用于该背景上的文字(如 --accent-foreground

快速开始

在组件中应用颜色:

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

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

切换主题:

通过 Uniwind,HeroUI Native 自动支持深色模式;可跟随系统,也可手动切换明暗变体:

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>
  );
}

覆盖颜色:

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

说明: 完整色板与可视化参考见 颜色

创建自定义主题:

可借助 Uniwind 的变体系统定义多套主题。完整自定义主题文档见 Uniwind 自定义主题指南

重要: 所有主题必须定义相同的变量集合。必填变量清单见 默认主题

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

      --surface-secondary: oklch(0.96 0.012 230);
      --surface-secondary-foreground: oklch(0.3 0.045 230);

      --surface-tertiary: oklch(0.94 0.015 230);
      --surface-tertiary-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);
      --backdrop: oklch(0% 0 0 / 20%);

      --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%);
      --separator: oklch(0.91 0.015 230);
      --focus: var(--accent);
      --link: oklch(0.62 0.17 230);

      /* 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 2px 8px 0 rgba(0, 0, 0, 0.02), 0 -6px 12px 0 rgba(0, 0, 0, 0.01),
        0 14px 28px 0 rgba(0, 0, 0, 0.03);
      --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);
    }

    @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);

      --surface-secondary: oklch(0.24 0.046 230);
      --surface-secondary-foreground: oklch(0.9 0.015 230);

      --surface-tertiary: oklch(0.27 0.044 230);
      --surface-tertiary-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);
      --backdrop: oklch(0% 0 0 / 20%);

      --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%);
      --separator: oklch(0.28 0.045 230);
      --focus: var(--accent);
      --link: oklch(0.75 0.18 230);

      /* Shadows */
      --surface-shadow: 0 0 0 0 transparent inset; /* No shadow on dark mode */
      --overlay-shadow: 0 0 1px 0 rgba(255, 255, 255, 0.3) inset;
      --field-shadow: 0 0 0 0 transparent inset; /* Transparent shadow to allow ring utilities to work */
    }
  }
}

重要: 添加自定义主题后,必须在 Metro 配置中注册:

// 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'],
});

在应用中切换主题:

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>
  );
}

添加自定义颜色

在主题中加入自定义语义色:

@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);
  }
}

/* 让颜色可被 Tailwind 使用 */
@theme inline {
  --color-info: var(--info);
  --color-info-foreground: var(--info-foreground);
}

在组件中使用:

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

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

自定义字体

要在应用中使用自定义字体,需要先加载字体,再覆盖字体相关的 CSS 变量。

1. 在应用中加载字体

先加载字体(例如使用 Expo 的 useFonts):

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. 配置字体 CSS 变量

加载完成后,在 global.css 中覆盖字体变量:

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

说明: CSS 变量中的字体名应与已加载字体的 PostScript 名称一致。请查阅字体包文档,或直接使用 useFonts 中出现的名称。

所有 HeroUI Native 组件会自动使用这些字体变量,以保持排版一致。

变量参考

HeroUI 定义三类变量:

  1. 基础变量 — 如 --white--black 等不随主题切换的值
  2. 主题变量 — 随明暗主题切换的颜色
  3. 计算变量 — 自动生成的按压态(hover)与尺寸变体等

完整参考:颜色文档默认主题变量共享主题工具

计算变量(Tailwind):

我们通过 Tailwind 的 @theme 指令自动生成按压态与圆角等计算变量,定义见 theme.css

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

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

表单控件依赖 --field-* 变量及其计算出的 hover/focus 变体。在主题中调整它们即可重塑输入框、复选框、单选与 OTP 等,而不会影响按钮、卡片等 Surface 组件的观感。

鲜亮配色

默认情况下,HeroUI Native 使用可读性更好的柔和前景色,即将语义色与前景色按比例混合,以在 soft 背景上获得更佳对比度。如果你偏好饱和度更高、更鲜亮的柔和前景色,可以在引入基础样式之后,额外导入可选的 heroui-native/styles/vibrant 样式:

/* global.css */
@import "heroui-native/styles";
@import "heroui-native/styles/vibrant"; 

这会将所有 *-soft-foreground 变量(accent、success、warning、danger)切换为「语义色 92% + 前景色 8%」的混合配方——更贴近原始色调,但仍带有轻微的对比度增强。AlertAvatarButtonChipToast 等组件会自动在其 soft 变体上使用新的柔和前景色——无需修改任何组件属性。

模式可读性优先(默认)鲜亮
Soft 前景color-mix(color 70-80%, foreground 30-40%)color-mix(color 92%, foreground 8%)

鲜亮配色优先考虑视觉饱和度而非对比度。对某些颜色组合(尤其是更浅的强调色),它可能不满足 WCAG 无障碍准则。

可选的鲜亮配色自 v1.0.4 起提供。

相关资源

本页目录