第58章:アクセシビリティ:ボタン/ラベル/フォーカス🧑🦽✨
この章のゴール🎯
- クリックだけじゃなく、キーボード(Tab/Enter/Space)でも気持ちよく操作できるUIにする💻⌨️
- ボタン・ラベル・フォーカスの“最低ライン”を守って、誰にとっても使いやすくする🌈
- 「ariaって何…😵」にならずに、まずはHTMLの正攻法で勝てるようになる🫶
アクセシビリティってなに?🫧
ざっくり言うと、 **「いろんな人・いろんな状況でも使えるようにすること」**だよ😊✨
- マウスが使えない(手がふさがってる/怪我/環境)🙌
- 画面を読み上げて使う(スクリーンリーダー)🔊
- 小さい画面・暗い場所・低速回線📱🌙🐢
こういう時に「ちゃんと押せる」「どこを操作中かわかる」って、めちゃ大事💡
1) ボタン:まず“buttonを使う”が最強👑🔘
✅ いいボタン(正解)
- クリックできるものは
<button>を使う - アイコンだけのボタンは **説明(ラベル)**を付ける
export function GoodButtons() {
return (
<div style={{ display: "flex", gap: 12, alignItems: "center" }}>
<button type="button">保存する</button>
{/* アイコンだけの場合:aria-labelで意味を補う */}
<button type="button" aria-label="検索">
🔍
</button>
</div>
);
}
❌ よくあるNG(やりがち🥺)
divやspanをボタンっぽくしてクリックさせる → キーボード操作や読み上げで詰みやすい💥
// NG例:見た目はボタンでも、アクセシビリティ的に弱い😢
export function BadButton() {
return <div onClick={() => alert("clicked!")}>押してね</div>;
}
ボタンの小ワザ✨
-
送信ボタンは
type="submit" -
送信じゃないボタンは
type="button"(これ超大事!😳)- フォーム内のボタンがうっかり送信しちゃう事故を防ぐ🧯
2) ラベル:入力欄は“誰?”がわかるように🏷️📝
入力フォームは、見た目の文字だけじゃなくて、機械にも「これは何の入力?」って伝えたいの💡
そこで label + htmlFor + id のセットが王道だよ✨
export function GoodLabelForm() {
return (
<form>
<div style={{ marginBottom: 12 }}>
<label htmlFor="email">メールアドレス</label><br />
<input id="email" name="email" type="email" autoComplete="email" />
</div>
<div style={{ marginBottom: 12 }}>
<label htmlFor="msg">メッセージ</label><br />
<textarea id="msg" name="message" rows={4} />
</div>
<button type="submit">送信</button>
</form>
);
}
✅ さらに親切:ヒント文は aria-describedby 💬
「パスワードは8文字以上だよ」みたいな補足を、読み上げにも伝えられるよ🔊
export function PasswordField() {
return (
<div>
<label htmlFor="pw">パスワード</label><br />
<input id="pw" type="password" aria-describedby="pw-help" />
<p id="pw-help">8文字以上で入力してね</p>
</div>
);
}
3) フォーカス:今どこ操作してるか“光らせる”✨🟦
キーボード操作の人は Tab を押して移動するよ⌨️ だから「今どこにいるか」が見えないと、迷子になる😵💫
フォーカス移動のイメージ図🧭
✅ :focus-visible で“キーボードの時だけ”枠線を出す🟦
マウスクリック時は出さず、Tab操作の時だけ出す…みたいにできるよ✨ (“見た目がダサいから消す”はNG!消すなら代わりを用意してね🥺)
CSS Modules例:components/a11y.module.css
.focusRing:focus-visible {
outline: 3px solid currentColor;
outline-offset: 3px;
border-radius: 8px;
}
.input {
padding: 8px 10px;
border: 1px solid #aaa;
border-radius: 8px;
}
.button {
padding: 10px 14px;
border: 1px solid #666;
border-radius: 10px;
cursor: pointer;
}
使う側(TSX)
import styles from "./a11y.module.css";
export function FocusDemo() {
return (
<div style={{ display: "grid", gap: 10, maxWidth: 360 }}>
<label htmlFor="name">お名前</label>
<input id="name" className={`${styles.input} ${styles.focusRing}`} />
<button type="button" className={`${styles.button} ${styles.focusRing}`}>
つぎへ
</button>
</div>
);
}
4) まとめ:この章の“最小チェックリスト”✅🎀
- ✅ クリックできるものは
button/ リンクはa(orLink) - ✅ フォームは
label(htmlFor) + idを基本セットにする - ✅ フォーカス(Tab移動)で「今どこ?」が見えるようにする
- ✅ アイコンだけは
aria-labelで意味を補う - ✅
type="button"を忘れない(フォーム事故防止🚑)
やってみよう(ミニ課題)🧪💖
目標:アクセシブルな“お問い合わせフォーム”を1枚作る📮✨
手順🔧
app/a11y/page.tsxを作る- 下のコンポーネントを貼る
- 実行したら、Tabキーだけで操作してみる(これ大事!⌨️👀)
app/a11y/page.tsx
import styles from "./page.module.css";
export default function Page() {
return (
<main className={styles.main}>
<h1 className={styles.title}>お問い合わせ📮</h1>
<form className={styles.form}>
<div className={styles.field}>
<label htmlFor="email" className={styles.label}>メールアドレス</label>
<input
id="email"
name="email"
type="email"
autoComplete="email"
className={`${styles.input} ${styles.focusRing}`}
required
/>
</div>
<div className={styles.field}>
<label htmlFor="topic" className={styles.label}>用件</label>
<select
id="topic"
name="topic"
className={`${styles.input} ${styles.focusRing}`}
defaultValue="general"
>
<option value="general">一般</option>
<option value="bug">不具合</option>
<option value="other">その他</option>
</select>
</div>
<div className={styles.field}>
<label htmlFor="message" className={styles.label}>内容</label>
<textarea
id="message"
name="message"
rows={5}
className={`${styles.input} ${styles.focusRing}`}
aria-describedby="msg-help"
required
/>
<p id="msg-help" className={styles.help}>200文字くらいまででOKだよ🫶</p>
</div>
<div className={styles.actions}>
<button type="submit" className={`${styles.button} ${styles.focusRing}`}>
送信する✨
</button>
<button type="button" className={`${styles.button} ${styles.focusRing}`} aria-label="入力内容をリセット">
リセット🧼
</button>
</div>
</form>
</main>
);
}
app/a11y/page.module.css
.main {
padding: 24px;
max-width: 520px;
margin: 0 auto;
}
.title {
font-size: 24px;
margin-bottom: 16px;
}
.form {
display: grid;
gap: 16px;
}
.field {
display: grid;
gap: 6px;
}
.label {
font-weight: 600;
}
.help {
font-size: 12px;
opacity: 0.8;
}
.input {
padding: 10px 12px;
border: 1px solid #999;
border-radius: 12px;
}
.actions {
display: flex;
gap: 12px;
margin-top: 8px;
}
.button {
padding: 10px 14px;
border: 1px solid #666;
border-radius: 12px;
cursor: pointer;
background: transparent;
}
.focusRing:focus-visible {
outline: 3px solid currentColor;
outline-offset: 3px;
}
できたらチェック✅😆
- Tabで「メール → 用件 → 内容 → 送信 → リセット」の順に移動できる?
- フォーカスがちゃんと見える?🟦
- ラベルクリックで入力欄にフォーカス入る?👆
- アイコン(今回はリセット)に意味が付いてる?(
aria-label)🫶
次の章(第59章)は「カード一覧を“それっぽく”仕上げる」だけど、今回は第58章ここまでだよ〜!🎉💖