Skip to content

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] のカスタム値は使わない。

要素クラス
ページ h1text-xl font-bold text-slate-900 tracking-tight可視sr-only は不可)
セクション h2text-lg font-bold text-slate-900
サブセクション h3text-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

html
<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 だけで境界を表現する。

html
<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 で区切る。

html
<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 で配置する:

html
<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

html
<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

html
<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

html
<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 は使わない。

html
<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

html
<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

html
<label class="block text-xs font-bold text-slate-700 mb-1.5">勘定科目</label>

Tabs

Top-level tabs(main tabs)

html
<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

html
<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

html
<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

html
<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 で統一
  • 詳細・閲覧トリガー: FileTextEye / 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-3xlrounded-lg(ボタン/パネル)/ rounded-2xl(モーダル)

Reference pages

canonical の実装例:

  • frontend/src/views/dashboard/corporate/reconciliation/NewReconciliationPage.vue — split-pane / dense list
  • frontend/src/views/dashboard/corporate/shared/DocumentListPage.vue — table-based list + row actions
  • frontend/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 上で議論し、合意の上で更新する。

Butler Series — Saikoku Studio