en HTML5

Input type number html

Input type number es uno de esos estándares cuya implementación actual en navegadores es algo incosistente y controvertida hasta el punto de considerarse el «peor input type» con multiplicidad de artículos y vídeos respaldando esa idea y explicando el porqué de esa afirmación.

Hoy vamos a desmentir que no es tan malo como parece y a construir tres componentes numéricos, dos con type number + otro de regalo 😉
Campos input type number con botones de control + y -

¿Es number realmente el peor input type?

Parece que la realidad es que todas esas acusaciones no son más que muchos usuarios hacíendose eco en las distintas plataformas (reddit, YouTube, etc) de un único artículo: «Why the number input is the worst input», publicado en stackoverflow. Un artículo publicado en tono «hater» que considero no merece siquiera un enlace, porque es del tipo de artículos que no aportan nada positivo a la comunidad. Personalmente prefiero los que plantean soluciones a los que sólo buscan hacerse virales.

Si es o no es el peor input type no lo sé, habría que determinar unos baremos y criterios comunes para evaluar todos los tipos en su conjunto y ver cuáles suman más puntos positivos y negativos en las distintas áreas, como accesibilidad, usabilidad, validación, soporte en navegadores, etc… No podemos basarnos en un único artículo que se ha hecho viral, fruto de la frustración que sufrió un tipo cuando quiso trabajar con él y fue incapaz de resolver algunos de los retos que plantea su uso.

Enfrentándonos a input type number

Las razones que argumenta el susodicho artículo se centran en cuatro puntos:

  1. Type number devuelve cadenas en blanco cuando se introduce un valor invalido.
  2. Soporta más que dígitos, por ejemplo la notación científica e, pero también símbolos + y -, indicadores de si el valor es positivo o negativo.
  3. Los atributos Min y Max pueden ser esquivados por ejemplo, copiando y pegando un valor.
  4. Y apunta también a una validación inconsitente entre los distintos navegadores.

En realidad los cuatro se pueden resumir en uno: una validación algo permisiva del estándar. Algo común en los types de HTML5 que ya hemos visto en otros artículos como input type url o input type email. Pero el problema no es tan malo como lo pintan.

Algún usuario menciona también cosas como que Dragon Naturaly Speaking, un conocido lector de documentos, no permite el dictado ni la selección… -¿En serio?-. Eso claramente es un fallo del lector y no del estándar en sí.

Controles inconsistentes

Por otro lado, nos encontramos también con la cuestión de sus controles. 

  1. Las flechitas que implementan los navegadores son dependientes del ratón, pues aparecen al pasar por encima. También al hacer foco… pero en ese caso no son más que un indicador de que nos encontramos ante input type number y no otro tipo de campo. Pues si navegas mediante el teclado no es que puedas activarlas. En su lugar podemos usar las flechas «Arriba» y «Abajo» del teclado.
  2. Y todo esto en referencia a navegadores de scritorio, porque en mobile ni tenemos ratón ni tampoco flechas visuales en el propio input number, ni en el teclado.
  3. Por otro lado, según vemos en caniuse.com, su comportamiento es inconsistente entre los navegadores. Ésto se refiere al hecho de que algunos navegadores no soportan las funciones increment/decrement ni mediante las flechas de la UI, ni las del teclado, haciéndolas del todo inútiles. Y para más INRI, en algunos el UI widget tampoco soporta step, max o min.
  4. Y finalmente, está la cuestión del aspecto de la UI, que es diferente también para cada navegador y de seguro no se adecúa para nada a la mayoría de diseños modernos.

Usabilidad de la UI

Además de lo comentado hasta ahora, incluso cuando dispones de un ratón para navegar, el tamaño de las flechas es ciertamente insuficiente para un manejo rápido y eficiente. Sí me gusta el hecho de que estén juntas, pues permite el uso más rápido, pero más vale que tengas un buen ratón y dedos hábiles, porque si no, te costará manejarlas debido a su reducido tamaño.

Optimizando input type number

Vamos a abordar entonces esos problemas que de forma tan catastrofista expuso el mencionado artículo.

