Módulo 1: What A Design System Is

La accesibilidad como propiedad del sistema

Descripción

La accesibilidad —que una interfaz la pueda usar todo el mundo, incluidas las personas que navegan con teclado, con lector de pantalla, o con baja visión— suele enseñarse como una lista de tareas: "acuérdate de poner contraste suficiente", "acuérdate del foco visible", "acuérdate del label". Y como lista de tareas, falla, porque depende de que cada persona, en cada pantalla, recuerde hacer cada cosa. Con suficientes pantallas y suficientes personas, alguien olvidará algo, y aparecerá el bug de accesibilidad que nadie ve hasta que un usuario no puede completar su compra.

Esta lección propone un cambio de marco: la accesibilidad no debería ser una tarea que cada pantalla recuerda, sino una propiedad que el sistema garantiza. Cuando el contraste está en los tokens, el foco visible está en el componente Button, y el label está en el componente SearchBar, entonces usar la pieza del sistema ya te da la accesibilidad —gratis, sin recordarla—. Se resuelve una vez, en la pieza, y se hereda en cada uso. Es exactamente la propiedad de la lección 5 ("cambia en un lugar, propaga a todos") aplicada a algo que importa muchísimo: que la UI sea usable por todos.

Conexión con el módulo. La lección 5 mostró que una única fuente de verdad hace que un cambio se propague a toda la UI. Esta lección aplica ese mismo mecanismo a la accesibilidad: si la resuelves en la pieza (el token, el componente), se propaga a todos sus usos. Es también la culminación del "por qué" del módulo: dijimos que la consistencia, la mantenibilidad y la accesibilidad son resultados del sistema, no metas que se persiguen aparte. Aquí se ve la tercera. La lección 7 armará la anatomía completa de Mercado, donde la accesibilidad ya vive dentro de las piezas. (El detalle fino —el algoritmo WCAG de contraste, todo ARIA— es el módulo 4 y la guía de accesibilidad; aquí instalamos el principio.)

Una analogía: la rampa en el edificio contra la ayuda caso por caso

Imagina un edificio público con escalones en la entrada. Hay dos formas de que una persona en silla de ruedas pueda entrar.

La forma caso por caso: no hay rampa, pero hay un timbre que dice "si necesita ayuda para subir, llame y alguien vendrá". Cada vez que llega una persona en silla de ruedas, depende de que alguien esté disponible, dispuesto y presente para ayudarla a subir los escalones. A veces funciona. A veces el encargado salió, o no oyó el timbre, o es domingo. La accesibilidad existe "en teoría", pero se resuelve —o no— en cada visita individual, y depende de que alguien recuerde y pueda ayudar. Es frágil, humillante y poco confiable.

La forma estructural: el edificio tiene una rampa. No hay que llamar a nadie, no depende de que alguien esté disponible, no se decide en cada visita: la rampa está ahí, y cualquiera que llegue —hoy, mañana, un domingo, a las 3 de la mañana— puede entrar por sí mismo. La accesibilidad no es un favor que se pide; es una propiedad del edificio. Se construyó una vez, y sirve para todas las visitas, para siempre, sin que nadie tenga que recordar nada.

La rampa es el sistema de diseño; el timbre es la lista de tareas. Poner el contraste, el foco y los roles dentro de las piezas del sistema es construir la rampa: la accesibilidad deja de depender de que cada programador, en cada pantalla, recuerde el favor, y pasa a estar garantizada por la estructura. Y como toda propiedad estructural, tiene el beneficio de la lección 5: si mejoras la rampa (arreglas el contraste en el token), todas las entradas mejoran a la vez. Guarda la imagen: sistemas que tienen rampas, no timbres.

De cerca: dónde vive la accesibilidad en cada capa

Lo poderoso del enfoque de sistema es que cada capa puede cargar una parte de la accesibilidad, de modo que usar la capa te la da sin pedirla. Veámoslo capa por capa (las mismas cuatro de la lección 3):

En los tokens vive el contraste. Si tus tokens de color están elegidos de modo que color.text sobre color.surface siempre tenga contraste suficiente (el módulo 4 lo mide con el algoritmo WCAG), entonces cualquier pieza que use esos tokens hereda un contraste accesible. El contraste no se verifica pantalla por pantalla; se garantiza en la paleta. Elegir bien los pares de color una vez protege toda la UI.

