結論
プロは入力欄の「安っぽさ」を、状態フィードバックと寸法・余白を具体数値で固めることで消す。ラベルはフィールド外に上揃え常時表示し、フォーカスは色だけでなく太さ+box-shadowで多層化してWCAG 3:1を満たす。高さ40〜56px・font-size 16px以上・8pxグリッドの一定リズムで「整っている=高品質」を作り、エラーは色単独でなくアイコン+テキスト+ARIAで多重化する。
01 — ラベルはフィールド外・上揃えで常時表示する
プレースホルダーをラベル代わりにすると、入力を始めた瞬間に何の欄か分からなくなり、薄いグレーはコントラスト不足でスクリーンリーダーにも届かない。ラベルはフィールド直上(top-aligned)に常時置くのが正解だ。Penzoのアイトラッキングでは視線移動(サッカード)が上揃え約50msに対し左揃えは約500msと、上揃えが最も速い。ラベルは1〜2語・センテンスケース("名")で。
primaryPlaceholders in Form Fields Are Harmful — NN/g
blogLabel Placement in Forms — UXmatters
02 — フォーカスは色だけでなく太さ+box-shadowで多層化する
キーボードユーザーにとってフォーカスインジケータはマウスカーソルに相当する唯一の位置手がかりだ。outline:none を代替なしで消すと操作不能になり、見た目も雑になる。WCAG SC1.4.11はボーダーもフォーカスも隣接背景に3:1以上を要求し、SC2.4.13(AAA)はフォーカス領域に2 CSS px厚の周長以上を求める。outline 3px+box-shadow 6px(白ハロー、厚みはoutlineの2倍)+ボーダー色変更の三層が汎用パターン。
blogA guide to designing accessible, WCAG-conformant focus indicators — Sara Soueidan
primaryUnderstanding SC 1.4.11: Non-text Contrast — W3C WAI
03 — 高さ・文字サイズ・余白を仕様値で固める
安っぽいフォームは入力欄の高さがバラバラで、ボタンとも揃わず縦のリズムが崩れている。高さは標準56px(密度高めで40〜48px)、入力/ラベル文字16px、font-sizeはmax(16px,1em)でiOS自動ズームを防ぐ。入力欄の高さは主ボタンの高さに揃え、フィールド間は最低16px。8pxグリッドの一定リズムこそが「整っている=高品質」の核だ。
primaryText fields — Components — Material Design
blogDesigning Form Layout: Spacing — SitePoint
04 — エラーは色+アイコン+テキスト+ARIAで多重化する
赤枠だけに頼ると、色覚多様性のユーザーに伝わらずWCAG非適合になる。aria-invalid="true" と aria-describedby でエラー文を関連付け、動的注入には role="alert" を付ける。検証は送信時の一括ではなくchange/blur時にフィールド近傍へインライン表示し、入力済みの誤り値は保持して修正させる。エラー本文テキストは4.5:1(枠線の3:1より高い)を確保する。
blogUltimate Guide to Accessible Form Design — UXPin
blogInput UI design: States, anatomy, and validation patterns — Setproduct
05 — 全状態を設計する(default/hover/focus/error/disabled)
安っぽいフォームは「のっぺり」していて、押せるのか・編集中なのかが分からない。プロはdefaultだけでなくhover/focus/filled/error/disabled/successまで明示的に色と太さを定義する。状態差が見えるだけで「作り込まれている」印象が一気に出る。disabledはWCAGコントラスト要件の対象外だが、視覚的に区別できるよう薄くする。
blogInput UI design: States, anatomy, and validation patterns — Setproduct
blogUI Designer's Guide to Creating Forms & Inputs — UI Prep
実装スニペット
ベースとなる入力欄(寸法・余白・iOSズーム防止)。
.field { display: flex; flex-direction: column; gap: 8px; }
.field label { font-size: 16px; line-height: 1.4; font-weight: 600; }
.input {
box-sizing: border-box;
width: 100%;
min-height: 40px; /* 主ボタン高さに揃える(8pxグリッド) */
padding: 8px 12px;
font-size: max(16px, 1em); /* iOS自動ズーム防止 */
line-height: 1.25;
border: 2px solid #6b7280; /* 隣接背景に3:1以上を確保 */
border-radius: 4px;
background: #fff;
transition: border-color 180ms ease-in-out, box-shadow 180ms ease-in-out;
}
.input::placeholder { color: #6b7280; } /* ラベル代わりにはしない・補助のみ */
.field + .field { margin-top: 16px; } /* フィールド間 最低16px */
フォーカス可視化(WCAG 1.4.11 / 2.4.13 準拠の多層)。:focus-visible でキーボード操作時のみ強いリングを出す。
.input:focus-visible {
outline: 3px solid #1a1a1a; /* 3px outline */
outline-offset: 0;
box-shadow: 0 0 0 6px #fff; /* ハロー幅 = outlineの2倍 */
border-color: #1d4ed8; /* 色も変える(色のみに頼らない) */
}
/* outline:none は単独で使わない。使うなら必ず上記box-shadow等で代替 */
エラー状態(色のみに頼らない・ARIA前提)。
.input[aria-invalid="true"] {
border-color: #b91c1c; /* 赤枠(3:1以上) */
border-width: 2px;
}
.error-text {
display: flex; align-items: center; gap: 6px;
margin-top: 8px;
color: #b91c1c; /* テキストは4.5:1以上 */
font-size: 12px; line-height: 1.4;
}
.error-text::before { content: "\26A0"; } /* アイコンを併用 */
/* HTML:
<input class="input" aria-invalid="true" aria-describedby="email-err">
<p id="email-err" class="error-text" role="alert">有効なメールアドレスを入力してください</p>
*/
全状態の定義(default/hover/filled/disabled/success)。
.input:hover { border-color: #374151; }
.input:not(:placeholder-shown) { border-color: #374151; } /* filled */
.input:disabled {
background: #f3f4f6; color: #9ca3af;
border-color: #e5e7eb; cursor: not-allowed;
}
/* disabledはWCAGコントラスト要件の対象外。だが識別可能に薄める */
.input--success { border-color: #15803d; }
チェックリスト
- ラベルをフィールド外・上揃えで常時表示し、プレースホルダーをラベル代わりにしていない
- font-sizeは `max(16px, 1em)` 以上で、iOSフォーカス時の自動ズームを防いでいる
- 入力欄の高さ(40〜56px)が主ボタンの高さに揃い、フィールド間は最低16px空いている
- 余白が8pxグリッドの一定リズムで、単一カラムで下方向に流れている
- `:focus-visible` で outline+box-shadow+ボーダー色の多層フォーカスを出し、隣接背景に3:1以上ある
- `outline:none` を代替なしで使っていない
- エラーを赤枠だけでなくアイコン+テキストで示し、本文は4.5:1を満たす
- `aria-invalid` / `aria-describedby` を付与し、動的エラーに `role="alert"` を使っている
- 検証はchange/blurでインライン表示し、誤り値は保持して修正させている
- default/hover/focus/filled/error/disabledまで各状態を視覚的に定義している
- 郵便番号・電話・カード番号など長さが決まる項目は幅を絞り、想定入力長を示している
限界 / 出典
primaryPlaceholders in Form Fields Are Harmful — NN/g
primaryWebsite Forms Usability: Top 10 Recommendations — NN/g
primaryUnderstanding SC 1.4.11: Non-text Contrast — W3C WAI
blogA guide to designing accessible, WCAG-conformant focus indicators — Sara Soueidan
primaryText fields — Components — Material Design
blogCustom CSS Styles for Form Inputs and Textareas — Modern CSS
blogUltimate Guide to Accessible Form Design — UXPin
blogInput UI design: States, anatomy, and validation patterns — Setproduct
blogLabel Placement in Forms — UXmatters
blogDesigning Form Layout: Spacing — SitePoint
blogUI Designer's Guide to Creating Forms & Inputs — UI Prep
blogThe Definitive Guide to Form Label Positioning — SitePoint