Validación de type number

En primer lugar, si los valores invalidos dan problemas, evitemos que puedan ser introducidos. Si lo que necesitamos es un simple contador, tanto el número e como introducir valores a mano parece poco útil. Si necesitásemos introducir valores altos, pongamos por ejemplo un campo de formulario bancario, podríamos acotar mediante los atributos min max las cantidades posibles ya que e sólo aparecerá con numeraciones extremadamente altas. Podría introducirse a mano, pero realmente ¿alguien se cree que nadie va a introducir una notación exponencial fuera del ámbito científico en un entorno web? Sinceramente me sorprendería mucho.

Contador numérico con imput type number

Bien, para solucionar el problema anterior sólo tenemos que convertir nuestro input en un campo de sólo lectura usando el atributo readonly. De esa forma el usuario no podrá ni escribir ni pegar nada dentro. De hecho ésto directamente inutilizará las flechas de la UI que ya no aparecerán (aunque seguirán ocupando espacio).

Esto soluciona de un plumazo el resto de problemas debidos al inconsistente funcionamiento y estétitca del widget UI. Queda pues solucionar cómo introducir valores.

Botones personalizados para type number

La solución os va a resultar sorprendentemente sencilla. En primer lugar, añadimos dos botones con una simples funciones javascript y seguidamente, mediante los atributos step, max y min, controlamos los valores que permitiremos. Veamos el código:

<div>
<button type="button" onclick=numberFLD.stepDown()>-</button>
<input id="numberFLD" type="number" step="1" min=0 max=99 readonly />
<button type="button" onclick=numberFLD.stepUp()>+</button>
</div>

En el ejemplo he usado step=1, pero podríamos usar 10, 50, o lo que queramos. Eso hará que las funciones stepDown y stepUp reduzcan y/o aumenten el valor de 10 en 10 o lo que hayamos espeficado en el atributo step.

Nuestros botones dinamizados con JS soportan sin problema los tres atributos, por lo que ya no hay de qué preocuparse. Además las las flechas del teclado obedecerán también al valor asignado a step.

En cuanto a los estilos estos serían los únicos necesarios para hacer desaparecer la UI del navegador. El resto a vuestro gusto:

input {
-moz-appearance: textfield;
text-align: center;
}
[type=number]::-webkit-inner-spin-button {
appearance: none;
}

Vemos que para Firefox tenemos que decirle que tome el aspecto de un textfield. Sin embargo, esto no transforma el campo, sólo lo disfraza. Sigue siendo un campo numérico. En los webkit sencillamente escondemos el shadow DOM de las flechitas.

Campo numérico para valores largos

En éste la introducción de valores ya no es méramente incremental, sino que el usuario debe/puede introducirlos a mano, aunque podemos reciclar el código anterior y simplemente eliminar el atributo readonly, conservando la posibilidad de incrementar el valor mediante los botones (- | +).

Hecho esto, tendremos que solucionar el problema que supone que el ususario pueda pegar contenido. No es complicado, sólo tendremos que añadir onpaste=»return false»;

<input id="number2FLD" type="number" onpaste="return false;" />

No permitiendo el pegado, la incidencia de poder saltarse los valores min y max, desaparece.

iOS Bug: en versiones antiguas de Safari el campo permitía la introducción de carácteres no numéricos. Esto ya no es así en las nuevas versiones.

Campos numéricos con valores negativos

Type number soporta los símbolos + y – delante de la cifra introducida pero no validará cuando no precedan a ésta. Por lo que ahí está todo correcto. Obviamente, podremos escribirlos manualmente donde queramos, pero haciendo usos de :valid e :invalid de CSS, podremos indicar al usuario si el valor introducido es o no correcto mediante los estilos del campo.

Si queremos evitar que se introduzcan valores negativos deberemos incluir el atributo min=0. De esa forma no validarán aunque el usuario pueda escribirlos.

