defaults について
Defaultsは、サーフェス、ナビゲーション、フォーム、読み込み、アクセシビリティなど、画面に共通するUI規則を定めるスキルです。開発者が一貫した動作を実装するための具体的な確認項目と指針を示します。
主な機能
- 01サーフェス・影・スクロールの規則
- 02フォーカス・キーボード・タッチの指針
- 03フォーム入力と検証の規約
- 04読み込みと更新通知のパターン
- 05レスポンシブ対応とアクセシビリティの確認
活用シーン
- 01共通UI規則の監査
- 02フォームと読み込み状態の確認
- 03キーボード・タッチ・フォーカス動作の確認
- 04長いコンテンツによるレイアウト崩れの予防
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ playful-design/ router --skill defaultsインストール後、Claude Code で/defaultsを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
LICENSErecipes.mdagents/openai.yaml詳細
デフォルト
このスキルでは、すべての画面に適用されるインターフェースのデフォルトを定めます。サーフェス、文字、サウンド、フォーカス、フォーム、読み込み、オーバーレイを扱います。
まずトークンを使い、その後にこれらの値を使う
以下の値にプロジェクトのトークンがある場合は、それを使用してください。該当するトークンがない場合は、ここに示す値を厳守してください。スクロールが突然途切れたり、シャドウが切れたりするスタイルは、いずれも指摘対象です。
言葉や句読点は copy に属します。これらをプロジェクトのプリミティブで構築するかどうかは reuse に属します。サウンドやアニメーションをそもそも設けるべきかどうかは restraint に属します。動きは motion に、色とコントラストは color に属します。同心円状の角丸、光学的な位置調整、アイコンの配置は alignment に属します。
スクロール領域の端をフェードさせる
スクロールコンテナの端は、mask-image のグラデーションでフェードさせます。オーバーフロー領域の端が突然切れる場合は defaults-fade-not-cut です。実装方法は recipes.md を参照してください。
シャドウを決して切らない
子要素のシャドウがコンテナ内に収まるよう、スクローラーに十分なパディングを設けてください。overflow: hidden によってシャドウが切れる場合は defaults-no-clipped-shadow です。recipes.md を参照してください。
コードとテーブルは折り返さずにスクロールする
コードブロックとデータテーブルは、スクロールバーを非表示にして横方向にスクロールさせます。コード行が折り返される場合は defaults-scroll-not-wrap です。recipes.md を参照してください。
オーバーレイ内のスクロールを保つ
スクロール可能なダイアログ、ドロワー、メニュー、サイドパネルには overscroll-behavior: contain を設定します。モーダル内のリストの末尾までスクロールすると背後のページがスクロールする場合は defaults-contain-overscroll です。
スティッキー要素の縁を保つ
スティッキーヘッダーまたはフッターには、その下でスクロールするコンテンツとの境界に 1px のボーダーを付けます。ボーダーのないスティッキーヘッダーは defaults-sticky-border です。
scroll-padding-block-start をスティッキーヘッダーの高さに設定し、アンカーやフォーカスされたフィールドがヘッダーの下に表示されるようにします。フォーカスされたフィールドがヘッダーの下に隠れる場合は defaults-clear-sticky-chrome です。
フローティングサーフェスにはボーダーではなくリングを使う
ポップオーバー、メニュー、カードなどのフローティング要素では、シャドウと、シャドウスタックの内側に置く半透明の 1px リングを組み合わせます。シャドウの横に不透明な border がある場合は defaults-ring-not-border です。強制カラーモードでも縁が描画されるよう、リングの下には border: 1px solid transparent を残してください。
シャドウは重ね、下方向に落とす
プロジェクトのエレベーショントークンを使用します。ない場合は、ぼかしを大きく、透明度を低くしながら、下方向にずらしたシャドウを 3 つ重ねます。0 4px 16px rgba(0, 0, 0, 0.2) が 1 つだけの場合は defaults-layered-shadows です。recipes.md を参照してください。
ホバー時にサーフェスを浮き上がらせるには、浮き上がったシャドウを持つ疑似要素をフェードインさせます。box-shadow にトランジションを設定する場合は defaults-fade-the-shadow です。recipes.md を参照してください。
コンテンツ画像には薄いアウトラインを付ける
写真やスクリーンショットには、内側に 1px 入れた 1px のアウトラインを付けます。ライトモードでは黒の 10%、ダークモードでは白の 10% にします。写真の縁がカードになじんで見えなくなる場合は defaults-image-outline です。ロゴや透明なアートワークには適用しません。recipes.md を参照してください。
カードを入れ子にしない
カードを別のカードの中に配置してはいけません。見出しは外に置き、1 つのサーフェスでコンテンツをまとめます。カードの中にカードがある場合は defaults-no-nested-cards です。
ピル型ではなく角丸にする
ボタンとチップには角丸トークンを使用します。border-radius: 9999px は defaults-rounded-not-pill です。
装飾を加えない
装飾的なグラデーション、グロー、ぼかし、奥行き効果を取り除きます。カードの背後にグローがある場合は defaults-no-decoration です。
テーマをブラウザーにも反映する
ダークテーマでは、スクロールバーとネイティブコントロールにもテーマが適用されるよう、ルートに color-scheme: dark を設定します。ダークページに明るいスクロールバーが表示される場合は defaults-color-scheme です。ページ上部の色に合わせ、配色ごとに theme-color タグを 1 つ設定します。
テーマを切り替える際は、1 フレームだけトランジションを無効にします。テーマ変更時にすべての色が同時にフェードする場合は defaults-quiet-theme-switch です。recipes.md を参照してください。
キーボード操作時のみフォーカスリングを表示する
outline とともに :focus-visible を使用します。クリックするたびにリングが表示される場合は defaults-focus-keyboard-only です。
ダイアログ内にフォーカスを保つ
ダイアログを開くとフォーカスが内部に移動し、Tab キーで内部を移動できるように保たれ、閉じるとトリガーにフォーカスが戻ります。ダイアログを開いている間、背後のページにフォーカスが残る状態は defaults-trap-and-return-focus です。showModal() で開いたネイティブの <dialog> はフォーカストラップを処理します。
ターゲットは少なくとも 24px
すべてのインタラクティブなターゲットは、短辺が少なくとも 24px あり、マウスでは 40px、タッチでは 44px を目指します。グリフが小さい場合は、負のインセットを持つ疑似要素でヒット領域を広げます。ヒットボックスが 16px の 16px アイコンボタンは defaults-hit-area です。拡張したヒット領域同士が重なることはありません。recipes.md を参照してください。
コントロールの役割を明確にする
アイコンのみのボタンには、動作を示す aria-label を付けます。名前のないゴミ箱アイコンは defaults-name-icon-buttons です。
ステータスを色だけで示してはいけません。アイコンまたは単語を組み合わせてください。エラーを示す唯一の手がかりが赤い点の場合は defaults-not-color-alone です。
リンクにはリンクを使う
ナビゲーションには <a href> を使うため、新しいタブで開く操作、中クリック、リンクのコピーがすべて機能します。onClick でルートを変更する div は defaults-links-are-links です。
状態は URL に保存する
フィルター、タブ、ページネーション、開いているパネルの状態を URL に書き込むことで、更新、共有、戻る操作でも状態が維持されます。更新すると失われるフィルターは defaults-state-in-url です。
ズームを有効に保つ
viewport タグで maximum-scale=1 や user-scalable=no を設定してはいけません。ピンチズームをブロックするページは defaults-allow-zoom です。
タップを自然にする
ルートに -webkit-tap-highlight-color: transparent を一度だけ設定し、タップ可能なすべての要素に独自の :active 状態を指定します。タップ時にグレーのフラッシュが表示される場合は defaults-no-tap-highlight です。
ボタンとリンクには touch-action: manipulation を設定し、2回目のタップを待たずにタップを実行できるようにします。タップの反応が 300ms 遅れる場合は defaults-fast-tap です。recipes.md を参照してください。
ホバースタイルはホバー対応ポインターでのみ適用する
ホバー時のみのスタイルは @media (hover: hover) and (pointer: fine) で囲みます。そうしないと、タッチ画面でタップした後もスタイルが残ります。タップした後も浮き上がったままのカードは defaults-hover-capable-only です。
全高レイアウトには動的な単位を使う
アプリのシェルや画面下部に固定するバーには 100dvh を使い、最初の画面には min-height: 100svh を使います。下部のボタンが URL バーの下に隠れる 100vh のシェルは defaults-dynamic-viewport です。
コンテンツをノッチから離す
viewport タグに viewport-fit=cover を追加し、固定ヘッダー、タブバー、トースト、シートに env(safe-area-inset-*) でパディングを設定します。ホームインジケーターの下に隠れる下部バーは defaults-safe-areas です。
コントロールを選択不可にする
ボタン、タブ、チップ、ドラッグハンドルには user-select: none を設定し、長押ししてもラベルが選択されないようにします。コンテンツのテキストは選択可能なままにします。長押しでハイライトされるボタンラベルは defaults-controls-not-selectable です。
ホットキーを入力欄で無効にする
入力欄、テキストエリア、または編集可能な要素にフォーカスがある間、ホットキーは実行されません。これを defaults-keys-ignore-inputs とします。recipes.md を参照してください。
Enter で送信する
フィールドが 1 つのフォームでは、Enter キーで送信します。複数のフィールドがあるフォームでは、最後のフィールドで Enter キーを押すと送信します。テキストエリアでは Enter キーで改行し、⌘ または Ctrl と Enter キーの組み合わせで送信します。クリックしないと送信できない検索フィールドは defaults-enter-submits です。
入力欄は貼り付けた内容を受け付ける
カード番号の空白や電話番号の括弧など、入力欄はユーザーが入力または貼り付けた内容をそのまま受け付けます。コード内で書式を取り除き、値を整えて表示します。+1 (555) 010-2030 を拒否する電話番号欄は defaults-accept-messy-input です。貼り付けをブロックしてはいけません。また、一部のキーボードが追加する末尾の空白は削除します。
入力欄に適したキーボードを表示する
すべての入力欄に type、inputmode、autocomplete を設定し、キーボードと自動入力を適切にします。プレーンテキストとして設定されたメールアドレス欄は defaults-input-types です。
モバイルでは、入力欄の文字サイズを 16px 以上にします。そうしないと、iOS ではフォーカス時にページがズームされます。スマートフォンで 14px の入力欄は defaults-input-16px です。
ラベルをクリックするとコントロールが反応します
すべてのコントロールにはラベルがあり、ラベルをクリックするとコントロールが動作します。テキストをクリックしても何も起きないチェックボックスは defaults-label-hit-area です。
フォームは送信時に検証します
リクエストが開始されるまで、送信ボタンは有効なままにします。送信時には、各エラーを対応するフィールドの横に表示し、最初のエラーにフォーカスします。フォームが有効になるまで送信ボタンを無効にする場合は defaults-validate-on-submit です。
ビジーボタンはラベルを維持します
リクエストの実行中もボタンのラベルを維持し、インジケーターを表示してボタンを無効にします。ラベルがスピナーに置き換わり、ボタンが小さくなる場合は defaults-loading-button です。
破壊的な操作は取り消せるようにします
破壊的な操作の後に、元に戻す方法を用意します。取り消せない場合は、メニューやポップオーバーを使ってその場で確認します。どちらも用意されていない削除操作は defaults-undo-destructive です。
数字の桁を揃えます
価格、件数、タイマー、テーブルの列には font-variant-numeric: tabular-nums を使用します。カウントの更新に合わせて幅が揺れる場合は defaults-tabular-numbers です。
見出しのバランスを整え、段落を読みやすく折り返します
見出しには text-wrap: balance を、本文の段落には text-wrap: pretty を設定します。最終行に単語が1つだけ残る見出しは defaults-balanced-wrap です。
テキストをくっきり表示します
ルート要素に一度だけ -webkit-font-smoothing: antialiased と -moz-osx-font-smoothing: grayscale を設定します。スムージングがない場合や、コンポーネントごとに設定している場合は defaults-antialiased-root です。
インラインコードを少し小さく表示します
文章中の等幅フォントは 0.9em に縮小します。文章中でサイズが変わらず、文より大きく見えるインラインコードは defaults-inline-mono です。
省略したテキストを再び読めるようにします
ユーザーが必要とする省略済みテキストは、ツールチップ、展開コントロール、詳細ビューのいずれかで全文を表示できるようにします。読み返す方法がないままファイル名が途中で切れる場合は defaults-truncate-with-way-back です。ファイル名とパスは、ほかと区別できるよう中央で省略します。数字、金額、日付は省略しません。
実際のデータに耐えられるレイアウトにします
すべてのフィールドを、現実的にあり得る最長の値でテストします。テキスト列には min-width: 0 を、アバターなどの固定ボックスには flex-shrink: 0 を設定し、メールアドレスとURLには overflow-wrap: anywhere を設定します。長いメールアドレスのせいで行のメニューが画面外に押し出される場合は defaults-survives-long-content です。
任意項目がない場合は、プレースホルダーを表示せず、その行を省きます。役割の欄にダッシュだけが表示される場合は defaults-omit-empty-fields です。
数字、日付、複数形をロケールに合わせます
Intl.NumberFormat、Intl.DateTimeFormat、Intl.PluralRules を使って書式を設定します。1 members や書式設定されていない 1284 は defaults-locale-format です。
更新を通知します
トースト、結果件数、インライン検証は、空の状態でレンダリングされ、テキストの変更時に更新される丁寧なライブリージョン role="status" に配置します。スクリーンリーダーに通知されないトーストは defaults-announce-updates です。
待機中はすぐにフィードバックを表示します
操作には400ms以内に応答するか、楽観的な値、スケルトン、プログレスバーなどでただちにフィードバックを表示します。ローディング時に null を返す分岐は defaults-instant-feedback です。
スピナーやスケルトンの表示は150~300ms遅らせ、表示したら少なくとも300~500msは表示を続けます。1フレームだけ点滅するスケルトンは defaults-no-flicker-loading です。スケルトンは置き換えるレイアウトに合わせます。recipes.md を参照してください。
音は確認のために使い、音だけに頼らないようにします
音は、支払い、アップロード、送信などの確認に適しています。見逃してはならないエラーにも適しています。すべての音には、ステータスの変更など、目に見える対応物を用意します。画面上で何も変化しないのに成功音が鳴る場合は defaults-sound-has-a-twin です。
入力、ホバー、スクロール、キーボード操作では音を鳴らしません。キー入力のたびにクリック音が鳴る場合は defaults-no-sound-on-repeat です。
ユーザーが音をオフにできるようにし、音量の初期値は 0.3 にします。オフにする方法がなく、audio.volume = 1 がハードコードされている場合は defaults-sound-optional です。再生方法とクリック音の合成については recipes.md を参照してください。
ダイアログは一度に1つだけ表示します
ダイアログの上に別のダイアログを開いてはいけません。メニュー、ポップオーバー、または新しいページを使用します。これは defaults-no-stacked-dialogs です。
ペア
各チェックはペアになっています。修正が必要な行の隣に、置き換える行が表示されます。
| ルール | 問題のある状態 | 置き換え |
|---|---|---|
defaults-fade-not-cut | overflow: auto による唐突な切れ目 | 両端に mask-image のフェードを適用 |
defaults-no-clipped-shadow | overflow: hidden でカードの影が切れる | スクロール領域に padding: 8px |
defaults-scroll-not-wrap | コードの行が2行に折り返される | white-space: nowrap と overflow-x: auto |
defaults-contain-overscroll | ドロワーのリストが末端でページをスクロールさせる | ドロワーに overscroll-behavior: contain |
defaults-sticky-border | 境界線のないスティッキーヘッダー | border-bottom: 1px solid var(--gray-a4) |
defaults-clear-sticky-chrome | 56px のスティッキーヘッダーの下にフォーカスされたフィールドが隠れる | scroll-padding-block-start: 56px |
defaults-ring-not-border | ポップオーバーに border: 1px solid #e5e5e5 と box-shadow を適用 | 透明な境界線の上に、影の先頭として 0 0 0 1px var(--gray-a4) を指定 |
defaults-layered-shadows | box-shadow: 0 4px 16px rgba(0, 0, 0, 0.2) | 下向きの3つのレイヤーを使い、ぼかしを強くしながら不透明度を下げる |
defaults-fade-the-shadow | transition: box-shadow 200ms | 浮き上がった影を保持する ::after の opacity を変更 |
defaults-image-outline | 白いカード上で写真の輪郭が見えない | outline-offset: -1px で outline: 1px solid rgba(0, 0, 0, 0.1) を指定 |
defaults-no-nested-cards | 見出しと別のカードを含むカード | 1つのカードの上に見出しを配置 |
defaults-rounded-not-pill | ボタンに border-radius: 9999px を指定 | border-radius: var(--radius-2) |
defaults-no-decoration | カードの背後にグラデーションの光彩がある | 単色の背景 |
defaults-color-scheme | スクロールバーが明るいダークテーマ | ルートに color-scheme: dark を指定 |
defaults-quiet-theme-switch | テーマ切り替え時にすべての色のトランジションが発生する | 1フレームだけトランジションを無効にする |
defaults-focus-keyboard-only | .button:focus { box-shadow: 0 0 0 2px var(--accent) } | .button:focus-visible { outline: 2px solid var(--accent) } |
defaults-trap-and-return-focus | Tab キーでダイアログの背後にあるページへ移動できる | フォーカスをダイアログ内に留め、その後トリガーに戻す |
defaults-hit-area | 16px のアイコンボタンのヒット領域が16px | ::before に inset: -12px を指定し、40px のターゲットにする |
defaults-name-icon-buttons | <button><TrashIcon /></button> | <button aria-label="Delete"> |
defaults-not-color-alone | エラーを示す手がかりが赤い点だけ | アイコンまたは Failed の文字を点と併用 |
defaults-links-are-links | <div onClick={() => router.push("/settings")}> | <a href="/settings"> |
defaults-state-in-url | フィルターの状態が useState にしか保持されていない | URL に ?status=open を指定 |
defaults-allow-zoom | maximum-scale=1, user-scalable=no | width=device-width, initial-scale=1 |
defaults-no-tap-highlight | タップするたびにブラウザーで灰色のフラッシュが表示される | -webkit-tap-highlight-color: transparent と :active のスタイル |
defaults-fast-tap | タップしてからボタンが反応するまで300msかかる | touch-action: manipulation |
defaults-hover-capable-only | .card:hover { transform: translateY(-2px) } | 同じルールを @media (hover: hover) and (pointer: fine) 内に指定 |
defaults-dynamic-viewport | アプリシェルに height: 100vh を指定 | height: 100dvh |
defaults-safe-areas | ホームインジケーターの下にタブバーがある | viewport-fit=cover とともに padding-bottom: env(safe-area-inset-bottom) を指定 |
defaults-controls-not-selectable | 長押しするとタブのラベルが選択される | タブに user-select: none を指定 |
defaults-keys-ignore-inputs | 検索中に J キーを押すと選択が移動する | ホットキーの対象から input、textarea、[contenteditable] を除外 |
defaults-enter-submits | 検索フィールドが form で囲まれていない | onSubmit で検索を実行する form |
defaults-accept-messy-input | 電話番号フィールドが +1 (555) 010-2030 を拒否する | 入力を受け付け、+15550102030 として保存し、書式を整えて表示 |
defaults-input-types | メールアドレスに <input type="text"> を使用 | type="email" autocomplete="email" |
defaults-input-16px | モバイルの入力欄に font-size: 14px を指定 | font-size: 16px |
defaults-label-hit-area | チェックボックスの横に単独の span がある | チェックボックスとテキストを1つの label 内に配置 |
defaults-validate-on-submit | 送信ボタンに disabled={!isValid} を指定 | ボタンを有効にし、送信時にエラーを表示して最初のエラーにフォーカス |
defaults-loading-button | ラベルをスピナーに置き換える | ラベルを残して横にスピナーを表示し、ボタンを無効にする |
defaults-undo-destructive | 元に戻す方法がなく、削除が即時に実行される | Undo を含むトースト |
defaults-tabular-numbers | 時間表示の数字が変わるたびに幅が変わる | font-variant-numeric: tabular-nums |
defaults-balanced-wrap | 見出しの最後の行に単語が1つだけ残る | h1 { text-wrap: balance } と p { text-wrap: pretty } |
defaults-antialiased-root | 明るいテキストに既定のサブピクセルスムージングが適用される | ルートに -webkit-font-smoothing: antialiased を指定 |
defaults-inline-mono | <code> のサイズが周囲の文章と同じ | font-size: 0.9em |
defaults-truncate-with-way-back | ファイル名が省略表示され、それ以外に情報がない | 完全な名前を表示するツールチップ |
defaults-survives-long-content | 長いメールアドレスが行のメニューを画面外に押し出す | テキストに min-width: 0、メニューに flex-shrink: 0 を指定 |
defaults-omit-empty-fields | 役割がない場合にダッシュを表示する | 役割の行を省略 |
defaults-locale-format | count が 1 のときに {count} members と表示する | Intl.PluralRules で member または members を選択 |
defaults-announce-updates | テキストとともにトーストをマウントするが、ロールが指定されていない | テキストが更新される空の role="status" 領域 |
defaults-instant-feedback | if (isLoading) return null | if (isLoading) return <Skeleton /> |
defaults-no-flicker-loading | 40ms で応答する場合にもスピナーを表示する | 200ms 後に表示し、少なくとも400msは表示を維持 |
defaults-sound-has-a-twin | ステータスを変更せずに playSound("success") を呼び出す | サウンドとともに setStatus("success") を呼び出す |
defaults-no-sound-on-repeat | onChange 内で playSound("keystroke") を呼び出す | 入力時にサウンドを鳴らさない |
defaults-sound-optional | audio.volume = 1 で、オフにする手段がない | サウンド設定を用意し、デフォルトの音量を 0.3 にする |
defaults-no-stacked-dialogs | 設定ダイアログから確認ダイアログを開く | ダイアログ内でメニューまたはポップオーバーを使用 |
報告
重大度。 HIGH は、利用やアクセスを妨げます。影が途中で切れている、スクロールが突然途切れる、入力中にホットキーが動作する、ダイアログが重なっている、24px 未満のターゲット、または無言の待機が該当します。また、貼り付けた書式を受け付けないフィールド、視覚的な代替手段やオフスイッチのない音声も該当します。名前のないアイコンボタン、色だけで示されたステータス、ダイアログからフォーカスが抜ける状態、ズームのブロック、リンクのないルートも該当します。また、有効になるまで無効化された送信ボタン、元に戻す方法のない切り詰められた値、取り消しや確認のない破壊的な操作も該当します。長い値によって画面外に押し出されたコンテンツ、操作部品を隠す 100vh のシェル、ホームインジケーターに重なるバーも該当します。
MEDIUM は、ピル型ボタン、クリック時のフォーカスリング、またはタッチ操作で解除されないホバー状態です。データ内のプロポーショナル数字、頻繁に行う操作で鳴る音、オーバーレイから漏れるスクロール、更新時に失われる状態、ちらつくスケルトンも該当します。また、不適切なキーボードが表示されるフィールド、16px 未満のモバイル入力欄、機能しないラベル、送信されない Enter キー、インジケーターのない処理中のボタンも該当します。通知のない更新、固定表示の UI の下に隠れたフィールド、カード内のカードも該当します。タップ時のフラッシュや遅延、選択可能なコントロールのラベル、書式設定されていない数値や複数形も該当します。
LOW は、影の横にある不透明な境界線、装飾的なグロー、単一の強い影、またはトランジションする box-shadow です。バランスの悪い見出し、画像のアウトラインの欠落、境界線のない固定ヘッダー、暗いページ上の明るいスクロールバー、にじんだテーマ切り替え、ルート要素のフォントスムージングの欠落も該当します。フルサイズのインラインコードや、空のフィールドに対するプレースホルダーのダッシュも該当します。
形式。 根本原因ごとに指摘事項を 1 つ記載します。
file:line - [rule-id] what is wrong
続けて Rule | Count | Severity の概要を記載します。HIGH が 1 件でも残っている場合は Block、それ以外は Approve とします。読んでいないファイルを Approve にしないでください。レイアウトに依存するチェックでは画面を開き、実行できなかったチェックはすべて Not verified と記載してください。