En los componentes vive el foco visible, los roles y el teclado. Si el componente Button trae, de fábrica, un anillo de foco visible (para quien navega con teclado) y se renderiza como un <button> real (con su rol y su comportamiento de teclado), entonces cada uso del Button es accesible sin que quien lo usa haga nada. La accesibilidad del botón se resolvió una vez, dentro del componente, y se hereda en sus cien usos. Lo mismo con SearchBar: si trae su <label> asociado por dentro, cada búsqueda del storefront es etiquetada correctamente sin esfuerzo.

En los patrones vive la estructura de la página: que haya un <main>, que los encabezados vayan en jerarquía, que las regiones estén marcadas (esto lo viste en web-fundamentals). Un patrón site-header bien construido trae la estructura semántica correcta, y todas las pantallas que lo usan la heredan.

El principio unificador: la accesibilidad se resuelve en la pieza más baja posible, para que se herede en todos los usos. Cuanto más abajo la resuelvas (en el token, en el componente base), a más lugares protege. Es la misma lógica del flujo hacia arriba de la lección 3: lo que pones en la base sube a todo lo construido encima.

Y una regla de oro que atraviesa todo el tema, y que verás repetida en el módulo 7: usa el elemento HTML correcto antes de alcanzar ARIA. Un <button> de verdad ya trae rol, foco y teclado; un <div> disfrazado de botón no trae nada y hay que reconstruirle todo a mano (y casi siempre queda peor). El sistema hereda accesibilidad gratis justamente porque usa los elementos correctos por dentro. La primera capa de accesibilidad es el HTML semántico que ya conoces; el sistema solo se asegura de que las piezas lo usen.

Ejemplo trabajado: botones accesibles, pieza por pieza contra sistema

Pongámoslo en números. El storefront tiene botones en cinco pantallas. Vamos a comparar dos mundos: uno donde cada pantalla estiliza su botón a mano (y por tanto la accesibilidad depende de que cada una recuerde el foco y el contraste), y otro donde las cinco usan el mismo componente Button del sistema (que trae foco y contraste resueltos). Contamos cuántos botones resultan accesibles en cada mundo. Consideramos un botón "accesible" si tiene foco visible y contraste suficiente (los dos, no uno):

// L6 — accesibilidad como PROPIEDAD del sistema. Cada boton de la pantalla, si se
// estiliza a mano, puede o no traer foco visible y contraste. Un componente Button
// del sistema lo garantiza UNA vez para todos.
const adHocButtons = [
  { screen: 'home',     focusVisible: true,  contrastOk: true },
  { screen: 'catalog',  focusVisible: false, contrastOk: true },
  { screen: 'product',  focusVisible: true,  contrastOk: false },
  { screen: 'cart',     focusVisible: false, contrastOk: false },
  { screen: 'checkout', focusVisible: true,  contrastOk: true },
];

function accessible(b) {
  return b.focusVisible && b.contrastOk;
}
const okAdHoc = adHocButtons.filter(accessible).length;

console.log('=== Botones accesibles en 5 pantallas ===\n');
console.log('Ad-hoc (cada pantalla estiliza su boton a mano):');
adHocButtons.forEach((b) => {
  console.log('  ' + b.screen.padEnd(9) + (accessible(b) ? 'accesible' : 'FALLA'));
});
console.log('  accesibles: ' + okAdHoc + ' de ' + adHocButtons.length + '\n');

// Con el sistema: un solo Button, con foco y contraste resueltos, usado en las 5.
const systemButton = { focusVisible: true, contrastOk: true };
const screens = ['home', 'catalog', 'product', 'cart', 'checkout'];
const okSystem = screens.filter(() => accessible(systemButton)).length;

console.log('Con un sistema (las 5 usan el mismo componente Button):');
console.log('  accesibles: ' + okSystem + ' de ' + screens.length);
console.log('  la accesibilidad se resolvio 1 vez y vale para TODAS');

Qué esperar. Al correr el archivo, la salida es exactamente esta:

=== Botones accesibles en 5 pantallas ===

Ad-hoc (cada pantalla estiliza su boton a mano):
  home     accesible
  catalog  FALLA
  product  FALLA
  cart     FALLA
  checkout accesible
  accesibles: 2 de 5

Con un sistema (las 5 usan el mismo componente Button):
  accesibles: 5 de 5
  la accesibilidad se resolvio 1 vez y vale para TODAS