Si necesitamos un campo que permita otros tipos de numeraciones como teléfonos, disponemos de type tel y type text que, combinados con el attributo pattern os permitirán introducir lo que queráis de forma controlada y validable. Podéis ver cómo al final del artículo.

Campo con decimales

Type number también permite números con decimales, pero sólo si se lo indicamos mediante el atributo step. El número de decimales dependerá de cuántos ceros añadamos. El campo no validará si introducimos más decimales de los indicados. Si asignamos el valor any no habrá restricciones al número de decimales, pero tampoco funcionarán las funciones step.

<input id="number2FLD" type="number" value="" step=".01" min="0" onpaste="return false;" />

Las funciones stepUp y stepDown tomarán el valor de step para incrementar o disminuir el valor del campo. Si quisiéramos que los botones utilicen un valor entero pero permitiendo decimales, podemos compensarlo pasándo un parámetro que multiplique el valor de step.

<button type="button" onclick=number2FLD.stepDown(100)>-</button>
<button type="button" onclick=number2FLD.stepUp(100)>+</button>

Por supuesto, podríamos crear una función más compleja que nos permitiese hacer combinaciones de teclas como Ctrl o Shift que, tanto mediante nuestros botones como con las flechas del teclado, multiplicasen el valor de Step, logrando incrementos de 1,10,100… En ese caso sería importante incluir algún tooltip de ayuda o similar, que indique al usuario las opciones.

Cómo último detalle relativo a los decimales, type number considera a ambos, «,» y «.», sepradores decimales. Es por ello que las zifras largas no los incluyen cada tres ceros, como separadores de millares. Ni tampoco permite escribir más de uno de ellos. No es que valide falso, es que bloquea cualquier intento de introducir un segundo separador (aunque no sea el mismo).

Nota: Firefox soporta variaciones linguísticas en la representación, esto afecta a los dígitos, pero también al separador. Sin embargo, no los restringe a uno u otro a la hora de escribirlos, sino que sólo los representa con el que sería el separador por defecto en el idioma indicado cuando toma el valor  del atributo value. Es más, si en lugar de usar los controles (o las flechas del teclado), modificamos el valor manualmente con un teclado, aún con éste configurado en el idioma correcto, introducirá números estándar.

Campo numérico con valor 0,01 en árabe

Campo numérico con valor 0,01 en árabe

Por ello desaconsejo el uso del atributo lang en conjunto con input type number a menos que sea readonly

Numeración extremadamente larga

Cuando tenemos numeraciones que superan el millón, esa carencia de separadores de los millares empieza a resultar un problema de usabilidad. Mi recomendación aquí, sería usar una máscara para añadir puntos o comas como separadores. Os dejo un enlace (inglés): https://css-tricks.com/input-masking/

También es recomendable controlar el valor máximo, especialmente si la base de datos tiene restringidos los caracteres. Type number no acepta maxlength pero sí max, que nos permite no sólo definir la longitud del string, sino también su valor. De esa forma si, por ejemplo, se usa una notación exponencial cuyo resultado es mayor que el valor de max, la validación devolverá falso.

Mensaje de validación para type number

Esta parte es importante. Puesto que type number es altamente configurable, deberemos indicar al usuario cómo puede/debe usarlo.

<ul class="errorMSG">
<li>Introoduzca un valor entre 0 y 1 millón.</li>
<li>Máximo 2 decimales</li>
<li>Soporta la notación exponencial "e" pero rogamos no se use</li>
</ul>

El tercer punto es probablemente innecesario o incluso contraproducente, porque no creo que a ningún usuario se le pueda ocurrir que pueda usar dicha notación. Sólo en la extraña circunstancia que introduzca un número muy largo y repetitivo, y use los controles para modificar el valor, si detecta que puede trasformarlo en un valor con exponente, el campo automáticamente convertirá el valor introducido, usando la notación exponencial «e».

Number REGEX pattern

Bien, lo primero: type number no soporta pattern. Pero hemos visto que su validación es correcta una vez sabes usarlo, entonces ¿para qué querríamos usar una REGEX? Combinado con input type number su uso es imprescindible para validar on submit el valor del campo vía JS o incluso serverside. La validación vía CSS puede ser manipulada, por lo que no es fiable más allá de comunicar al usuario la validez del campo.

