フォーム・入力
スピンボタン
解説ありSpinbutton
数値の増減入力。上下キーとボタンでの増減、範囲の通知。
スピンボタンとは?
スピンボタンは、+ / −(または ↑ / ↓)で値を1つずつ増減して、 ある範囲の中から数を選ぶ UI です。数量・人数・階数の指定などでよく使われます。
スライダーと似ていますが、スピンボタンは「飛び飛びの値を細かく正確に指定する」のが得意です。 伝えるべきことは同じで、現在値と範囲(最小・最大)、そしてキーボード操作です。
なぜアクセシビリティが大事なの?
- キーボードだけで操作する人。+ − が
<div>だとフォーカスも押下もできず、 値を変えられません。↑ ↓ で増減できることも期待されます。 - スクリーンリーダーを使う人。役割(スピンボタン)・現在値・範囲が伝わらないと、 「いくつなのか」「あといくつ増やせるのか」が分かりません。
これを一番カンタンに満たすのが、ネイティブの <input type="number"> です。 増減ボタン・キーボード操作・読み上げをブラウザが提供します。
ライブデモ(推奨実装)
下はネイティブの <input type="number"> です。マウスを使わず、フォーカスして ↑ ↓ で変えてみてください。
試してみよう:Tab で入力欄へ → ↑ ↓ で1ずつ増減、数字キーで直接入力もできる。1〜10 の範囲。
ポイント
スクリーンリーダーで入力欄にフォーカスすると「数量, スピンボタン, 1」のように役割と現在値が読み上げられ、↑↓ で変えると新しい値が読み上げられます。すべてネイティブが自動で提供します。
キーボード操作
| キー | 動作 | 必須/任意 |
|---|---|---|
| ↑ | 値を1ステップ増やす | 必須 |
| ↓ | 値を1ステップ減らす | 必須 |
| Home / End | 最小値 / 最大値にする | 任意(推奨) |
| 数字キー | 値を直接入力する | 任意 |
補足
↑ ↓ での増減と直接入力は、<input type="number"> なら自動です。 自作(role="spinbutton")の場合は keydown で ↑↓ を自前実装します。
必要な WAI-ARIA / ロール
| 付ける場所 | 属性 / ロール | 意味 |
|---|---|---|
| ネイティブ number | <label for> で関連付け | 入力欄に名前を与える。role・値・範囲・増減は自動。 |
| 自作の本体 | role="spinbutton" | 「スピンボタンである」と支援技術へ伝える。 |
| 自作の本体 | tabindex="0" | キーボードでフォーカス可能にし、↑↓ を受け取れるようにする。 |
| 自作の本体 | aria-valuemin / aria-valuemax | 選べる範囲(最小・最大)。 |
| 自作の本体 | aria-valuenow | 現在値。増減のたびに必ず更新する。 |
| 自作の本体 | aria-label / aria-labelledby | 何の数を選ぶスピンボタンかの名前。 |
| 増減ボタン | <button> + aria-label="増やす/減らす" | + − は本物の button にし、記号だけの場合は名前を補う。 |
実装:推奨パターン(Good)
良い例 / 推奨
まずは <input type="number">。label を結ぶだけで、role・値・範囲・キーボード操作が揃います。
マークアップ:
<label for="qty">数量</label>
<input
type="number"
id="qty"
name="qty"
min="1"
max="10"
step="1"
value="1" />デザイン上どうしてもネイティブが使えないときだけ、role="spinbutton" で自作します。 + − は本物の <button> にし、本体は ↑↓ に対応させます。
<!-- ネイティブが使えない見た目要件のときだけ自作する -->
<span id="ppl-label">大人の人数</span>
<div class="spin">
<button type="button" data-dec aria-label="減らす">−</button>
<div role="spinbutton"
tabindex="0"
aria-labelledby="ppl-label"
aria-valuemin="1"
aria-valuemax="9"
aria-valuenow="2"
id="ppl-spin">2</div>
<button type="button" data-inc aria-label="増やす">+</button>
</div>const spin = document.getElementById('ppl-spin');
const MIN = 1, MAX = 9, STEP = 1;
function setValue(next) {
const v = Math.min(MAX, Math.max(MIN, next));
spin.setAttribute('aria-valuenow', String(v));
spin.textContent = String(v); // 見た目も同期
}
document.querySelector('[data-inc]').addEventListener('click', () =>
setValue(Number(spin.getAttribute('aria-valuenow')) + STEP)
);
document.querySelector('[data-dec]').addEventListener('click', () =>
setValue(Number(spin.getAttribute('aria-valuenow')) - STEP)
);
// spinbutton 本体にフォーカスして ↑↓ でも増減できるように
spin.addEventListener('keydown', (e) => {
const now = Number(spin.getAttribute('aria-valuenow'));
if (e.key === 'ArrowUp') { e.preventDefault(); setValue(now + STEP); }
if (e.key === 'ArrowDown') { e.preventDefault(); setValue(now - STEP); }
if (e.key === 'Home') { e.preventDefault(); setValue(MIN); }
if (e.key === 'End') { e.preventDefault(); setValue(MAX); }
});アンチパターン(Bad)
下はテキスト入力と <div> の + − で作ったものです。マウスでは押せますが、キーボードでは増減できず、値も範囲も読み上げられません。
試してみよう:Tab を押しても + − ボタンにフォーカスできません。スクリーンリーダーでは「スピンボタン」とも現在値とも分かりません。
<!-- ❌ アンチパターン:text input + div の + − -->
<span>数量</span>
<div class="spin">
<div class="btn" onclick="dec()">−</div> <!-- div なのでフォーカス不可 -->
<input type="text" id="qty" value="1"> <!-- ラベル無し・数値でない -->
<div class="btn" onclick="inc()">+</div>
</div>
<!--
role="spinbutton" も aria-valuenow/min/max も無い。
+ − が div なのでキーボードで押せず、値も範囲も読み上げられない。
-->悪い例 / 避ける
この実装の問題点:
- キーボードで増減できない — + − が
divでフォーカスも押下もできない。 - 役割が伝わらない —
role="spinbutton"が無く、スピンボタンと認識されない。 - 値・範囲が伝わらない —
aria-valuenow / valuemin / valuemaxが無い。 - 名前が無い —
label未関連付けで、何の数か分からない。
実装チェックリスト
- まず
<input type="number">を検討した(最優先) - 入力欄/本体に名前がある(
<label for>またはaria-label(ledby)) - + − は本物の
<button>で、キーボードで押せる - ↑ ↓ で値を増減できる
- 自作時は
role="spinbutton"+aria-valuemin/max/nowがある - 増減のたびに
aria-valuenowを更新している - 範囲外に増減しない(最小・最大で止まる)
- フォーカスが見える(フォーカスリングを消していない)