Product Craft Bible
Number Input & Stepper
Inicio Formularios e Inputs Number Input & Stepper
Formularios e Inputs

Number Input & Stepper

8 reglas GOV.UK Design System (text input) · GOV.UK Technology Blog 2020 · MDN input/numberGOV.UK Technology Blog 2020 (HTML spec) · GOV.UK Design System (text input)Baymard Institute (Cart & Checkout Usability)WCAG 2.5.5 / 2.5.8 · Apple HIG (44pt) · Material Design 3 (48dp) · Nielsen Norman (touch-target size)
46

Number Input & Stepper

8 reglas
427

Usa type="text" inputmode="numeric" en lugar de type="number"

El control nativo <input type="number"> arrastra problemas de usabilidad y accesibilidad que rara vez compensa: el scroll del ratón sobre el campo incrementa el valor por accidente, Chrome descarta letras en silencio sin avisar al usuario, NVDA lo anuncia como campo sin etiqueta y Dragon NaturallySpeaking no lo reconoce. Por eso GOV.UK migró toda su plataforma de millones de usuarios a type="text" con inputmode="numeric": ese par activa el teclado numérico en móvil sin el comportamiento del spinbutton nativo. Reserva type="number" solo para campos genuinamente incrementables con rango acotado donde tu research lo justifique.

GOV.UK Design System (text input) · GOV.UK Technology Blog 2020 · MDN input/number
Preferir
type=text + inputmode
<input
  type="text"
  inputmode="numeric"
  pattern="[0-9]*"
  autocomplete="off">
Teclado numérico en iOS y Android
Sin scroll-hijack accidental
Compatible con NVDA y Dragon
Evitar
type=number nativo
<input
  type="number">

// spinner nativo + flechas
Scroll cambia el valor por error
Chrome descarta letras sin feedback
NVDA lo lee sin etiqueta
428

Cinco categorías de datos nunca deben usar type="number"

La spec HTML reserva type="number" para "números incrementables". Los datos que parecen numéricos pero no se incrementan deben ir en type="text" con el inputmode adecuado. Con type="number", los navegadores intentan redondear cifras grandes y las convierten a notación exponencial, y truncan los ceros a la izquierda. Las cinco categorías que nunca deben usarlo son: tarjetas de crédito (16 dígitos), números de documento o pasaporte (con ceros iniciales), códigos postales (muchos empiezan por 0), teléfonos (espacios, guiones, +) y cualquier número muy grande con riesgo de redondeo silencioso.

GOV.UK Technology Blog 2020 (HTML spec) · GOV.UK Design System (text input)
Preferir
Número de tarjeta · type="text" inputmode="numeric"
Tarjetas Pasaporte / documento Código postal Teléfono Números grandes
Evitar
Número de tarjeta · type="number"
Safari inserta comas y rompe los 16 dígitos
429

El stepper (− valor +) solo para cantidades pequeñas y acotadas

El quantity stepper con botones − y + es el control correcto cuando el rango es pequeño (típicamente 1–10), el contexto es táctil (carrito, asientos, número de personas) y el usuario rara vez salta más de 2–3 unidades. Para cantidades grandes, precios, años o cualquier valor arbitrario, un stepper sin campo editable obliga a docenas de taps. Baymard documentó errores reales en checkout cuando el stepper no permite escribir directo: en Williams Sonoma el "1" por defecto se concatenó con la nueva entrada y guardó "21" en lugar de "2"; en West Elm el usuario debía borrar el valor antes de escribir. Un stepper puro es adecuado solo si el máximo esperado es bajo; en otro caso, añade un campo central editable.

Baymard Institute (Cart & Checkout Usability)
Preferir
Campo central editable + teclado numérico.
Escribe 24 de un toque, o ajusta con − / +.
Evitar
1
Stepper puro, sin campo editable.
Para pedir 24 unidades → 23 taps.
430

Touch target mínimo de 44×44 px en los botones − y +

Los botones de incremento y decremento son targets pequeños que exigen precisión; por debajo de cierto tamaño disparan errores y rage taps. WCAG 2.5.5 (AAA) pide al menos 44×44 px y WCAG 2.5.8 (AA) fija el mínimo absoluto en 24×24 px. Las guías de plataforma convergen en el límite operativo más alto: Apple HIG recomienda 44×44 pt y Material 3, 48dp de altura. El área táctil puede extenderse con padding sin alterar el tamaño visual del botón, y conviene dejar al menos 8 px de separación entre − y + para que el dedo no active el botón contrario.

