主题
使用 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 定义三类变量:
- 基础变量 — 如
--white、--black等不随主题切换的值 - 主题变量 — 随明暗主题切换的颜色
- 计算变量 — 自动生成的按压态(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%」的混合配方——更贴近原始色调,但仍带有轻微的对比度增强。Alert、Avatar、Button、Chip、Toast 等组件会自动在其 soft 变体上使用新的柔和前景色——无需修改任何组件属性。
| 模式 | 可读性优先(默认) | 鲜亮 |
|---|---|---|
| Soft 前景 | color-mix(color 70-80%, foreground 30-40%) | color-mix(color 92%, foreground 8%) |
鲜亮配色优先考虑视觉饱和度而非对比度。对某些颜色组合(尤其是更浅的强调色),它可能不满足 WCAG 无障碍准则。
可选的鲜亮配色自 v1.0.4 起提供。