Siempre intentando aprender. Amante de los gatos, las buenas conversaciones y los pequeños detalles.
Algunas ideas erróneas sobre los botones y los enlaces en las SPA
Cuando tenemos una SPA, o cualquier aplicación que se ejecute en un entorno de tipo navegador, no solemos prestar atención a los enlaces y botones más allá de la parte visual; la mayoría de los kits de interfaz de usuario (UI kits) los incluyen como dos componentes distintos: HeroUI Button y HeroUI Link.
Como puedes ver, ambos tienen un aspecto diferente y, a primera vista, supongo que sabes cuándo usar uno y cuándo el otro. ¿Seguro?
¿Qué son los botones y los enlaces?
Según el estándar ARIA del W3C, «un botón es un widget que permite a los usuarios activar una acción o evento, como enviar un formulario, abrir un cuadro de diálogo o cancelar una acción».
En cuanto al enlace: «un widget de enlace proporciona una referencia interactiva a un recurso».
Los enlaces (mediante una etiqueta de ancla, a) también pueden contener cualquier otro elemento en su interior (excepto otra a), pero los botones solo deberían contener algunos elementos de línea según el estándar: strong, em, span, small, br, img, svg, canvas, etc.
Pero más allá de eso, el estándar no define el aspecto que debe tener un botón o un enlace; el estándar define el significado semántico y el comportamiento.
Los vemos de forma diferente porque el navegador aplica la «hoja de estilos del agente de usuario»: el CSS predeterminado que el navegador aplica a las etiquetas. Por eso un botón (sin ningún CSS de usuario) se ve diferente según el navegador o el dispositivo.
Un caso de uso común
Imagina este ejemplo, que representa la página de detalle de un elemento. En la cabecera tenemos los botones de acción: las flechas permiten navegar al elemento anterior y siguiente, un botón para eliminar el elemento y otro para añadir uno nuevo.
¿Pero estás seguro de que todos deberían ser botones? Tienen aspecto de botones, pero no todos deberían comportarse como tales según la definición del W3C.
Un error típico es realizar la navegación mediante código: <button onClick="navigateTo('/item/23')">.
Parece que funciona, pero estás perdiendo mucho y degradando la experiencia del usuario:
El usuario no puede usar el botón central del ratón para abrir el enlace en una pestaña nueva.
El botón de retroceso del navegador no volverá a la página anterior (esto dependerá de cómo tu sistema de rutas añada la navegación al historial).
El usuario no puede hacer clic derecho y compartir el enlace.
En general, pierdes cualquier comportamiento que un enlace nativo pueda proporcionar.
«Pero eso parece un botón...»: semántica frente a apariencia en botones y enlaces
Hablar de un botón o un enlace no se reduce a una sola cosa: podemos hablar del comportamiento o del aspecto visual. Esto es importante. Volviendo al ejemplo:
Los botones de flecha son visualmente botones, pero semánticamente enlaces.
El botón «Eliminar» es visual y semánticamente un botón, ya que activa una acción.
El de «Añadir nuevo» depende; si navega a una página nueva o cambia la URL para forzar la apertura de un desplegable, debería ser semánticamente un enlace.
Podrías envolver el botón en una etiqueta de ancla, pero en mi opinión no es la solución ideal.
Una solución
La solución que me gusta implementar en los sistemas de diseño que creo o mantengo es tener un componente interno Action que utilice <button> o a (o la etiqueta de rutas del framework que utilices) como contenedor de contenido dependiendo de las propiedades (props) pasadas:
Si la prop to (o href para enlaces puros) está definida, significa que queremos navegar, por lo que debería ser semánticamente un enlace; de lo contrario, debería ser un botón.
Y en cuanto a la parte visual, también es útil proporcionar variantes visuales a los enlaces al igual que a los botones: primary, secondary, danger, etc.
Para este componente interno exponemos una prop para decidir si el aspecto visual debe ser como el de un botón o el de un enlace (render-as).
Este componente debería aceptar más props relacionadas con el comportamiento de botón o de enlace, así que si estamos usando TypeScript podemos usar uniones discriminadas:
type ActionProps = {
// Aquí las props comunes
} & (
| {
href: string
target?: '_blank' | 'self' | '_parent' | '_top'
rel?: string
} | {
type?: ButtonType
href?: never
target?: never
}
)
La idea es proporcionar el mismo comportamiento para el contenido del botón y del enlace, por ejemplo iconos, estado de carga, deshabilitado, etc.
Teóricamente puedes crear un comportamiento de botón con la apariencia de un enlace, pero puede resultar confuso para los usuarios, así que prefiero evitar este caso exponiendo dos componentes públicos:
Un componente
Buttonque siempre se renderiza visualmente como un botón, pero puede comportarse como un enlace dependiendo de la presencia de la proptoohref.Un componente
Linkque se comporta y se renderiza visualmente como un enlace.
Como he mencionado, ambos componentes son solo un intermediario (proxy) que limita las props expuestas y las conecta con el componente interno Action.
Accesibilidad
La idea es proporcionar el mismo comportamiento para el contenido del botón y del enlace, por ejemplo iconos, estado de carga, deshabilitado, etc.
Teóricamente puedes crear un comportamiento de botón con la apariencia de un enlace, pero puede resultar confuso para los usuarios, así que prefiero evitar este caso exponiendo dos componentes públicos:
Un componente
Buttonque siempre se renderiza visualmente como un botón, pero puede comportarse como un enlace dependiendo de la presencia de la proptoohref.Un componente
Linkque se comporta y se renderiza visualmente como un enlace.
Como he mencionado, ambos componentes son solo un intermediario (proxy) que limita las props expuestas y las conecta con el componente interno Action.
Comentarios
Aún no hay comentarios. ¡Sé el primero en comentar!