Campos alfanuméricos

El rol de type number es spinbutton, es decir, es un campo pensado para incrementar y disminuir su valor dentro de un rango definido. Cuando no es necesaria esa funcionalidad, y/o carece de sentido, como en aquellos casos que requieren algo más que números, por definición type number no sería la mejor opción.

Es el caso de algunos campos alfanuméricos cuyos valores son principalmente numéricos pero que deben permitir otros símbolos (coordenadas, IBAN, códigos en general…). 

Pongamos un ejemplo. Digamos que queremos permitir secuéncias numéricas separadas por guiones y seguidas por dos dígitos de control opcionales que separaríamos con una coma:

<input id="number3FLD" pattern="^[1-9](?:\d(-)?){0,11}(,[\d]{1,2})?$" />

Nota: No he incluido atributo type, ya que por defecto el valor es text.

Teclado virtual numérico en dispositivos móviles

Pattern nos da esa flexibilidad que no nos da type number, pero al ser type text, su inputmode por defecto también será text. Esto implica que en móviles el teclado virtual ofrecido será alfanumérico.

En iOS, si usamos ciertos patterns como [0-9]* o \d*, automáticamente se ofrecerá el teclado numérico, pero si queremos una solución multiplataforma ( iOS + Android) que nos permita REGEX más avanzadas, deberemos usar inputmode decimal. Inputmode numeric, paradójiamente sólo nos ofrecerá el teclado numérico en Android.

<input id="number3FLD" pattern="^[1-9](?:\d(-)?){0,11}(,[\d]{1,2})?$" inputmode="decimal" />
teclados virtuales numéricos de Android y iOS

Imagen tomada de CSS-Tricks.com

Deberemos asegurarnos que el teclado ofrecido nos permite introducir todos los carácteres válidos definidos por la REGEX. Si necesitasemos espacios o guiones, inputmode numéric sería la opción adecuada, ya que el teclado numérico de iOS, como véis no los ofrece.

Podéis probar las tres soluciones en el siguiente CODEPEN:

See the Pen
Type number custom controlers
by Daniel Abril (@elcssar)
on CodePen.

Soporte en navegadores de type number

 Navegador IE/
EDGE
Firefox Chrome Opera Safari Webkit Mobile
Type numer  V11+*/Sí Sí*** Sí**
Inputmode  No/Sí

*No soporta el incremento/disminución del valor vía flechas, sean las del UI o las del teclado.

**Lo mismo que el punto anterior pero además el UI widget no toma en cuenta los atributos «step», «min» o «max».

***Firefox no soportan datalist. Tampoco en su versión Mobile a la que añadiríamos el primero punto.

Conclusión

En primer lugar vemos que con la solución dada en este artículo, ninguna de estas incompatibilidades serán un problema ya que prescindimos del UI widget nativo.

Tampoco lo serán los valores incorrectos ni poder esquivar los límites impuestos por min y max porque no permitimos el pegado y hemos usado CSS para mostrar al usuario cuándo el valor introducido es incorrecto.

Si bien FF y Safari permiten escribir texto, la validación CSS también soluciona ese problema. Y si aún nos preocupa que se pueda enviar un valor erróneo, usando una REGEX on submit podemos hacer una última validación.

Al final tenemos tres opciones perfectamente válidas y funcionales para trabajar con números, aprovechándo las ventajas del input type number de HTML5, que nos aporta atributos y funciones muy útiles con las que podemos construir nuestro propio componente totalmente funcional de forma sencilla y conservando la semántica.

Espero que os haya gustado. Recordad la importancia de mantener una actitud positiva antes los retos y cuando tengáis dudas preguntad a El CSSar 😉

Si no os queréis perder las novedades, podéis suscribiros y os llegará un correo cuando haya nuevas publicaciones o actualizaciones de artículos anteriores, totalmente libre de SPAM ;).

Escribe un comentario

Comentario