mcpField
U
ui-design-system

ui-design-system

github.com/hieutrtr设计与 UI8118 次安装38 周前更新
代码仓库

关于 ui-design-system

UI Design System 是面向界面开发与审查的技能,帮助保持设计令牌、布局和无障碍规范一致。它提供 Tailwind 与 shadcn/ui 的约定,并要求修改界面代码前先检查现有令牌。

主要功能

  1. 01检查现有设计令牌
  2. 02定义语义化颜色和字体令牌
  3. 03统一间距与布局
  4. 04优先使用可用的 shadcn/ui 基元
  5. 05遵循响应式与无障碍规范

适用场景

  1. 01按现有设计系统构建界面组件
  2. 02为项目设置设计令牌
  3. 03检查界面一致性和无障碍性
  4. 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 代码之前,请检查项目中是否已有设计令牌:

  1. 读取 tailwind.config.ts(或 .js),查看自定义主题扩展
  2. 读取 src/styles/globals.css 或 app/globals.css,查看 CSS 自定义属性
  3. 如果已配置 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-xs12px16px说明文字、辅助文本
text-sm14px20px次要文本、标签
text-base16px24px正文(默认)
text-lg18px28px小标题
text-xl20px28px分区标题
text-2xl24px32px页面标题
text-3xl30px36px主视觉标题

排版规则:

  • 在 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-14px行内图标内边距、紧凑间距
p-2 / gap-28px紧凑元素间距
p-3 / gap-312px输入框内边距、小卡片内边距
p-4 / gap-416px标准组件内边距
p-6 / gap-624px卡片内边距、分区间距
p-8 / gap-832px分区内边距
p-12 / gap-1248px页面分区间距
p-16 / gap-1664px主要布局间距

间距规则:

  • 对于 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,以便组合使用
  • 使用 variant prop 表示视觉变化,而不是创建单独的组件
  • 在解构时为可选 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
交互元素要求

每个交互元素都必须具备:

  1. 可见标签或 aria-label: <Button aria-label="Close dialog">X</Button>
  2. 焦点指示器: Tailwind 的 ring 工具类:focus-visible:ring-2 focus-visible:ring-ring
  3. 键盘访问: 可通过 Tab 键聚焦,并通过 Enter/Space 键激活
  4. 禁用状态: 同时具备视觉状态和 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。

评论

支持 Markdown