mcpField
U
ui-design-system

ui-design-system

github.com/hieutrtrデザイン・UI8118 回インストール38 週間前に更新
リポジトリ

ui-design-system について

UI Design System は、一貫したデザイントークン、レイアウト、アクセシビリティを必要とする画面の開発やレビュー向けのスキルです。Tailwind と shadcn/ui の指針を示し、UI コードの変更前に既存トークンを確認するよう求めます。

主な機能

  1. 01既存のデザイントークンを確認
  2. 02セマンティックな色と文字のトークンを定義
  3. 03一貫した余白とレイアウトを適用
  4. 04利用可能な場合は shadcn/ui のプリミティブを使用
  5. 05レスポンシブとアクセシビリティの規則に準拠

活用シーン

  1. 01既存のデザインシステムに沿ってUIコンポーネントを構築
  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. CSS カスタムプロパティについて、src/styles/globals.css または app/globals.css を読みます
  3. shadcn/ui が設定されている場合は、components.json を読みます

デザイントークンが存在しない場合は、初期セットを生成し、作業を進める前にユーザーに確認を求めてください(「エッジケース」を参照)。

デザイントークン
カラートークン

Tailwind が使用する CSS カスタムプロパティとして色を定義します。コンポーネント内でハードコードした hex/rgb 値は使用しないでください。

CSS カスタムプロパティ(shadcn/ui との互換性のため HSL 形式):

/* 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 のような値は使いません)
コンポーネントの構造
階層:コンテナー > レイアウト > コンテンツ

すべてのコンポーネントは、次の 3 層構造に従います:

// 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>ポータルを使用した独自のモーダル
ドロップダウン<DropdownMenu>独自のドロップダウン
カード<Card>スタイルを適用した <div>
入力欄<Input>スタイルを適用した <input>
セレクト<Select>ネイティブの <select>
ツールチップ<Tooltip>独自のツールチップ
タブ<Tabs>独自のタブコンポーネント
テーブル<Table>通常の <table>
アラート<Alert>独自のバナー用 div

shadcn/ui がインストールされていない場合は、同等のパターンと一貫したクラスの順序で、プレーンな Tailwind に切り替えてください。

TypeScript コンポーネントインターフェース

JSDoc の説明を付けた TypeScript インターフェースとして props をエクスポートします。

/** 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 を使用します
  • オプショナルな prop のデフォルト値は、インターフェースではなく分割代入で指定します
  • 値を制限する文字列には、ユニオン型を使用します:"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>の代わりに<button className="...">を、<Card>の代わりにスタイルを設定した<div>を使用します。同じスペーシングとカラートークンのアプローチを維持します。

  • **コンポーネントが重複する場合:**リクエストされたコンポーネントが既存のものと重複する場合は、その旨を伝えます。「類似したUserCardコンポーネントがsrc/components/UserCard.tsxにあります。これを拡張しますか、それとも別のコンポーネントを作成しますか?」

  • ダークモードのトークンがない場合::rootトークンは存在するものの.darkバリアントがない場合は、作業を進める前に対応するダークバリアントを生成します。すべてのセマンティックカラートークンに、ライトとダーク両方の値が必要です。

  • **カスタムブランドカラー:**ユーザーが特定のブランドカラーの16進数値を指定した場合は、HSLに変換してトークンシステムに組み込みます。コンポーネント内で16進数値を直接使用しないでください。

  • **既存コードのスペーシングに一貫性がない場合:**一貫性がないことを伝え、最も近い8ptグリッドの値を提案し、既存のコンポーネントを正規化するか、新しいコードにのみグリッドを適用するかを確認します。

スターター用のトークンセット、カラーパレットのガイド、タイポグラフィのスケールについては、references/design-tokens-reference.mdを参照してください。

コメント