Design System
Butler Series で UI を作るときの共通ルール。新しいページ・コンポーネントを作るときは必ず参照してください。
Why this exists
ページごとに異なるパレット・タイポグラフィ・レイアウトパターンが混在すると「同じアプリに見えない」「フレームワークがバラバラ」と誤解される。実際は全ページ Tailwind で書かれていても、トークン選択が違うだけで視覚的に大きく分かれる。今後そうならないために、canonical なトークンを以下に固定する。
Design direction
密度重視・power user 向けデータアプリケーションスタイルを採用する。
| ターゲット | 経理担当・税理士(毎日数百件処理する power user) |
| 参考 | Linear / Vercel / Notion / Stripe Dashboard |
| 反対の方向(採用しない) | カードベース・大フォント・余白多め(freee / Salesforce 系) |
ただし密度の追求は アクセシビリティ AA を下回らない範囲 に留める(後述の最小フォントサイズなど)。
Color tokens
Grayscale
slate-* を使う。gray-* は使わない。
| 用途 | クラス |
|---|---|
| ページ背景 | bg-slate-50 |
| カード/ヘッダー背景 | bg-white |
| 標準ボーダー | border-slate-200 |
| 淡いボーダー | border-slate-100 |
| ボディテキスト | text-slate-700 |
| 補足テキスト | text-slate-500 |
| プライマリ見出し | text-slate-900 |
| 無効/プレースホルダ | text-slate-400 |
Semantic colors
| 意味 | クラス |
|---|---|
| プライマリアクション | blue-600(hover: blue-700、bg light: blue-50、text light: blue-700) |
| 成功 / 入金 / 確定 | emerald-*(バナー: bg-emerald-50 border-emerald-200 text-emerald-800、入金金額: text-emerald-700) |
| 警告 / 自動候補 | amber-*(バナー: bg-amber-50 border-amber-200 text-amber-800、バッジ: bg-amber-50 text-amber-700) |
| エラー / 出金 / 削除 | rose-*(出金金額: text-rose-700、バッジ: bg-rose-50 text-rose-700) |
indigo, purple, green, red, gray は使わない(用途が重複し統一感が崩れる)。
Typography
最小フォントサイズは text-xs(12px)。text-[10px] や text-[11px] のカスタム値は使わない。
| 要素 | クラス |
|---|---|
| ページ h1 | text-xl font-bold text-slate-900 tracking-tight(可視、sr-only は不可) |
| セクション h2 | text-lg font-bold text-slate-900 |
| サブセクション h3 | text-sm font-bold text-slate-700 |
| 小キャップス・列ラベル | text-xs font-bold text-slate-500 uppercase tracking-wide |
| ボディテキスト | text-sm text-slate-700 |
| 補足テキスト | text-xs text-slate-500 |
Font weight
- 見出し・ボタン・ラベル:
font-bold - ボディ・補足: 通常(指定なし)
font-mediumは 使わない(中途半端でメリハリが失われる)
Layout
Page shell
<div class="h-full flex flex-col bg-slate-50">
<header class="bg-white border-b border-slate-200 px-8 py-5 shrink-0 z-10">
<h1 class="text-xl font-bold text-slate-900 tracking-tight">ページタイトル</h1>
</header>
<main class="flex-1 overflow-y-auto p-8">
<!-- セクションカードを配置 -->
</main>
</div>Section panel (flat, no shadow)
セクションを区切る要素は フラット で。shadow-sm を付けて浮かせる「カード型」は使わない。border + rounded-lg だけで境界を表現する。
<section class="bg-white border border-slate-200 rounded-lg overflow-hidden">
<div class="px-6 py-3 border-b border-slate-200 flex items-center gap-2">
<h2 class="text-lg font-bold text-slate-900">セクション名</h2>
</div>
<div class="p-6 space-y-6">
<!-- 中身 -->
</div>
</section>Why flat over cards: Butler Series はデータアプリ寄りの方向性のため、ページ全体に shadow-sm カードを並べると視覚ノイズが多くなり「freee 風の古典 SaaS」っぽく見える。Linear / Vercel / Stripe Dashboard は同様にカードを多用しない。
Split-pane(消込・照合系)
データ密度が高い画面はスプリットペインで OK。セクションパネルすら使わず border-r border-slate-200 で区切る。
<div class="flex-1 flex overflow-hidden">
<div class="flex-1 flex flex-col bg-white border-r border-slate-200">
<!-- 左カラム -->
</div>
<div class="flex-1 flex flex-col bg-white">
<!-- 右カラム -->
</div>
</div>Page main content wrapper
カードと違って、フォーム系ページのメインコンテンツ領域は max-w で中央寄せしない。edge-to-edge で配置する:
<main class="flex-1 overflow-y-auto bg-slate-50">
<div class="space-y-6 px-8 py-6">
<!-- セクションパネル(複数)-->
</div>
</main>max-w-[1400px] mx-auto のような幅制限は使わない(過去に使われていたが #115 でフラット統一の際に廃止)。
Buttons
Primary
<button class="px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white font-bold rounded-lg disabled:opacity-30 disabled:cursor-not-allowed transition-colors">
保存する
</button>Secondary
<button class="px-3 py-1.5 bg-white border border-slate-300 text-slate-700 hover:bg-slate-50 font-medium rounded-lg transition-colors">
キャンセル
</button>Destructive
<button class="px-3 py-1.5 border border-slate-300 text-slate-600 hover:border-rose-300 hover:text-rose-600 hover:bg-rose-50 rounded-lg transition-colors">
削除
</button>Row inline detail (eye / memo)
行内の「詳細」アイコンは FileText を使う。Eye は使わない。
<button class="p-1.5 text-blue-600 bg-blue-50 hover:bg-blue-100 rounded-lg transition-colors" :title="$t('common.detail')">
<FileText :size="15" />
</button>Forms
Input
<input
type="text"
class="w-full border border-slate-300 rounded-lg px-3 py-2 text-sm focus:outline-none focus:ring-1 focus:ring-blue-500 focus:border-blue-500"
/>Label
<label class="block text-xs font-bold text-slate-700 mb-1.5">勘定科目</label>Tabs
Top-level tabs(main tabs)
<button :class="['px-4 py-2 text-sm transition-colors flex items-center gap-2 border-b-2',
active ? 'text-slate-900 border-blue-600 font-bold' : 'text-slate-500 border-transparent hover:text-slate-700']">
タブ名
<span class="inline-flex items-center justify-center min-w-[18px] h-[18px] px-1 text-xs font-bold rounded-full bg-rose-50 text-rose-700">{{ count }}</span>
</button>Sub-tabs / pill toggle
<div class="inline-flex bg-slate-100 rounded-lg p-0.5 border border-slate-200">
<button :class="['px-3 py-1.5 text-sm font-bold rounded-md transition-colors',
active ? 'bg-white text-slate-900 shadow-sm' : 'text-slate-500 hover:text-slate-700']">
オプション
</button>
</div>Cards / list rows
<div :class="['border rounded-md p-2.5 cursor-pointer transition-all bg-white',
selected ? 'border-blue-500 bg-blue-50/40 ring-1 ring-blue-500' : 'border-slate-200 hover:border-slate-300']">
<!-- 中身 -->
</div>選択状態は border-blue-500 bg-blue-50/40 ring-1 ring-blue-500 で統一。
Modals
<div v-if="open" class="fixed inset-0 z-50 flex items-center justify-center p-4">
<div class="absolute inset-0 bg-slate-900/50" @click="close"></div>
<div class="bg-white rounded-2xl shadow-xl w-full max-w-md relative z-10 overflow-hidden">
<div class="px-6 py-4 border-b border-slate-200 flex items-center justify-between">
<h2 class="text-base font-bold text-slate-900">モーダルタイトル</h2>
<button @click="close" class="text-slate-400 hover:text-slate-600">
<X class="w-5 h-5" />
</button>
</div>
<div class="p-6 space-y-4">
<!-- 中身 -->
</div>
<div class="px-6 py-3 border-t border-slate-200 bg-slate-50 flex justify-end gap-2">
<!-- ボタン -->
</div>
</div>
</div>Iconography
- ライブラリ:
lucide-vue-nextで統一 - 詳細・閲覧トリガー:
FileText(Eye/Infoは使わない) - 削除:
Trash2 - 編集:
Pencil - 追加:
Plus - 閉じる:
X - 展開/折り畳み:
ChevronDown/ChevronUp - リンク・紐付け:
Link2 - 成功・確定:
CheckCircleまたはCheckCircle2
サイズは基本 w-4 h-4(16px)または :size="15"。インラインの小アイコンは w-3.5 h-3.5。
Border radius
| 用途 | クラス |
|---|---|
| バッジ・チップ | rounded-full |
| インプット・ボタン・セクションパネル | rounded-lg |
| モーダル | rounded-2xl |
| インライン要素(行内ボタン等) | rounded または rounded-md |
| ピル型トグルの内側ボタン | rounded-md |
rounded-xl / rounded-3xl は使わない。rounded-2xl はモーダル専用。
Shadow
| 用途 | クラス |
|---|---|
| セクションパネル | 使わない(border で境界を表現) |
| モーダル | shadow-xl |
| 浮き上がり要素(ドロップダウン・トースト等) | shadow-lg |
| 固定ヘッダー | 使わない |
スプリットペインの分割線にはシャドウを使わず border-r border-slate-200 で表現する。ページ全体は基本フラットで、Z 軸を強調するのはモーダルとオーバーレイのみ。
Accessibility
- 色のコントラスト比は WCAG AA を満たすこと
text-slate-400 on bg-whiteは補足テキスト用途のみ、本文には使わない- 重要な情報を色だけで表現しない(テキスト・アイコンと組み合わせる)
- インタラクティブ要素には
:titleまたは:aria-labelを付ける
Anti-patterns(やってはいけない)
| ❌ | ✅ |
|---|---|
text-[10px] text-[11px] | text-xs(最小) |
gray-* の混在使用 | slate-* 統一 |
font-medium を見出しに | font-bold |
| カード+大フォント+空白多めの古典 SaaS スタイル | 密度重視のデータアプリスタイル |
Eye を「詳細」に | FileText(メモアイコン) |
indigo/green/red/gray パレット | blue/emerald/rose/slate に統一 |
<h1 class="sr-only"> で見出し非表示 | 見出しは可視(ヘッダーに置く) |
| インラインスタイル直書き | Tailwind ユーティリティのみ |
セクションに shadow-sm カード | フラット border rounded-lg(カードは entity 単位のみ) |
max-w-[1400px] mx-auto で幅制限 | edge-to-edge(px-8 で左右パディング) |
ヘッダーに sticky shadow-sm | フラットヘッダー(Z 軸はモーダルだけ) |
rounded-xl / rounded-3xl | rounded-lg(ボタン/パネル)/ rounded-2xl(モーダル) |
Reference pages
canonical の実装例:
frontend/src/views/dashboard/corporate/reconciliation/NewReconciliationPage.vue— split-pane / dense listfrontend/src/views/dashboard/corporate/shared/DocumentListPage.vue— table-based list + row actionsfrontend/src/views/dashboard/corporate/invoices/InvoiceCreatePage.vue— form-heavy create page
新ページを作るときは、まず似たパターンの reference page を開いてコピー元にする。
Updating this doc
このドキュメントは Butler Series 全体(butler-tax / butler-law / butler-platform)の UI 全体に適用される。変更したいときは PR 上で議論し、合意の上で更新する。