Lee el resultado como lo que es: una fotografía de por qué la accesibilidad-como-tarea fracasa y la accesibilidad-como-propiedad funciona.

En el mundo ad-hoc, solo 2 de 5 botones son accesibles. Y fíjate en el patrón de las fallas: catalog tiene contraste pero le falta el foco visible; product tiene foco pero le falta contraste; cart no tiene ninguno de los dos. Ninguna pantalla es "descuidada" a propósito —cada una acertó en algo—, pero cada una tuvo que recordar dos cosas independientes, y bastó olvidar una para fallar. Con cinco pantallas y dos requisitos cada una, hay diez oportunidades de olvido, y se materializaron varias. Así se ve la accesibilidad cuando depende de la memoria: parcial, irregular, con agujeros que nadie eligió dejar.

En el mundo del sistema, son 5 de 5. No porque los programadores fueran más cuidadosos, sino porque no tuvieron que serlo: usaron el componente Button, y el Button ya traía el foco y el contraste resueltos por dentro. La accesibilidad se resolvió una vez —al construir el componente— y se heredó en los cinco usos. Es la rampa: no depende de que cada visita pida el favor; está en la estructura. Y nota el corolario, que es el superpoder de la lección 5 aplicado aquí: si mañana descubres que el foco del Button se puede mejorar, lo arreglas una vez en el componente y los cinco usos —y los quinientos— mejoran a la vez. En ad-hoc, esa mejora serían cinco (o quinientas) correcciones manuales, con su cuota garantizada de olvidos.

Una pregunta para que la lleves adelante: el modelo dice que un botón es accesible si tiene foco y contraste. En el mundo del sistema, ¿dónde vive cada uno de esos dos requisitos? (El contraste vive en los tokens de color —el par color.primary / texto encima elegido con contraste suficiente—; el foco visible vive en el componente Button. Dos capas, dos requisitos, ambos heredados. Así reparte un sistema la accesibilidad entre sus capas.)

Un matiz: el sistema da la base, no la exime de pensar

Cuidado con leer esto como "uso un design system, entonces mi app es accesible, fin". No es así, y la honestidad importa. Un sistema te da la base —contraste en los tokens, foco y roles en los componentes— que resuelve la mayoría de los problemas mecánicos y repetitivos. Pero la accesibilidad tiene partes que ninguna pieza puede decidir por ti: el alt correcto de esta imagen (¿qué describe?), el orden lógico de foco en este flujo, si este texto es comprensible, si esta animación puede marear. Eso requiere criterio humano en cada caso.

Lo que el sistema hace —y no es poco— es quitar de la mesa los errores repetitivos para que tu atención quede libre para los que sí requieren pensar. Sin sistema, gastas tu cuidado en recordar el foco de cada botón (y aun así se te escapan, como viste: 2 de 5). Con sistema, el foco de los botones está resuelto, y puedes gastar ese cuidado en el alt de las imágenes y el flujo del checkout. El sistema no reemplaza el juicio de accesibilidad; lo concentra donde de verdad hace falta. La rampa no hace que el edificio entero sea perfecto; hace que nadie tenga que pelear la entrada, para que la energía vaya a lo demás.

Errores comunes

Tratar la accesibilidad como una fase final ("ya luego la agregamos"). Qué pasa: se construye toda la UI y se deja "hacerla accesible" para el final, como una capa de pintura. Por qué pasa: se ve como un requisito de cumplimiento, no como una propiedad estructural. Cómo detectarlo: tienes cientos de botones y campos ya construidos a mano, y "agregar accesibilidad" significa revisar cada uno —justo el trabajo que produjo el 2-de-5—. Cómo corregirlo: la accesibilidad se pone en las piezas, desde el principio, para que se herede. Un Button accesible desde su primera versión hace accesibles sus mil usos futuros sin trabajo extra; un Button que "luego se hará accesible" hereda su carencia a los mil usos y luego cuesta mil correcciones. Es infinitamente más barato construir la rampa que retro-adaptar mil timbres.

