メインコンテンツまでスキップ

コンポーネントパターン

この章では、実際のプロジェクトで使える再利用可能な UI コンポーネントのパターンを学びます。

この章は「スタイリング」に focus

この章は Tailwind CSS による スタイリング に焦点を当てています。モーダルの開閉やアコーディオンの展開など、インタラクティブな動作には JavaScript (またはフレームワーク) が必要です。主要なコンポーネントには基本的な JavaScript 実装例も記載しています。

ボタン

基本ボタン

<!-- プライマリ -->
<button class="px-4 py-2 bg-blue-500 text-white font-medium rounded-lg
hover:bg-blue-600 focus:ring-4 focus:ring-blue-300
transition-colors">
プライマリ
</button>

<!-- セカンダリ -->
<button class="px-4 py-2 bg-gray-200 text-gray-800 font-medium rounded-lg
hover:bg-gray-300 focus:ring-4 focus:ring-gray-200
transition-colors">
セカンダリ
</button>

<!-- アウトライン -->
<button class="px-4 py-2 border-2 border-blue-500 text-blue-500 font-medium rounded-lg
hover:bg-blue-50 focus:ring-4 focus:ring-blue-200
transition-colors">
アウトライン
</button>

<!-- ゴースト -->
<button class="px-4 py-2 text-blue-500 font-medium rounded-lg
hover:bg-blue-50 focus:ring-4 focus:ring-blue-200
transition-colors">
ゴースト
</button>

サイズバリエーション

<!-- Small -->
<button class="px-3 py-1.5 text-sm bg-blue-500 text-white rounded-md">
Small
</button>

<!-- Medium -->
<button class="px-4 py-2 text-base bg-blue-500 text-white rounded-lg">
Medium
</button>

<!-- Large -->
<button class="px-6 py-3 text-lg bg-blue-500 text-white rounded-xl">
Large
</button>

ボタングループ

<div class="inline-flex rounded-lg overflow-hidden">
<button class="px-4 py-2 bg-blue-500 text-white border-r border-blue-400">

</button>
<button class="px-4 py-2 bg-blue-500 text-white border-r border-blue-400">
中央
</button>
<button class="px-4 py-2 bg-blue-500 text-white">

</button>
</div>

アイコン付きボタン

<button class="inline-flex items-center gap-2 px-4 py-2 bg-blue-500 text-white rounded-lg">
<svg class="w-5 h-5">...</svg>
<span>ダウンロード</span>
</button>

<!-- アイコンのみ -->
<button class="p-2 bg-blue-500 text-white rounded-lg">
<svg class="w-5 h-5">...</svg>
</button>

カード

基本カード

<div class="bg-white rounded-xl shadow-lg overflow-hidden">
<img src="..." class="w-full h-48 object-cover" />
<div class="p-6">
<span class="text-sm text-blue-600 font-semibold">カテゴリ</span>
<h3 class="mt-2 text-xl font-bold text-gray-900">カードタイトル</h3>
<p class="mt-2 text-gray-600">
説明文がここに入ります...
</p>
<button class="mt-4 px-4 py-2 bg-blue-500 text-white rounded-lg w-full">
詳細を見る
</button>
</div>
</div>

水平カード

<div class="flex bg-white rounded-xl shadow-lg overflow-hidden">
<img src="..." class="w-1/3 object-cover" />
<div class="flex-1 p-6">
<h3 class="text-xl font-bold">タイトル</h3>
<p class="mt-2 text-gray-600">説明文...</p>
</div>
</div>

インタラクティブカード

<div class="group bg-white rounded-xl shadow-lg overflow-hidden
hover:shadow-xl transition-shadow cursor-pointer">
<div class="relative">
<img src="..." class="w-full h-48 object-cover
group-hover:scale-105 transition-transform duration-300" />
<div class="absolute inset-0 bg-linear-to-t from-black/50 to-transparent
opacity-0 group-hover:opacity-100 transition-opacity"></div>
</div>
<div class="p-6">
<h3 class="text-xl font-bold group-hover:text-blue-500 transition-colors">
タイトル
</h3>
<p class="mt-2 text-gray-600">説明文...</p>
</div>
</div>

