harden について
フロントエンド開発者やエージェント向けのデザインスキルです。エッジケース、エラー、ローカライズの問題、実環境の負荷に備え、コンテンツ、状態、権限、復旧の堅牢性を高めます。
主な機能
- 01極端な入力とエラー状況をテスト
- 02テキストのはみ出しとローカライズを改善
- 03エラー処理と復旧を設計
- 04空・読み込み・権限状態に対応
- 05影響の大きい操作を保護
- 06メディアと入力処理を堅牢化
活用シーン
- 01長文や多様な入力にフォームを対応
- 02ネットワークや API エラーからの復旧を改善
- 03言語や地域が異なる環境での使いやすさを向上
- 04一括操作や破壊的操作のリスクを軽減
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ aladicf/ better-web-ui --skill hardenインストール後、Claude Code で/hardenを入力して読み込まれたか確認します。
詳細
エッジケース、エラー、国際化の問題、理想化された設計を破綻させる実環境の使用シナリオに対して、インターフェースを強化します。
エッジケースは、ハッピーパスのリリース後に後始末として扱うのではなく、設計の入力として扱ってください。
エラー、警告、成功、情報の各状態を設計する際は、セマンティックカラーのリファレンスを参照し、セマンティックカラーが明確に機能し、単なる装飾にならないようにしてください。 通知フロー、アクティビティフィード、サマリー、アラート設定を強化し、通知疲れや中断の過負荷を防ぐ際は、ステータスの伝達のリファレンスを参照してください。 ユーザーがアップロードしたメディア、スクリーンショット、アイコンの拡大縮小、画像のはみ出し動作を強化する際は、画像の扱いを参照してください。 タスクに検証動作、サマリー、厳格なバリデーター、回復可能なフィールドエラー、または不適切な復旧設計による離脱が関わる場合は、エラーからの復旧のリファレンスを参照してください。 サインイン、セッションの有効期限切れ、パスワード設定、MFA、ロックアウト、アカウント復旧のフローを強化する際は、認証とアカウント復旧のリファレンスを参照してください。 ロールエディター、アクセス権のリクエストフロー、403からの復旧、管理者とメンバーで異なる画面、権限の境界、リスクの高い権限変更を強化する際は、権限とロールのUXのリファレンスを参照してください。 言語セレクター、地域別の上書き、通貨や配送の設定、旅行中、VPN利用中、または複数言語が混在する状況で失敗しうるロケール切り替えフローを強化する際は、言語とロケールの選択のリファレンスを参照してください。 キーボード操作、フォーカスインジケーター、スキップリンク、非表示コンテンツの動作、モーダルのフォーカス管理、またはカスタムコンポーネントやサードパーティ製コンポーネントのアクセシビリティに関する主張を強化する際は、コンポーネントのアクセシビリティのリファレンスを参照してください。 401、403、404、429、500、503 などの状態で、失敗時に専用のルートレベルの復旧ページが必要な場合は、空状態のパターンのリファレンスを参照してください。 ストレス、緊急性、運用上のプレッシャー、緊急事態のもとでも使いやすさを維持する必要があるワークフローを強化する際は、インタラクション設計のリファレンスを参照してください。 ローディング状態、古いデータを示す手掛かり、スケルトンの使い方、脆弱な実際の動作を覆い隠している可能性のある、パフォーマンスが良さそうに見える状態を強化する際は、ローディングフィードバックと体感パフォーマンスのリファレンスを参照してください。 インターフェースは技術的には動作するものの、小さなジャンクの繰り返し、状態の喪失、ホバーの罠、弱いフィードバックなどの細かな不満によって、不安定で信頼を損なう印象になる場合は、小さな失敗と体感品質のリファレンスを参照してください。
必須の準備
ユーザーは /harden でこのワークフローを開始します。このスキルが有効になったら、$frontend-design を読み込んでください。そこには設計原則、アンチパターン、コンテキスト収集プロトコルが含まれています。先へ進む前にそのプロトコルに従ってください。設計コンテキストがまだ存在しない場合は、まず $setup を読み込む必要があります。さらに、現在のフローを破綻させる可能性が最も高いリスクのあるシナリオ、エッジケース、ユーザーの制約も収集してください。
堅牢化の必要性を評価する
弱点とエッジケースを特定します。
- 極端な入力でテストする:
- 非常に長いテキスト(名前、説明、タイトル)
- 非常に短いテキスト(空、1文字)
- 特殊文字(絵文字、RTL テキスト、アクセント記号)
- 大きな数値(数百万、数十億)
- 多数の項目(リスト項目1000件以上、選択肢50件以上)
- データなし(空の状態)
- 扱いにくいメディア(小さなアイコン、大きなスクリーンショット、切り抜きや背景色がまちまちなユーザーアップロード画像)
- エラーシナリオをテストする:
- ネットワーク障害(オフライン、低速、タイムアウト)
- API エラー(400、401、403、404、500)
- バリデーションエラー
- 厳しすぎるバリデーターや、書式に関する期待値の不一致
- フォーム、複数ステップのフロー、オーバーレイでのブラウザーの戻る/進む操作
- 権限エラー
- レート制限
- 同時実行操作
-
国際化をテストする:
- 長い翻訳(ドイツ語は英語より30%長くなることがよくあります)
- RTL 言語(アラビア語、ヘブライ語)
- 文字セット(中国語、日本語、韓国語、絵文字)
- 日付/時刻の形式
- 数値の形式(1,000 と 1.000)
- 通貨記号
-
負荷の高い利用状況をテストする:
- 小さな作業スペースや分割画面の作業スペース
- 騒がしく、中断が頻繁に発生する環境
- 忍耐の余裕がない、緊急の意思決定
- 密集した操作項目ではなく、明確な次のステップを1つ必要としているユーザー
重要:完璧なデータでしか機能しないデザインは、本番環境で使える状態ではありません。現実に備えて堅牢化しましょう。
堅牢化の観点
耐障害性を体系的に高めます。
テキストのオーバーフローと折り返し
長いテキストへの対応:
/* Single line with ellipsis */
.truncate {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
/* Multi-line with clamp */
.line-clamp {
display: -webkit-box;
-webkit-line-clamp: 3;
-webkit-box-orient: vertical;
overflow: hidden;
}
/* Allow wrapping */
.wrap {
word-wrap: break-word;
overflow-wrap: break-word;
hyphens: auto;
}
Flex/Grid のオーバーフロー:
/* Prevent flex items from overflowing */
.flex-item {
min-width: 0; /* Allow shrinking below content size */
overflow: hidden;
}
/* Prevent grid items from overflowing */
.grid-item {
min-width: 0;
min-height: 0;
}
レスポンシブな文字サイズ:
- 可変タイポグラフィには
clamp()を使います - 読みやすい最小サイズを設定します(コンパクトなレイアウトでは14px)
- テキストの拡大をテストします(ズーム200%)
- コンテナがテキストに合わせて広がるようにします
国際化(i18n)
テキストの拡張:
- 翻訳用に30~40%の余裕を確保します
- コンテンツに合わせて調整される flexbox/grid を使います
- 最も長い言語(通常はドイツ語)でテストします
- テキストコンテナの固定幅を避けます
// ❌ Bad: Assumes short English text
<button className="w-24">Submit</button>
// ✅ Good: Adapts to content
<button className="px-4 py-2">Submit</button>
RTL(右から左)への対応:
/* Use logical properties */
margin-inline-start: 1rem; /* Not margin-left */
padding-inline: 1rem; /* Not padding-left/right */
border-inline-end: 1px solid; /* Not border-right */
/* Or use dir attribute */
[dir="rtl"] .arrow { transform: scaleX(-1); }
文字セットへの対応:
- あらゆる場所で UTF-8 エンコーディングを使います
- 中国語/日本語/韓国語(CJK)の文字でテストします
- 絵文字でテストします(2~4バイトになる場合があります)
- さまざまな文字体系(ラテン文字、キリル文字、アラビア文字など)に対応します
日付/時刻の書式設定:
// ✅ Use Intl API for proper formatting
new Intl.DateTimeFormat('en-US').format(date); // 1/15/2024
new Intl.DateTimeFormat('de-DE').format(date); // 15.1.2024
new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD'
}).format(1234.56); // $1,234.56
複数形の処理:
// ❌ Bad: Assumes English pluralization
`${count} item${count !== 1 ? 's' : ''}`
// ✅ Good: Use proper i18n library
t('items', { count }) // Handles complex plural rules
エラー処理
ネットワークエラー:
- 明確なエラーメッセージを表示します
- 再試行ボタンを用意します
- 何が起きたかを説明します
- オフラインモードを提供します(該当する場合)
- タイムアウトのシナリオに対応します
// Error states with recovery
{error && (
<ErrorMessage>
<p>Failed to load data. {error.message}</p>
<button onClick={retry}>Try again</button>
</ErrorMessage>
)}
フォームのバリデーションエラー:
- フィールドの近くにインラインエラーを表示します
- 明確で具体的なメッセージを表示します
- 修正方法を提案します
- 不必要に送信を妨げないようにします
- エラーが発生してもユーザーの入力を保持します
- ブロックするとユーザーの離脱につながり、リスクモデル上も許容される場合は、住所や電話番号など現実に即した書式を受け入れるための例外経路を用意します
API エラー:
- 各ステータスコードに適切に対応します
- 400:バリデーションエラーを表示します
- 401:可能な場合は、意図した遷移先を保持したうえで、サインインまたはセッションの更新を促します
- 403:明確なアクセス方法を示す権限エラーの状態を表示します
- 404:適切な回復ナビゲーションを備えた「見つかりません」状態を表示します
- 429:判明している場合は再試行までの時間を示し、一時的なレート制限状態を表示します
- 500:再試行に加えて、安全な代替手段またはサポートへの経路を示すサーバーエラー状態を表示します
- 503:利用可能な場合はステータスや復旧予定時刻を示し、メンテナンス中または一時的な障害状態を表示します
障害によってルート全体が使えなくなる場合は、小さなインラインメッセージやトーストではなく、専用のエラーページとして設計します。
段階的な機能低下:
- JavaScript がなくても主要機能が動作します
- 画像に alt テキストを設定します
- プログレッシブエンハンスメントを適用します
- 未対応の機能にはフォールバックを用意します
エッジケースと境界条件
力には責任が伴います
機能が強力であるほど、インターフェースではその結果を慎重に伝え、誤操作による損害を防ぐ必要があります。
強い影響力を持つ機能には、次のものがあります。
- 一括操作
- 管理パネル
- 権限エディター
- 自動化と連携
- 大量のコンテンツを変更または生成できる AI ツール
- 破壊的な編集、公開、請求、アカウント操作
追加する安全策:
- ロールベースの権限と、明確な機能の境界
- 影響の大きい操作の実行前にプレビュー、概要、件数を表示する
- 元に戻せる操作には取り消し機能を、取り消せない操作やコストの高い操作には確認を設ける
- 操作が組織に影響する場合は、監査証跡や確認できる履歴を残す
- 安全なデフォルト設定、明示的な警告、わかりやすい結果の説明
強力な機能は、危険ではなく制御されていると感じられるべきです。機能によって大規模なミスが起きる可能性がある場合は、実行前に何が起きるかをユーザーが理解しなければ進められないよう、インターフェースを設計してください。
空の状態:
- リストに項目がない
- 検索結果がない
- 通知がない
- 表示するデータがない
- 次に行う操作を明確に示す
通知に関する例外ケース:
- 通知量の急増(短時間にイベントが多発する)
- 重複した通知、または内容がほぼ同じ通知
- 状態変更後も未読バッジが古いまま残る
- おやすみ時間、ミュート、スヌーズの設定が守られない
- リアルタイムの割り込みではなく、概要を必要とするユーザー
読み込み中の状態:
- 初回読み込み
- ページネーションの読み込み
- 更新
- 読み込み中の内容を表示する(「プロジェクトを読み込んでいます…」)
- 時間のかかる処理では所要時間の目安を示す
大規模なデータセット:
- ページネーションまたは仮想スクロール
- 検索・フィルター機能
- パフォーマンスの最適化
- 10,000 件すべてを一度に読み込まない
並行操作:
- 二重送信を防ぐ(読み込み中はボタンを無効にする)
- 競合状態に対処する
- ロールバック可能な楽観的更新
- 競合の解決
権限の状態:
- 閲覧権限がない
- 編集権限がない
- 読み取り専用モード
- 理由を明確に説明する
- 該当する場合は、アクセス権をリクエストする方法や、権限の管理者を確認する方法を示す
ブラウザー互換性:
- 最新機能向けのポリフィル
- 未対応の CSS に対するフォールバック
- 機能検出(ブラウザー検出ではない)
- 対象ブラウザーでテストする
画像、スクリーンショット、ユーザーメディア
ユーザーがアップロードした画像:
- 意図したアスペクト比の制御されたコンテナーに画像を収める
- グリッドを崩す予測不能な元のレイアウトをそのまま維持するのではなく、cover やトリミングを使う
- 画像がページに溶け込む場合は、控えめな内側の影や内側の境界線で背景のはみ出しを防ぐ
スクリーンショット:
- 小さく縮小しすぎて読めないスクリーンショットに注意する
- 詳細が重要な場合は、範囲を絞ったトリミングや部分的なスクリーンショットを使うか、元のレイアウトを小さくしてスクリーンショットを撮る
アイコンとビジュアルアセット:
- 小さなアイコンを、本来のデザインサイズを大幅に超えて拡大しない
- 詳細なアートワークを、形が崩れるほど小さく縮小しない
入力の検証とサニタイズ
フォームと入力におけるPostelの法則
インターフェースからの出力は慎重にしつつ、システムが安全に正規化できる場合は、問題のない入力のばらつきを許容します。
耐障害性を高める方法:
- 意味が明確であれば、スペース、ハイフン、国番号の記号の有無を問わず電話番号を受け付ける
- 貼り付けた値を必須フィールドでエラーにするのではなく、先頭と末尾の空白を自動的に削除する
- 大文字と小文字が関係ない場合は、大文字と小文字を区別しない識別子、タグ、検索クエリを一貫して扱う
- 別のツールからデータを持ち込んだユーザーを困らせるのではなく、貼り付けられたコンテンツを適切に正規化する
- エラーが発生したときはユーザーの入力内容を保持し、簡単に修正できるようにする
曖昧さ、セキュリティ、または取り返しのつかないエラーが実際に問題になる場合にのみ、厳密に扱います。目的は、むやみに許容することではなく、不必要に細かな調整作業を減らすことです。
クライアントサイドの検証:
- 必須フィールド
- 形式の検証(メールアドレス、電話番号、URL)
- 文字数制限
- パターンマッチング
- カスタム検証ルール
サーバーサイドの検証(常に実施):
- クライアントサイドの検証だけを信頼しない
- すべての入力を検証してサニタイズする
- インジェクション攻撃から保護する
- レート制限
制約への対応:
<input
type="text"
maxlength="100"
pattern="[A-Za-z0-9]+"
required
aria-describedby="username-hint"
/>
<small id="username-hint">
Letters and numbers only, up to 100 characters
</small>
安全な場合は拒否する前に正規化する:
- 入力が無効だと判断する前に、空白、句読点、大文字と小文字を正規化する
- 書式の変更によって意味や確信度に影響が出る場合は、標準化された結果をユーザーに表示する
- 安全でない、曖昧、または実質的に誤っている入力は拒否する。ただし、理由と修正方法を説明する
アクセシビリティの耐障害性
キーボード操作:
- すべての機能をキーボードから利用できる
- 論理的なタブ順序
- モーダル内でのフォーカス管理
- 長いコンテンツにはスキップリンクを用意する
スクリーンリーダーのサポート:
- 適切なARIAラベル
- 動的な変更を通知する(ライブリージョン)
- 内容を説明する代替テキスト
- セマンティックHTML
動きへの配慮:
@media (prefers-reduced-motion: reduce) {
* {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
}
}
ハイコントラストモード:
- Windowsのハイコントラストモードでテストする
- 色だけに頼らない
- 代替となる視覚的な手がかりを用意する
パフォーマンスの耐障害性
低速な接続:
- 画像を段階的に読み込む
- スケルトンスクリーン
- 楽観的なUI更新
- オフラインサポート(サービスワーカー)
メモリリーク:
- イベントリスナーをクリーンアップする
- サブスクリプションをキャンセルする
- タイマーやインターバルをクリアする
- アンマウント時に保留中のリクエストを中止する
スロットリングとデバウンス:
// Debounce search input
const debouncedSearch = debounce(handleSearch, 300);
// Throttle scroll handler
const throttledScroll = throttle(handleScroll, 100);
テスト戦略
手動テスト:
- 極端なデータ(非常に長い、非常に短い、空)でテストする
- 異なる言語でテストする
- オフラインでテストする
- 低速な接続でテストする(3Gにスロットリングする)
- 運用上のプレッシャーがかかる状況で製品が使われる場合は、騒がしい環境、忙しい環境、または画面分割環境でテストする
- スクリーンリーダーでテストする
- キーボードのみの操作でテストする
- 古いブラウザーでテストする
- ワークフローにインシデント、期限、承認、またはその他の重大な局面が含まれる場合は、少なくとも1回、負荷テストまたは緊急時の訓練を実施する
自動テスト:
- エッジケースの単体テスト
- エラーシナリオの統合テスト
- 重要な経路のE2Eテスト
- ビジュアルリグレッションテスト
- アクセシビリティテスト(axe、WAVE)
重要:堅牢化とは、予期しない事態を想定することです。実際のユーザーは、思いもよらない操作をします。
絶対にしないこと:
- 入力が完璧だと想定する(すべてを検証する)
- 国際化を無視する(グローバルな利用を想定して設計する)
- 汎用的なエラーメッセージ(「エラーが発生しました」)のままにする
- オフラインの状況を忘れる
- クライアントサイドの検証だけを信頼する
- テキストに固定幅を使用する
- 英語と同じ長さのテキストを想定する
- 1つのコンポーネントでエラーが起きたときに、インターフェース全体をブロックする
- ユーザーがアップロードした画像によってレイアウトの形状や間隔が決まるようにする
- スクリーンショットを小さくしすぎて、内容を判読できなくする
- 小さなアイコンを本来の視覚的なサイズ以上に拡大し、それを「レスポンシブ」と呼ぶ
ハードニングの検証
エッジケースを含めて徹底的にテストします。
- 長文:100文字以上の名前を試します
- 絵文字:すべてのテキストフィールドで絵文字を使用します
- RTL:アラビア語またはヘブライ語でテストします
- CJK:中国語/日本語/韓国語でテストします
- ネットワークの問題:インターネットを切断し、接続を制限します
- 大規模データセット:1000件以上の項目でテストします
- 同時操作:送信ボタンをすばやく10回クリックします
- エラー:API エラーを強制的に発生させ、すべてのエラー状態をテストします
- 空の状態:すべてのデータを削除し、空の状態をテストします
デモでの完璧さではなく、本番環境での実態に備えてハードニングすることを忘れないでください。ユーザーが予想外のデータを入力したり、操作の途中で接続が切れたり、想定外の方法で製品を使ったりすることを想定しましょう。すべてのコンポーネントにレジリエンスを組み込みます。