Reconstruir a mano lo que un elemento HTML ya da (el <div> disfrazado de botón). Qué pasa: se hace un botón con <div onClick=...> y se le agrega estilo, pero no rol, ni foco, ni comportamiento de teclado. Por qué pasa: un <div> es un lienzo en blanco, más fácil de estilar "como uno quiere". Cómo detectarlo: tu "botón" no se puede activar con Enter/Espacio, no aparece al navegar con Tab, y un lector de pantalla no lo anuncia como botón. Cómo corregirlo: usa el elemento correcto antes de alcanzar ARIA. Un <button> real trae rol, foco y teclado gratis; el sistema hereda accesibilidad porque sus componentes usan los elementos correctos por dentro. Reconstruir eso sobre un <div> es rehacer a mano —y peor— lo que el navegador ya te daba. La pieza del sistema debe estar construida sobre el HTML correcto; ahí nace su accesibilidad heredada.

Creer que el contraste es cuestión de gusto. Qué pasa: se elige un gris clarito sobre blanco "porque se ve elegante y minimalista", sin medir. Por qué pasa: el contraste parece una decisión estética. Cómo detectarlo: el texto se ve bien en tu monitor, en tu iluminación, con tu vista —pero una persona con baja visión, o al sol, no lo puede leer—. Cómo corregirlo: el contraste es medible, no opinable —el módulo 4 lo calcula con el algoritmo WCAG y da un veredicto AA/AAA—. En un sistema, esa medición se hace una vez al elegir los pares de tokens de color, y luego toda la UI hereda pares con contraste garantizado. La accesibilidad del color no se defiende con "a mí me parece que se lee"; se defiende con un ratio que pasa el umbral. Ponlo en los tokens y deja de adivinar.

Ejercicios

Ejercicio 1 — ¿En qué capa vive? Para cada aspecto de accesibilidad, di en qué capa del sistema se resuelve mejor (token / componente / patrón) para que se herede en todos los usos:

  • (a) Que el texto tenga contraste suficiente sobre su fondo.
  • (b) Que cada botón muestre un anillo de foco visible al navegar con teclado.
  • (c) Que cada pantalla tenga un <main> y encabezados en jerarquía.
  • (d) Que el campo de búsqueda tenga un <label> asociado.
Ver solución
  • (a) Tokens. El contraste es una propiedad de los pares de color. Si los tokens (color.text sobre color.surface) se eligen con contraste suficiente, toda pieza que los use lo hereda. Se garantiza en la paleta, una vez.
  • (b) Componente. El foco visible vive en el componente Button: se define una vez en el componente y cada uso lo hereda. (Se apoya en tokens —el color del anillo de foco puede ser un token— pero el comportamiento de mostrarlo es del componente.)
  • (c) Patrón. La estructura de la página (un <main>, encabezados en jerarquía, regiones) vive en los patrones como site-header y en la plantilla de página. Cada pantalla que usa el patrón hereda la estructura semántica correcta.
  • (d) Componente. El <label> asociado al <input> vive dentro del componente SearchBar: se resuelve una vez, por dentro, y cada búsqueda del storefront queda etiquetada sin esfuerzo.

El principio que se repite: resuelve cada cosa en la pieza más baja que la contenga, para que suba a todos los usos. Contraste → tokens; foco/label → componentes; estructura → patrones.

Ejercicio 2 — Predice el conteo. Sin correr nada, di cuántos botones "accesibles" contaría el modelo del ejemplo (foco y contraste, los dos) para esta lista ad-hoc, y cuál sería el conteo si las mismas pantallas usaran el componente Button del sistema:

const buttons = [
  { screen: 'a', focusVisible: true,  contrastOk: true },
  { screen: 'b', focusVisible: true,  contrastOk: false },
  { screen: 'c', focusVisible: true,  contrastOk: true },
];
Ver solución

Ad-hoc: 2 de 3. El modelo cuenta accesible solo si focusVisible && contrastOk. La pantalla a (true, true) → accesible. La b (true, false) → falla, porque le falta el contraste aunque tenga foco. La c (true, true) → accesible. Total: 2.

Con el sistema: 3 de 3. Si las tres usan el componente Button (que trae foco y contraste resueltos), las tres heredan ambos requisitos → las tres accesibles. El contrastOk: false de la pantalla b era consecuencia de estilarla a mano; usar la pieza del sistema lo elimina, porque el contraste ya está garantizado en el Button (vía sus tokens).

La lección del ejercicio: en ad-hoc, basta fallar uno de los dos requisitos para caer (por eso b cae con solo el contraste mal); en el sistema, ambos vienen resueltos de fábrica, así que no hay dónde fallar.