フォーム要素

テキスト入力

<div>
<label class="block text-sm font-medium text-gray-700 mb-1">
ラベル
</label>
<input
type="text"
class="w-full px-4 py-2 border border-gray-300 rounded-lg
focus:ring-2 focus:ring-blue-500 focus:border-transparent
placeholder:text-gray-400"
placeholder="プレースホルダー"
/>
<p class="mt-1 text-sm text-gray-500">ヘルプテキスト</p>
</div>

エラー状態

<div>
<label class="block text-sm font-medium text-gray-700 mb-1">
メールアドレス
</label>
<input
type="email"
class="w-full px-4 py-2 border border-red-500 rounded-lg
focus:ring-2 focus:ring-red-500 focus:border-transparent
text-red-900 placeholder:text-red-300 bg-red-50"
placeholder="example@email.com"
/>
<p class="mt-1 text-sm text-red-500">有効なメールアドレスを入力してください</p>
</div>

セレクト

<select class="w-full px-4 py-2 border border-gray-300 rounded-lg
focus:ring-2 focus:ring-blue-500 focus:border-transparent
bg-white appearance-none
bg-[url('data:image/svg+xml,...')] bg-no-repeat bg-position-[right_1rem_center]">
<option>オプション 1</option>
<option>オプション 2</option>
<option>オプション 3</option>
</select>

チェックボックス

<label class="flex items-center gap-3 cursor-pointer">
<input
type="checkbox"
class="w-5 h-5 accent-blue-500"
/>
<span class="text-gray-700">利用規約に同意する</span>
</label>

ラジオボタン

<div class="space-y-3">
<label class="flex items-center gap-3 cursor-pointer">
<input
type="radio"
name="plan"
class="w-5 h-5 accent-blue-500"
/>
<span class="text-gray-700">ベーシックプラン</span>
</label>
<label class="flex items-center gap-3 cursor-pointer">
<input
type="radio"
name="plan"
class="w-5 h-5 accent-blue-500"
/>
<span class="text-gray-700">プロプラン</span>
</label>
</div>

トグルスイッチ

<label class="relative inline-flex items-center cursor-pointer">
<input type="checkbox" class="sr-only peer" />
<div class="w-11 h-6 bg-gray-200 rounded-full
peer-focus:ring-4 peer-focus:ring-blue-300
peer-checked:bg-blue-500
after:content-[''] after:absolute after:top-0.5 after:left-0.5
after:w-5 after:h-5 after:bg-white after:rounded-full
after:transition-all peer-checked:after:translate-x-5"></div>
<span class="ml-3 text-gray-700">通知を有効にする</span>
</label>

ナビゲーション

ヘッダー

<header class="bg-white shadow-sm sticky top-0 z-50">
<nav class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div class="flex justify-between h-16">
<!-- ロゴ -->
<div class="flex items-center">
<a href="/" class="font-bold text-xl text-gray-900">Logo</a>
</div>

<!-- デスクトップメニュー -->
<div class="hidden md:flex items-center gap-8">
<a href="#" class="text-gray-600 hover:text-gray-900">Home</a>
<a href="#" class="text-gray-600 hover:text-gray-900">Products</a>
<a href="#" class="text-gray-600 hover:text-gray-900">About</a>
<a href="#" class="text-gray-600 hover:text-gray-900">Contact</a>
</div>

<!-- アクションボタン -->
<div class="flex items-center gap-4">
<button class="px-4 py-2 text-gray-600 hover:text-gray-900">
ログイン
</button>
<button class="px-4 py-2 bg-blue-500 text-white rounded-lg hover:bg-blue-600">
サインアップ
</button>
</div>
</div>
</nav>
</header>

タブ

