ui-design について
Tailwind CSS を使った画面の構築やレビュー向けに、エージェントへ設計指針を与えるスキルです。アクセシビリティ、操作状態、アニメーション、タイポグラフィ、レイアウトに関する制約をまとめています。
主な機能
- 01Tailwind CSS のデフォルト値を優先
- 02操作性とアクセシビリティの要件
- 03アニメーションのプロパティと時間の制約
- 04タイポグラフィとレイアウトの規則
- 05ローディング・エラー・空状態への対応
活用シーン
- 01Tailwind CSS による UI コンポーネントの構築
- 02フロントエンドのアクセシビリティとキーボード操作の確認
- 03アニメーションやローディング・エラー状態の設計
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ ckorhonen/ claude-skills --skill ui-designインストール後、Claude Code で/ui-designを入力して読み込まれたか確認します。
詳細
UI デザイン
エージェントを使ってより優れたインターフェースを構築するための、明確な制約です。
使用する場面
- Tailwind CSS で UI コンポーネントを構築する
- アニメーションやトランジションを実装する
- キーボード/フォーカスの動作を伴うインタラクティブな要素を追加する
- アクセシビリティの観点からフロントエンドコードをレビューする
- 適切な z-index と間隔を用いてレイアウトを設計する
- ローディング状態、エラー処理、または空の状態を扱う
スタック
| 要件 | ルール |
|---|---|
| Tailwind CSS | カスタム値より先に、デフォルト値(間隔、角丸、シャドウ)を必ず使用する |
| アニメーションライブラリ | JavaScript アニメーションには motion/react(旧 framer-motion)を必ず使用する |
| CSS アニメーション | 登場アニメーションやマイクロアニメーションには tw-animate-css を使用することが推奨されます |
| クラスのロジック | cn ユーティリティ(clsx + tailwind-merge)を必ず使用する |
コンポーネント
| 要件 | ルール |
|---|---|
| インタラクティブなプリミティブ | キーボード/フォーカスの動作には、アクセシブルなプリミティブ(Base UI、React Aria、Radix)を必ず使用する |
| 既存のコンポーネント | まずプロジェクトに既存のプリミティブを必ず使用する |
| 一貫性 | 同じインタラクション領域内で、異なるプリミティブシステムを決して混在させない |
| 新しいプリミティブ | スタックとの互換性がある場合は、Base UI を優先することが推奨されます |
| アイコンボタン | アイコンのみのボタンには、必ず aria-label を追加する |
| カスタム動作 | 明示的に依頼されない限り、キーボードやフォーカスの動作を手作業で再構築しない |
インタラクション
| 要件 | ルール |
|---|---|
| 破壊的な操作 | 破壊的、または取り消せない操作には、必ず AlertDialog を使用する |
| ローディング状態 | 構造を反映したスケルトンを使用することが推奨されます |
| ビューポートの高さ | h-screen は決して使用せず、h-dvh を使用する |
| 固定要素 | 必ず safe-area-inset を考慮する |
| エラー表示 | 操作が発生する場所の近くに、必ずエラーを表示する |
| 入力の動作 | input または textarea 要素での貼り付けを決してブロックしない |
アニメーション
| 要件 | ルール |
|---|---|
| デフォルト | 明示的に依頼されない限り、アニメーションを追加しない |
| コンポジターのプロパティ | transform と opacity のみを必ずアニメーションさせる |
| レイアウトのプロパティ | width、height、top、left、margin、padding は決してアニメーションさせない |
| 描画のプロパティ | 小規模で局所的な UI(テキスト、アイコン)を除き、background と color は避けることが推奨されます |
| 登場時のイージング | 登場時には ease-out を使用することが推奨されます |
| フィードバックの時間 | インタラクションのフィードバックは、決して 200ms を超えないようにする |
| ループ | 画面外にある場合、ループアニメーションを必ず一時停止する |
| アクセシビリティ | 必ず prefers-reduced-motion に対応する |
| カスタムイージング | 明示的に依頼されない限り、カスタムのイージングカーブを導入しない |
| 大きな領域 | 大きな画像や全画面の領域をアニメーションさせないことが推奨されます |
タイポグラフィ
| 要件 | ルール |
|---|---|
| 見出し | 必ず text-balance を使用する |
| 本文 | 段落には必ず text-pretty を使用する |
| データ | 必ず tabular-nums を使用する |
| 情報量の多い UI | truncate または line-clamp を使用することが推奨されます |
| 文字間隔 | 明示的に依頼されない限り、letter-spacing(tracking-)を変更しない |
レイアウト
| 要件 | ルール |
|---|---|
| Z-index | 固定スケールを使用する(任意の z-x は使用しない) |
| 正方形の要素 | w-x + h-x ではなく、size-x を使用することが推奨されます |
パフォーマンス
| 要件 | ルール |
|---|---|
| ぼかし効果 | 大きな blur() または backdrop-filter の領域を決してアニメーションさせない |
| Will-change | アニメーションの実行中以外は、will-change を決して適用しない |
| useEffect | レンダリングロジックで表現できる処理には、決して使用しない |
デザイン
| 要件 | ルール |
|---|---|
| グラデーション | 明示的に依頼されない限り、決して使用しない |
| 紫色/マルチカラーのグラデーション | 決して使用しない |
| グロー効果 | 主要なアフォーダンスとして決して使用しない |
| シャドウ | 明示的に依頼されない限り、Tailwind CSS のデフォルトスケールを使用することが推奨されます |
| 空の状態 | 次に取るべき明確なアクションを必ず示す |
| アクセントカラー | 1 つのビューにつき 1 色までに抑えることが推奨されます |
| カラートークン | 新しいトークンを導入する前に、既存のテーマまたは Tailwind CSS のトークンを使用することが推奨されます |
よくある落とし穴
UI デザインで最もよくある失敗は、余白、色のコントラスト、モバイル対応、状態管理、視覚的階層に関するものです。これらの落とし穴は、インターフェースの構築時にエージェントが繰り返し犯す間違いから明らかになっています。
1. 余白の不統一(任意のマージン、システムなし)
問題: 意図したシステムなしに、Tailwind の余白値を一貫性なく組み合わせると、レイアウトに統一感がなくなります。
// ❌ WRONG - arbitrary pixel values, no spacing system
<div className="ml-[13px] mr-[22px] pt-[7px]">Content</div>
// ✅ CORRECT - use system scales
<div className="ml-4 mr-4 pt-4">Content</div>
うまくいかない理由:
- デザインが場当たり的に見えます(ある余白は
2px、別の余白は16pxで、パターンがありません) - モバイルレイアウトが崩れます(余白が比例して拡大・縮小されません)
- 配置を揃えられません(間隔がグリッドに揃いません)
修正方法:
- 必ず Tailwind の余白スケール(
0、1、2、3、4、6、8、12、16、24、32など)を使用します - よく使うパターンには余白システムを定義します:
- ボタンのパディング:
px-4 py-2 - カードのパディング:
p-6 - セクション間の間隔:
gap-8 - ネストされた要素の間隔:
ml-6(ml-3ではなく)
- ボタンのパディング:
- 関連する要素をグループ化し、間隔を統一します(すべての横並びリストで同じ
gap-xを使用します)
2. 色のコントラスト不足(アクセシビリティ違反)
問題: WCAG AA のコントラスト比(テキストは4.5:1、UI コンポーネントは3:1)を満たさない配色により、弱視のユーザーがコンテンツを読めなくなります。
// ❌ WRONG - gray-400 text on gray-100 background fails contrast
<p className="text-gray-400 bg-gray-100">Important content</p>
// ✅ CORRECT - gray-700 on gray-100 passes WCAG AA
<p className="text-gray-700 bg-gray-100">Important content</p>
うまくいかない理由:
- ユーザーの約4~5%は色覚異常があり、さらに広い範囲にあたる15%以上は何らかの視覚障害を抱えています
- 明るいディスプレイ(スマートフォン)では、コントラストの低いテキストを読めません
- 法的責任が生じる可能性があります(多くの管轄区域で WCAG への準拠が必須です)
修正方法:
- 十分なコントラストがある色の組み合わせを使用します:
- どのような背景でも
text-gray-900/text-white(常に安全です) - 明るい背景では
text-gray-700 - 暗い背景では
text-gray-300
- どのような背景でも
- 色をテストします: オンラインのコントラストチェッカーを使用します(https://webaim.org/resources/contrastchecker/)
- 問題のある組み合わせを避けます:
gray-100上のgray-400❌pink-100上のred-500❌- 無効状態:白地では
text-gray-400、色付き背景ではtext-gray-600を使用します
- グレー以外の色付きテキストの場合: より暗い色合いを使用します(
red-300ではなくred-700)
3. モバイルに不向きなレイアウト(小さなタッチターゲット、横スクロール)
問題: 44×44px未満のタッチターゲット、横スクロール、または狭いビューポートに適応しないレイアウトです。
// ❌ WRONG - button too small, no responsive layout
<button className="px-2 py-1 text-xs">Click me</button>
<div className="flex gap-2">
<div className="w-80">Sidebar</div>
<div className="w-80">Content</div>
</div>
// ✅ CORRECT - hit target size, responsive stack
<button className="px-4 py-3 md:px-6 md:py-3">Click me</button>
<div className="flex flex-col md:flex-row gap-4">
<div className="w-full md:w-80">Sidebar</div>
<div className="flex-1">Content</div>
</div>
うまくいかない理由:
- トラフィックの40%はモバイルからであり、小さなボタンはタップできません
- スマートフォンでの横スクロールは、UX を著しく損ないます
- デスクトップで機能するレイアウトが、モバイルでは使いものにならない場合があります
修正方法:
- タッチターゲットは最低44×44pxにします
- ボタン:少なくとも
px-4 py-3(標準:px-6 py-3) - リンク:クリック可能な領域を広く取ります
- ボタン:少なくとも
- モバイルでは縦に並べます: デフォルトでは
flex-colを使用し、デスクトップではmd:flex-rowを使用します - レスポンシブな幅を使用します:
- モバイルでは
w-full、デスクトップではmd:w-80またはmd:w-1/3を使用します - 柔軟なレイアウトでは、固定幅ではなく
flex-1を使用します
- モバイルでは
- レスポンシブ対応をテストします: 375px(iPhone SE)と768px(タブレット)のブレークポイントで確認します
- 横スクロールを避けます: ユーザーに横方向へのスクロールを強制してはいけません
4. 状態の欠落(読み込み中、エラー、空、無効)
問題: 正常な場合しか表示せず、読み込み中、エラー発生時、または空の場合に何も表示されないインターフェースです。
// ❌ WRONG - no feedback states
<button onClick={save}>Save</button>
<div>{data.map(item => <ItemCard item={item} />)}</div>
// ✅ CORRECT - all states accounted for
<button onClick={save} disabled={loading}>
{loading ? 'Saving...' : 'Save'}
</button>
{loading && <LoadingSkeleton />}
{error && <ErrorAlert message={error} />}
{!loading && data.length === 0 && <EmptyState action="Create your first item" />}
{!loading && !error && data.map(item => <ItemCard item={item} />)}
うまくいかない理由:
- ユーザーは操作が成功したかどうかわかりません(フィードバックがないと、失敗したと感じます)
- 空のリストは、意図的に空なのではなく、壊れているように見えます
- エラーからの復旧手段がありません
修正方法:
- 読み込み状態: スピナーではなく、構造を示すスケルトンを使用します
- コンテンツの読み込み中もレイアウトを安定させます
- 回転する表示だけでなく、これから表示される内容をユーザーに示します
- エラー状態: 失敗した操作の近くに表示します
- 何が起きたのか、どうすれば解決できるのかを説明します
- 再試行ボタンを含めます
- 空の状態: 意図を示し、操作につなげます
- 「まだ項目がありません」→「最初の項目を作成しましょう」(ボタン)
- 空の状態が何を意味するのか、どうすれば内容を追加できるのかを示します
- 無効状態: 必ず視覚的なフィードバックを提供します
disabled={true}とopacity-50またはcursor-not-allowedを組み合わせます- 理由を説明せず、何も知らせないまま無効にしてはいけません
5. 階層が不明瞭(すべてが同じくらい重要に見える)
問題: すべてのテキストが同じサイズで、すべての色の強さが同じで、すべての間隔が同じです。目立つものがありません。
// ❌ WRONG - flat hierarchy
<div className="text-lg text-gray-700">
<div className="text-lg text-gray-700">Subheading</div>
<div className="text-lg text-gray-700">Subtitle</div>
<button className="text-lg px-4 py-2">Action</button>
</div>
// ✅ CORRECT - clear hierarchy
<div>
<h1 className="text-3xl font-bold text-gray-900">Title</h1>
<h2 className="text-xl font-semibold text-gray-700 mt-6">Subheading</h2>
<p className="text-base text-gray-600 mt-2">Subtitle</p>
<button className="mt-6 px-6 py-3 bg-blue-600 text-white font-semibold rounded">
Primary Action
</button>
</div>
うまくいかない理由:
- ユーザーは最初にどこを見ればよいかわかりません
- ページをざっと見て把握するのに時間がかかります(流し読みできる構造がありません)
- 重要な操作が見逃されます
修正方法:
- サイズの階層:
- 見出し:
text-3xl(h1)→text-2xl(h2)→text-xl(h3) - 本文:
text-base(段落)→text-sm(補助テキスト)
- 見出し:
- ウェイトの階層:
- 主な操作:
font-boldまたはfont-semibold - 重要なテキスト:
font-semibold - 本文:
font-normal - 補助テキスト:より薄い色で
font-normal
- 主な操作:
- 色の階層:
- 主なテキスト:
text-gray-900 - 補助テキスト:
text-gray-600 - 第三階層:
text-gray-500
- 主なテキスト:
- 間隔の階層:
- セクション間:
gap-8(最大) - コンポーネント間:
gap-4 - 密度の高いコンテンツ:
gap-2
- セクション間:
- ビューごとにアクセントカラーを1色にする: CTA には単一の色を使い、複数の色が注意を奪い合わないようにします
クイックリファレンス
使用可能なアニメーションプロパティ
transform, opacity
使用禁止のアニメーションプロパティ
width, height, top, left, margin, padding, blur(), backdrop-filter
必須のアクセシビリティパターン
// Icon button - always add aria-label
<button aria-label="Close dialog">
<XIcon />
</button>
// Respect reduced motion
@media (prefers-reduced-motion: reduce) {
* { animation: none !important; }
}
クラスユーティリティのパターン
import { clsx } from 'clsx';
import { twMerge } from 'tailwind-merge';
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}
コメント
Markdown 対応