WCAG 2.5.5 / 2.5.8 · Apple HIG (44pt) · Material Design 3 (48dp) · Nielsen Norman (touch-target size)
Preferir
3
44 × 44 px · gap 8px
Apple HIG 44pt · Material 3 48dp
Evitar
3
24 × 24 px · sin separación
Mínimo absoluto WCAG, no objetivo táctil
431

Edición directa del campo; selecciona todo el contenido al hacer focus

Un stepper que solo acepta los botones − y + frustra al usuario cuando necesita saltar valores. El campo central debe ser editable y debe seleccionar todo su contenido en el evento focus (vía input.select()), eliminando la necesidad de borrar el valor existente antes de escribir. Baymard observó que no hacerlo es una de las causas más frecuentes de errores de cantidad en checkout: en West Elm el usuario tenía que borrar primero, y en Williams Sonoma el default "1" se concatenó con la nueva cifra produciendo "21" en vez de "2". Pre-seleccionar el valor permite sobrescribir de inmediato.

Baymard Institute (Cart & Checkout Usability)
Preferir
1
Click → el "1" queda seleccionado.
Escribe "24" y reemplaza al instante.
onfocus="this.select()"
Evitar
1
Click → cursor al final del "1".
Hay que borrar antes de escribir.
Resultado típico: "21" en vez de "2".
432

Desactiva el botón en el límite y comunícalo con texto, no solo color

Cuando el usuario alcanza el mínimo o el máximo, el botón correspondiente debe desactivarse con disabled real (o aria-disabled="true"), no solo con una opacidad menor. Deshabilitar únicamente con CSS deja al usuario de tecnología asistiva sin saber por qué el botón no responde. El estado del límite debe comunicarse también por texto: una nota visible o un aria-describedby en el campo que indique el rango ("Mínimo: 1 unidad", "Rango: 1 a 99"). Así el lector de pantalla anuncia "disminuir, botón, desactivado" y el usuario entiende el porqué.

WAI-ARIA APG (spinbutton pattern) · MDN ARIA spinbutton role
Preferir
1
disabled Mínimo: 1 · campo anuncia "Rango 1 a 99"
Evitar
1
Solo opacidad: el lector de pantalla no avisa
433

Custom stepper: role="spinbutton" con foco solo en el campo

Un stepper construido con divs o botones no semánticos necesita el rol ARIA correcto para que la tecnología asistiva lo entienda como control de rango. MDN recomienda usar el <input type="number"> nativo con min/max siempre que sea posible; el rol custom es solo para cuando el HTML semántico no alcanza. En ese caso, el campo central lleva role="spinbutton" con aria-valuenow, aria-valuemin, aria-valuemax y un nombre accesible. El foco de teclado reside únicamente en el campo: los botones − y + llevan tabindex="-1" y se accionan con clic o con las flechas (↑/↓ ±1, Home/End van a min/max).

MDN ARIA spinbutton role · WAI-ARIA APG (spinbutton pattern)
Preferir
stepper.html
<!-- Botones fuera del tab order -->
<button tabindex="-1" aria-label="Disminuir">−</button>

<input role="spinbutton"
  aria-valuenow="3"
  aria-valuemin="1"
  aria-valuemax="99"
  aria-labelledby="qty-label"
  type="text" inputmode="numeric">

<button tabindex="-1" aria-label="Aumentar">+</button>
Evitar
stepper-roto.html
<!-- Sin roles, todo en el tab order -->
<div class="stepper">
  <div tabindex="0">−</div>
  <span>3</span>
  <div tabindex="0">+</div>
</div>

// El AT no sabe que es un control numérico.
// El valor "3" nunca se anuncia.
434

Para decimales usa inputmode="decimal"; nunca asumas el separador

El separador decimal varía por locale: punto en EE.UU. y Reino Unido, coma en España, México y Alemania. <input type="number"> no controla este comportamiento de forma consistente entre navegadores y puede rechazar entradas válidas. Con type="text" inputmode="decimal" el teclado muestra la tecla decimal nativa de la plataforma y acepta cualquier formato, dejando el parseo a tu código: normaliza tanto "," como "." antes de parseFloat(), sin asumir el locale del navegador. Para mostrar valores formateados (no editar) usa Intl.NumberFormat en vez de manipular strings. Para entrada de negativos opcionales, GOV.UK advierte que algunos teclados táctiles bloquean el signo, así que usa type="text" sin inputmode.

GOV.UK Design System (text input) · MDN input/number
Preferir
en-US
tecla .
es-MX / de-DE
tecla ,
inputmode="decimal" muestra la tecla decimal nativa.
Normaliza "," y "." antes de parseFloat().
Evitar
type="number"
entrada rechazada
El usuario es-MX escribe "1.250,50" y el
navegador lo descarta por separador "incorrecto".