<div class="border-b border-gray-200">
<nav class="flex gap-8">
<a href="#" class="py-4 px-1 border-b-2 border-blue-500 text-blue-600 font-medium">
タブ 1
</a>
<a href="#" class="py-4 px-1 border-b-2 border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300">
タブ 2
</a>
<a href="#" class="py-4 px-1 border-b-2 border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300">
タブ 3
</a>
</nav>
</div>

パンくずリスト

<nav class="flex items-center gap-2 text-sm">
<a href="#" class="text-gray-500 hover:text-gray-700">ホーム</a>
<span class="text-gray-400">/</span>
<a href="#" class="text-gray-500 hover:text-gray-700">カテゴリ</a>
<span class="text-gray-400">/</span>
<span class="text-gray-900 font-medium">現在のページ</span>
</nav>

ページネーション

<nav class="flex items-center justify-center gap-1">
<button class="px-3 py-2 text-gray-500 hover:bg-gray-100 rounded-lg disabled:opacity-50" disabled>
前へ
</button>
<button class="px-4 py-2 bg-blue-500 text-white rounded-lg">1</button>
<button class="px-4 py-2 text-gray-700 hover:bg-gray-100 rounded-lg">2</button>
<button class="px-4 py-2 text-gray-700 hover:bg-gray-100 rounded-lg">3</button>
<span class="px-2">...</span>
<button class="px-4 py-2 text-gray-700 hover:bg-gray-100 rounded-lg">10</button>
<button class="px-3 py-2 text-gray-700 hover:bg-gray-100 rounded-lg">
次へ
</button>
</nav>

フィードバック

アラート

<!-- 情報 -->
<div class="flex items-start gap-3 p-4 bg-blue-50 border-l-4 border-blue-500 rounded-r-lg">
<svg class="w-5 h-5 text-blue-500 shrink-0 mt-0.5">...</svg>
<div>
<h4 class="font-medium text-blue-800">情報</h4>
<p class="mt-1 text-sm text-blue-700">お知らせ内容がここに入ります。</p>
</div>
</div>

<!-- 成功 -->
<div class="flex items-start gap-3 p-4 bg-green-50 border-l-4 border-green-500 rounded-r-lg">
<svg class="w-5 h-5 text-green-500 shrink-0 mt-0.5">...</svg>
<div>
<h4 class="font-medium text-green-800">成功</h4>
<p class="mt-1 text-sm text-green-700">操作が完了しました。</p>
</div>
</div>

<!-- 警告 -->
<div class="flex items-start gap-3 p-4 bg-yellow-50 border-l-4 border-yellow-500 rounded-r-lg">
<svg class="w-5 h-5 text-yellow-500 shrink-0 mt-0.5">...</svg>
<div>
<h4 class="font-medium text-yellow-800">警告</h4>
<p class="mt-1 text-sm text-yellow-700">注意が必要です。</p>
</div>
</div>

<!-- エラー -->
<div class="flex items-start gap-3 p-4 bg-red-50 border-l-4 border-red-500 rounded-r-lg">
<svg class="w-5 h-5 text-red-500 shrink-0 mt-0.5">...</svg>
<div>
<h4 class="font-medium text-red-800">エラー</h4>
<p class="mt-1 text-sm text-red-700">問題が発生しました。</p>
</div>
</div>

バッジ

<span class="px-2.5 py-0.5 text-xs font-semibold bg-blue-100 text-blue-800 rounded-full">
新着
</span>
<span class="px-2.5 py-0.5 text-xs font-semibold bg-green-100 text-green-800 rounded-full">
完了
</span>
<span class="px-2.5 py-0.5 text-xs font-semibold bg-yellow-100 text-yellow-800 rounded-full">
保留
</span>
<span class="px-2.5 py-0.5 text-xs font-semibold bg-red-100 text-red-800 rounded-full">
緊急
</span>

ツールチップ