Ejercicio 3 — Rampa o timbre. Para cada situación, di si describe accesibilidad "estructural" (rampa: resuelta en el sistema, heredada) o "caso por caso" (timbre: depende de que cada pantalla la recuerde), y qué conviene:

  • (a) "Cada programador debe acordarse de agregar :focus-visible con un anillo a sus botones."
  • (b) "El componente Button trae su anillo de foco; usarlo ya te lo da."
  • (c) "Elegimos los pares de color de los tokens midiendo su contraste una vez; todo lo que los use pasa AA."
  • (d) "En cada pantalla revisamos a mano que los textos tengan contraste antes de publicar."
Ver solución
  • (a) Timbre (caso por caso). Depende de que cada programador recuerde hacerlo en cada botón. Es exactamente lo que produjo el "2 de 5": frágil, con olvidos garantizados a escala. Conviene moverlo al componente.
  • (b) Rampa (estructural). El foco está en el componente; usarlo lo hereda. Resuelto una vez, vale para todos los usos. Esto es lo correcto.
  • (c) Rampa (estructural). El contraste se garantiza en los tokens, medido una vez; toda la UI que use esos pares hereda el contraste. Es la accesibilidad del color hecha propiedad del sistema. Correcto.
  • (d) Timbre (caso por caso). Revisar "a mano en cada pantalla" es el trabajo repetitivo que falla a escala —y que además se hace una y otra vez—. Conviene mover el contraste a los tokens (opción c) para no revisarlo pantalla por pantalla nunca más.

El patrón: (a) y (d) ponen la carga en la memoria humana repetida → timbres, frágiles. (b) y (c) ponen la accesibilidad en la pieza → rampas, heredadas. Siempre que puedas, construye la rampa: resuelve en la pieza, hereda en el uso.

Resumen y siguiente paso

En esta lección cambiaste el marco de la accesibilidad: de una lista de tareas que cada pantalla debe recordar (y olvida) a una propiedad del sistema que se resuelve una vez en la pieza y se hereda en cada uso. Con la rampa contra el timbre viste que la accesibilidad estructural no depende de que alguien recuerde el favor: está en el edificio, para todas las visitas. Aprendiste dónde vive cada parte —el contraste en los tokens, el foco/roles/label en los componentes, la estructura en los patrones— y la regla de oro: usa el elemento HTML correcto antes de alcanzar ARIA. Lo mediste ejecutando: botones estilados a mano dieron 2 de 5 accesibles (memoria que falla), y con el componente Button del sistema, 5 de 5 (accesibilidad heredada). Y fuiste honesto sobre el límite: el sistema da la base y libera tu atención para los juicios que sí requieren pensar (el alt, el flujo), no te exime de ellos.

Antes de avanzar deberías poder: explicar la accesibilidad como propiedad del sistema y no como tarea; decir en qué capa vive cada aspecto (contraste/foco/estructura); distinguir "rampa" de "timbre"; y explicar por qué usar el elemento HTML correcto es la primera capa de accesibilidad.

Con esta lección cerraste el por qué del módulo: viste las tres cosas que un sistema entrega como resultadoconsistencia (L4), mantenibilidad (L5) y accesibilidad (L6)—, todas nacidas de la misma estructura de piezas y única fuente de verdad. La lección 7 junta todo en una sola vista: la anatomía completa del sistema de diseño de Mercado. Vas a ver las cuatro capas pobladas con sus piezas reales —los tokens por categoría, los componentes con sus variantes, los patrones— y a ejecutar el inventario completo, contando incluso cuántas combinaciones produce cada componente. Es el plano terminado, listo para que el proyecto (L8) te ponga a levantarlo.

Recursos

  • web.dev, "Color and contrast accessibility" — web.dev/articles/color-and-contrast-accessibility. Cómo el contraste (la parte de accesibilidad que vive en los tokens) se mide y por qué importa; base del módulo 4. En inglés.
  • WAI (W3C), "Introduction to Web Accessibility" — w3.org/WAI/fundamentals/accessibility-intro. El marco general: por qué la accesibilidad importa y qué cubre; la referencia autoritativa. En inglés.
  • MDN, "ARIA: the first rule" (usa HTML nativo primero) — developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA. Por qué el elemento HTML correcto vence a ARIA; la regla de oro de esta lección. En inglés.
  • Radix Primitives — radix-ui.com/primitives. Componentes "sin estilo pero accesibles" (foco, roles y teclado resueltos) que vistes con tus tokens; el modelo del módulo 7, y un ejemplo vivo de accesibilidad heredada. En inglés.