关于 ui-design-system
UI Design System 是面向界面开发与审查的技能,帮助保持设计令牌、布局和无障碍规范一致。它提供 Tailwind 与 shadcn/ui 的约定,并要求修改界面代码前先检查现有令牌。
主要功能
- 01检查现有设计令牌
- 02定义语义化颜色和字体令牌
- 03统一间距与布局
- 04优先使用可用的 shadcn/ui 基元
- 05遵循响应式与无障碍规范
适用场景
- 01按现有设计系统构建界面组件
- 02为项目设置设计令牌
- 03检查界面一致性和无障碍性
- 04将 shadcn/ui 集成到现有布局
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ hieutrtr/ ai1-skills --skill ui-design-system安装后在 Claude Code 中输入/ui-design-system确认已加载。
附带文件references/design-tokens-reference.md
详细介绍
UI 设计系统
适用场景
在以下情况下启用此技能:
- 创建必须遵循设计系统的新 UI 组件
- 构建间距和结构一致的页面布局
- 设置或扩展设计令牌(颜色、排版、间距)
- 为项目选择颜色、字体或间距值
- 检查 UI 代码的设计一致性和无障碍性
- 将 shadcn/ui 组件集成到现有布局中
请勿在以下情况下使用此技能:
- 实现后端 API(使用
python-backend-expert) - 测试组件或钩子(使用
react-testing-patterns) - 进行 E2E 浏览器测试(使用
e2e-testing) - 处理与设计系统无关的常规 React 模式(使用
react-frontend-expert) - 部署或 CI/CD(使用
deployment-pipeline)
说明
步骤 0:读取现有设计令牌
生成任何 UI 代码之前,请检查项目中是否已有设计令牌:
- 读取
tailwind.config.ts(或.js),查看自定义主题扩展 - 读取
src/styles/globals.css或app/globals.css,查看 CSS 自定义属性 - 如果已配置 shadcn/ui,请读取
components.json
如果不存在设计令牌,请生成一组入门令牌,并在继续之前请用户确认(参见边缘情况)。
设计令牌
颜色令牌
将颜色定义为供 Tailwind 使用的 CSS 自定义属性。组件中不得使用硬编码的十六进制或 rgb 值。
CSS 自定义属性(使用 HSL 格式以兼容 shadcn/ui):
/* globals.css */
@layer base {
:root {
--background: 0 0% 100%;
--foreground: 222 47% 11%;
--primary: 221 83% 53%;
--primary-foreground: 210 40% 98%;
--secondary: 210 40% 96%;
--secondary-foreground: 222 47% 11%;
--muted: 210 40% 96%;
--muted-foreground: 215 16% 47%;
--accent: 210 40% 96%;
--accent-foreground: 222 47% 11%;
--destructive: 0 84% 60%;
--destructive-foreground: 210 40% 98%;
--border: 214 32% 91%;
--input: 214 32% 91%;
--ring: 221 83% 53%;
--radius: 0.5rem;
}
.dark {
--background: 222 47% 11%;
--foreground: 210 40% 98%;
--primary: 217 91% 60%;
--primary-foreground: 222 47% 11%;
--secondary: 217 33% 17%;
--secondary-foreground: 210 40% 98%;
--muted: 217 33% 17%;
--muted-foreground: 215 20% 65%;
--accent: 217 33% 17%;
--accent-foreground: 210 40% 98%;
--destructive: 0 63% 31%;
--destructive-foreground: 210 40% 98%;
--border: 217 33% 17%;
--input: 217 33% 17%;
--ring: 224 76% 48%;
}
}
Tailwind 配置映射:
// tailwind.config.ts
export default {
theme: {
extend: {
colors: {
background: "hsl(var(--background))",
foreground: "hsl(var(--foreground))",
primary: {
DEFAULT: "hsl(var(--primary))",
foreground: "hsl(var(--primary-foreground))",
},
secondary: {
DEFAULT: "hsl(var(--secondary))",
foreground: "hsl(var(--secondary-foreground))",
},
muted: {
DEFAULT: "hsl(var(--muted))",
foreground: "hsl(var(--muted-foreground))",
},
accent: {
DEFAULT: "hsl(var(--accent))",
foreground: "hsl(var(--accent-foreground))",
},
destructive: {
DEFAULT: "hsl(var(--destructive))",
foreground: "hsl(var(--destructive-foreground))",
},
border: "hsl(var(--border))",
input: "hsl(var(--input))",
ring: "hsl(var(--ring))",
},
},
},
} satisfies Config;
颜色使用规则:
- 始终使用语义令牌类:
bg-primary、text-foreground、border-border - 组件代码中不得使用原始 Tailwind 调色板颜色(
bg-blue-500) - 每种颜色都必须定义深色模式变体
- 在有色背景上显示文本时,使用
foreground变体
排版比例
使用 Tailwind 的字号工具类定义排版比例:
| 令牌 | 大小 | 行高 | 用途 |
|---|---|---|---|
text-xs | 12px | 16px | 说明文字、辅助文本 |
text-sm | 14px | 20px | 次要文本、标签 |
text-base | 16px | 24px | 正文(默认) |
text-lg | 18px | 28px | 小标题 |
text-xl | 20px | 28px | 分区标题 |
text-2xl | 24px | 32px | 页面标题 |
text-3xl | 30px | 36px | 主视觉标题 |
排版规则:
- 在
tailwind.config.ts中设置基础字体:fontFamily: { sans: ["Inter", "system-ui", "sans-serif"] } - 标题和标签使用
font-medium(500),正文使用font-normal(400) text-2xl及以上的标题使用tracking-tight- 使用
max-w-prose(65ch)限制行长,以提高可读性
间距(8pt 网格)
所有间距值均遵循 8pt 基础网格:
| Tailwind 类 | 值 | 用途 |
|---|---|---|
p-1 / gap-1 | 4px | 行内图标内边距、紧凑间距 |
p-2 / gap-2 | 8px | 紧凑元素间距 |
p-3 / gap-3 | 12px | 输入框内边距、小卡片内边距 |
p-4 / gap-4 | 16px | 标准组件内边距 |
p-6 / gap-6 | 24px | 卡片内边距、分区间距 |
p-8 / gap-8 | 32px | 分区内边距 |
p-12 / gap-12 | 48px | 页面分区间距 |
p-16 / gap-16 | 64px | 主要布局间距 |
间距规则:
- 对于 flex/grid 子元素,使用
gap-*,而不是分别设置外边距 - 垂直堆叠的同级元素优先使用
space-y-* - 卡片:使用
p-6内边距,内部元素之间使用gap-4 - 页面分区:使用
py-12或py-16垂直内边距 - 不得混用间距体系(不要使用
margin: 13px)
组件结构
层级:容器 > 布局 > 内容
每个组件都遵循三层结构:
// Container: outer wrapper with spacing, background, border
<Card className="p-6">
{/* Layout: flex/grid arrangement */}
<div className="flex items-center gap-4">
{/* Content: actual UI elements */}
<Avatar src={user.avatar} alt={user.name} />
<div className="space-y-1">
<h3 className="text-sm font-medium">{user.name}</h3>
<p className="text-sm text-muted-foreground">{user.role}</p>
</div>
</div>
</Card>
语义化 HTML
每种用途都应使用正确的 HTML 元素:
| 元素 | 用途 | 不要使用 |
|---|---|---|
<button> | 可点击的操作 | <div onClick> |
<a> | 导航链接 | 用于链接的 <button> |
<nav> | 导航区域 | <div> |
<main> | 页面主要内容 | <div> |
<article> | 独立内容(卡片、帖子) | <div> |
<section> | 带标题的主题分组 | <div> |
<aside> | 侧边栏或相关性较弱的内容 | <div> |
<header> | 某个部分的介绍性内容 | <div> |
<footer> | 某个部分的页脚内容 | <div> |
<ul> / <ol> | 项目列表 | 用于每个列表项的 <div> |
shadcn/ui 基元组件
优先使用 shadcn/ui 组件,而非自定义实现:
| 需求 | 使用 | 不要使用 |
|---|---|---|
| 按钮 | <Button> | 添加样式的自定义 <button> |
| 模态框 | <Dialog> | 使用 portal 的自定义模态框 |
| 下拉菜单 | <DropdownMenu> | 自定义下拉菜单 |
| 卡片 | <Card> | 添加样式的 <div> |
| 输入框 | <Input> | 添加样式的 <input> |
| 选择框 | <Select> | 原生 <select> |
| 工具提示 | <Tooltip> | 自定义工具提示 |
| 标签页 | <Tabs> | 自定义标签页组件 |
| 表格 | <Table> | 普通 <table> |
| 提示框 | <Alert> | 自定义横幅 div |
如果未安装 shadcn/ui,请改用具有等效模式且类名顺序一致的纯 Tailwind。
TypeScript 组件接口
将 props 导出为带有 JSDoc 描述的 TypeScript 接口:
/** Props for the UserProfileCard component. */
interface UserProfileCardProps {
/** User data to display. */
user: User;
/** Called when the edit button is clicked. */
onEdit?: (userId: string) => void;
/** Visual variant of the card. */
variant?: "default" | "compact";
/** Additional CSS classes applied to the root element. */
className?: string;
}
export function UserProfileCard({
user,
onEdit,
variant = "default",
className,
}: UserProfileCardProps) {
// ...
}
Props 规则:
- 将接口命名为
{ComponentName}Props - 每个组件都添加
className?: string,以便组合使用 - 使用
variantprop 表示视觉变化,而不是创建单独的组件 - 在解构时为可选 props 设置默认值,而不是在接口中设置
- 对受限的字符串值使用联合类型:
"sm" | "md" | "lg"
响应式设计
断点
使用 Tailwind 的移动优先断点:
| 前缀 | 最小宽度 | 目标设备 |
|---|---|---|
| (无) | 0px | 移动设备(默认) |
sm: | 640px | 大屏手机 / 小型平板 |
md: | 768px | 平板 |
lg: | 1024px | 桌面设备 |
xl: | 1280px | 大屏桌面设备 |
响应式规则:
- 采用移动优先设计:基础样式用于移动设备,再添加断点覆盖样式
- 响应式网格使用
grid-cols-1 md:grid-cols-2 lg:grid-cols-3 - 在移动设备上将导航纵向排列:
flex-col md:flex-row - 在移动设备上隐藏非必要元素:
hidden md:block - 设置容器最大宽度:
max-w-7xl mx-auto px-4 sm:px-6 lg:px-8
响应式布局模式
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{items.map((item) => (
<ItemCard key={item.id} item={item} />
))}
</div>
</div>
无障碍访问(WCAG 2.1 AA)
对比度
- 普通文本(< 18px 或 < 14px 粗体):对比度至少为 4.5:1
- 大号文本(>= 18px 或 >= 14px 粗体):对比度至少为 3:1
- UI 组件和图形对象:对比度至少为 3:1
交互元素要求
每个交互元素都必须具备:
- 可见标签或 aria-label:
<Button aria-label="Close dialog">X</Button> - 焦点指示器: Tailwind 的
ring工具类:focus-visible:ring-2 focus-visible:ring-ring - 键盘访问: 可通过 Tab 键聚焦,并通过 Enter/Space 键激活
- 禁用状态: 同时具备视觉状态和
aria-disabled或disabled属性
ARIA 模式
- 仅含图标的按钮:
aria-label="Delete item" - 加载状态:在加载容器上添加
aria-busy="true" - 动态内容更新:在容器上添加
aria-live="polite" - 表单错误:在输入框上添加
aria-invalid="true",在提示消息上添加role="alert" - 模态框:
aria-modal="true"、焦点陷阱,以及按 Escape 键关闭 - 导航地标:
<nav aria-label="Main navigation">
示例
用户资料卡片
interface UserProfileCardProps {
user: { id: string; name: string; role: string; avatarUrl: string };
onEdit?: (userId: string) => void;
className?: string;
}
export function UserProfileCard({ user, onEdit, className }: UserProfileCardProps) {
return (
<Card className={cn("p-6", className)}>
<div className="flex items-center gap-4">
<Avatar className="h-12 w-12">
<AvatarImage src={user.avatarUrl} alt={user.name} />
<AvatarFallback>{user.name.charAt(0)}</AvatarFallback>
</Avatar>
<div className="space-y-1">
<h3 className="text-sm font-medium leading-none">{user.name}</h3>
<p className="text-sm text-muted-foreground">{user.role}</p>
</div>
</div>
{onEdit && (
<div className="mt-4">
<Button
variant="outline"
size="sm"
onClick={() => onEdit(user.id)}
aria-label={`Edit ${user.name}'s profile`}
>
Edit Profile
</Button>
</div>
)}
</Card>
);
}
SaaS 仪表盘设计令牌设置
Tailwind 配置扩展:
// tailwind.config.ts
import type { Config } from "tailwindcss";
export default {
theme: {
extend: {
colors: {
background: "hsl(var(--background))",
foreground: "hsl(var(--foreground))",
primary: {
DEFAULT: "hsl(var(--primary))",
foreground: "hsl(var(--primary-foreground))",
},
secondary: {
DEFAULT: "hsl(var(--secondary))",
foreground: "hsl(var(--secondary-foreground))",
},
muted: {
DEFAULT: "hsl(var(--muted))",
foreground: "hsl(var(--muted-foreground))",
},
accent: {
DEFAULT: "hsl(var(--accent))",
foreground: "hsl(var(--accent-foreground))",
},
destructive: {
DEFAULT: "hsl(var(--destructive))",
foreground: "hsl(var(--destructive-foreground))",
},
border: "hsl(var(--border))",
input: "hsl(var(--input))",
ring: "hsl(var(--ring))",
},
fontFamily: {
sans: ["Inter", "system-ui", "sans-serif"],
},
borderRadius: {
lg: "var(--radius)",
md: "calc(var(--radius) - 2px)",
sm: "calc(var(--radius) - 4px)",
},
},
},
} satisfies Config;
响应式仪表盘布局
export function DashboardLayout({ children }: { children: React.ReactNode }) {
return (
<div className="min-h-screen bg-background">
<nav className="border-b border-border" aria-label="Main navigation">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 h-16 flex items-center">
{/* nav content */}
</div>
</nav>
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
<div className="grid grid-cols-1 lg:grid-cols-[240px_1fr] gap-8">
<aside className="hidden lg:block" aria-label="Sidebar">
{/* sidebar content */}
</aside>
<main>{children}</main>
</div>
</div>
</div>
);
}
边缘情况
-
**没有现有设计令牌:**生成一组入门令牌(参见
references/design-tokens-reference.md),并在编写任何组件代码前提交给用户确认。询问:“未找到设计令牌。以下是一组入门令牌——要应用这些令牌吗?” -
**未安装 shadcn/ui:**改用纯 Tailwind,并采用等效模式。使用
<button className="...">代替<Button>,使用带样式的<div>代替<Card>。保持相同的间距和颜色令牌方案。 -
**组件重叠:**如果请求的组件与现有组件重复,请指出:“
src/components/UserCard.tsx中已存在类似的UserCard组件。应该扩展它,还是创建单独的组件?” -
**缺少深色模式令牌:**如果存在
:root令牌,但缺少.dark变体,请先生成匹配的深色变体再继续。每个语义颜色令牌都必须同时具有浅色和深色值。 -
**自定义品牌颜色:**用户提供特定品牌十六进制值时,将其转换为 HSL 并集成到令牌系统中。切勿在组件中直接使用十六进制值。
-
**现有代码中的间距不一致:**指出不一致之处,建议最接近的 8pt 网格值,并询问是要规范化现有组件,还是仅将网格应用于新代码。
有关入门令牌集、调色板指南和字体排版比例,请参见 references/design-tokens-reference.md。