<div class="relative group">
<button class="px-4 py-2 bg-gray-800 text-white rounded-lg">
ホバーしてね
</button>
<div class="absolute bottom-full left-1/2 -translate-x-1/2 mb-2
px-3 py-1 bg-gray-900 text-white text-sm rounded
opacity-0 group-hover:opacity-100 transition-opacity
pointer-events-none whitespace-nowrap">
ツールチップテキスト
<div class="absolute top-full left-1/2 -translate-x-1/2
border-4 border-transparent border-t-gray-900"></div>
</div>
</div>

モーダル

<!-- オーバーレイ -->
<div class="fixed inset-0 z-50 flex items-center justify-center p-4
bg-black/50 backdrop-blur-sm">
<!-- モーダル本体 -->
<div class="bg-white rounded-xl shadow-2xl max-w-lg w-full max-h-[90vh] overflow-y-auto">
<!-- ヘッダー -->
<div class="flex items-center justify-between p-6 border-b border-gray-200">
<h2 class="text-xl font-bold">モーダルタイトル</h2>
<button class="p-1 text-gray-400 hover:text-gray-600 rounded">
<svg class="w-6 h-6">...</svg>
</button>
</div>

<!-- 本文 -->
<div class="p-6">
<p class="text-gray-600">モーダルの内容がここに入ります。</p>
</div>

<!-- フッター -->
<div class="flex justify-end gap-3 p-6 border-t border-gray-200 bg-gray-50 rounded-b-xl">
<button class="px-4 py-2 text-gray-700 hover:bg-gray-100 rounded-lg">
キャンセル
</button>
<button class="px-4 py-2 bg-blue-500 text-white hover:bg-blue-600 rounded-lg">
確認
</button>
</div>
</div>
</div>

モーダルの開閉

CSS だけではモーダルの表示切り替えはできません。以下はバニラ JavaScript での実装例です。

<button id="open-modal" class="px-4 py-2 bg-blue-500 text-white rounded-lg hover:bg-blue-600">
モーダルを開く
</button>

<!-- オーバーレイ (初期状態は非表示) -->
<div id="modal-overlay" class="fixed inset-0 z-50 hidden flex items-center justify-center p-4
bg-black/50 backdrop-blur-sm">
<div class="bg-white rounded-xl shadow-2xl max-w-lg w-full">
<!-- ...モーダル本体 (上記と同じ構造)... -->
</div>
</div>

<script>
const openBtn = document.getElementById('open-modal');
const overlay = document.getElementById('modal-overlay');
const closeBtn = overlay.querySelector('button');

openBtn.addEventListener('click', () => {
overlay.classList.remove('hidden');
});

// オーバーレイクリックで閉じる
overlay.addEventListener('click', (e) => {
if (e.target === overlay) {
overlay.classList.add('hidden');
}
});

// 閉じるボタン
closeBtn.addEventListener('click', () => {
overlay.classList.add('hidden');
});
</script>

アクセシビリティ

コンポーネントを実装する際は、適切な ARIA 属性を付与しましょう。

<!-- モーダル -->
<div role="dialog" aria-modal="true" aria-labelledby="modal-title">
<h2 id="modal-title">モーダルタイトル</h2>
...
</div>

<!-- アラート -->
<div role="alert" class="bg-red-50 border border-red-200 text-red-800 p-4 rounded-lg">
エラーが発生しました
</div>

<!-- アイコンボタン -->
<button aria-label="閉じる" class="p-1 text-gray-400 hover:text-gray-600 rounded">
<svg class="w-6 h-6">...</svg>
</button>

まとめ

  • ボタン: サイズ、バリアント、状態を組み合わせ
  • カード: 画像、コンテンツ、アクションの構成
  • フォーム: ラベル、入力、バリデーション状態
  • ナビゲーション: ヘッダー、タブ、ページネーション
  • フィードバック: アラート、バッジ、モーダル
  • 一貫したスペーシングと色使いが重要
  • アクセシビリティ: ARIA 属性の適切な付与

次に読む