フォーム・入力

スピンボタン

解説あり

Spinbutton

数値の増減入力。上下キーとボタンでの増減、範囲の通知。

スピンボタンとは?

スピンボタンは、+ / −(または ↑ / ↓)で値を1つずつ増減して、 ある範囲の中から数を選ぶ UI です。数量・人数・階数の指定などでよく使われます。

スライダーと似ていますが、スピンボタンは「飛び飛びの値を細かく正確に指定する」のが得意です。 伝えるべきことは同じで、現在値範囲(最小・最大)、そしてキーボード操作です。

なぜアクセシビリティが大事なの?

これを一番カンタンに満たすのが、ネイティブの <input type="number"> です。 増減ボタン・キーボード操作・読み上げをブラウザが提供します。

ライブデモ(推奨実装)

下はネイティブの <input type="number"> です。マウスを使わず、フォーカスして で変えてみてください。

アクセシブルなスピンボタン(ネイティブ 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> の + − で作ったものです。マウスでは押せますが、キーボードでは増減できず、値も範囲も読み上げられません。

text input + 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 未関連付けで、何の数か分からない。

実装チェックリスト


原文(英語):Spinbutton Pattern — W3C APG(新しいタブで開きます)