Tips & Tricks (Actualizado: 2/6/2026)

Accesibilidad con Claude Code: HTML semántico, ARIA, axe y revisión manual

Flujo práctico para mejorar accesibilidad con Claude Code: HTML, teclado, formularios, foco, axe y lectores de pantalla.

Accesibilidad con Claude Code: HTML semántico, ARIA, axe y revisión manual

La accesibilidad no es una tarea de “pasar una herramienta al final”. Empieza con HTML semántico, sigue con navegación por teclado, manejo de foco, mensajes de error, contraste de color y termina con pruebas automáticas y revisión manual con lector de pantalla.

Claude Code ayuda mucho en ese flujo, pero solo si la petición es precisa. Si le dices “hazlo accesible”, puede añadir ARIA a un div sin implementar Enter o Space, dejar un modal que atrapa visualmente pero no el foco, o mostrar errores que una persona con lector de pantalla no oye.

En esta guía uso fuentes primarias: W3C WCAG 2.2 como objetivo práctico, WAI-ARIA Authoring Practices Guide para patrones de widgets, MDN sobre ARIA para priorizar HTML semántico, documentación de axe-core de Deque para automatización y la documentación oficial de Claude Code para el uso de la herramienta.

Define el estándar antes del cambio

“Mejorar accesibilidad” es demasiado amplio para un agente. Un objetivo útil es: WCAG 2.2 AA como referencia, HTML semántico antes que ARIA, operación completa por teclado en los flujos principales, foco visible, errores comprensibles y evidencia de pruebas automáticas y manuales.

ÁreaLínea mínimaFallo típico
HTML semánticoUsar button, a, form, label, main, nav según su funcióndiv clicable en lugar de control nativo
TecladoTab, Shift+Tab, Enter, Space y Escape cubren el flujo principalEl modal solo cierra con el ratón
FocoEl foco entra al UI nuevo y vuelve al disparador al cerrarEl foco se pierde detrás del diálogo
ARIASolo se añade cuando HTML nativo no expresa el estadoaria-label oculta la falta de etiqueta visible
ColorTexto, controles, errores y foco tienen contraste suficienteEl error depende solo del color rojo
FormulariosEtiquetas, ayuda, estado inválido y errores se asocian al inputEl error se ve pero no se anuncia
Pruebasaxe más teclado y lector de pantallaCero violaciones automáticas se interpreta como auditoría completa

MDN resume la regla práctica: si existe un elemento HTML nativo con la semántica y el comportamiento necesarios, úsalo antes de ARIA. Claude Code debe corregir primero la estructura y solo después añadir estados como aria-expanded, aria-invalid o aria-modal.

Prompt seguro para Claude Code

Un buen prompt de accesibilidad parece una checklist de revisión. Incluye alcance, límites, estándar y prueba esperada.

claude <<'PROMPT'
Scope:
- Review only src/components/CheckoutForm.tsx and its tests.
- Do not change pricing copy, analytics events, or unrelated styles.

Accessibility target:
- Use WCAG 2.2 AA as the practical target.
- Prefer semantic HTML before ARIA.
- Add ARIA only when native HTML cannot express the state.

Check these items:
- Labels, descriptions, required state, and validation errors.
- Keyboard operation with Tab, Shift+Tab, Enter, Space, and Escape.
- Focus order, visible focus, and focus return after closing UI.
- Color contrast and non-color error indicators.
- Automated axe check plus manual screen-reader notes.

Output:
- Findings first, with file and line references.
- Minimal patch.
- Commands to verify.
- Any remaining risk.
PROMPT

La parte clave es Findings first. Primero quieres saber qué problema existe y dónde. Después pides el parche mínimo. Esto reduce cambios grandes que podrían romper copys de precio, eventos de analytics o CTA monetizados. Para más hábitos de alcance pequeño, revisa tips de productividad con Claude Code.

Caso 1: CTA de producto o de artículo

Un CTA de producto suele ser una ruta de ingresos. Si parece una tarjeta pero su acción real es navegar, debe ser un enlace real, no un contenedor con onclick.

<div class="hero-card" onclick="location.href='/en/products'">
  <div class="title">Claude Code Templates</div>
  <div class="button">Buy now</div>
</div>

Una base más accesible separa encabezado, explicación y enlace.

<section aria-labelledby="templates-heading" class="product-cta">
  <h2 id="templates-heading">Reduce revisiones con plantillas de Claude Code</h2>
  <p>
    Copia prompts reutilizables para implementación, revisión,
    depuración y documentación.
  </p>
  <a class="primary-link" href="/en/products">
    Ver recursos de producto
  </a>
</section>

Cuando pidas este cambio, dile a Claude Code que conserve destinos, atributos de tracking y texto de conversión. Un arreglo accesible que borra el CTA correcto no es un buen arreglo de negocio.

Caso 2: Formulario de contacto o consultoría

Los formularios combinan accesibilidad y conversión. Si el usuario no entiende la etiqueta, el formato esperado o el error, no enviará la consulta.

import { FormEvent, useState } from "react";

type Errors = {
  name?: string;
  email?: string;
};

export function ConsultationForm() {
  const [errors, setErrors] = useState<Errors>({});

  function handleSubmit(event: FormEvent<HTMLFormElement>) {
    event.preventDefault();
    const data = new FormData(event.currentTarget);
    const nextErrors: Errors = {};

    if (!String(data.get("name") || "").trim()) {
      nextErrors.name = "Introduce tu nombre.";
    }

    if (!String(data.get("email") || "").includes("@")) {
      nextErrors.email = "Introduce un correo electrónico válido.";
    }

    setErrors(nextErrors);
  }

  return (
    <form aria-labelledby="consultation-title" onSubmit={handleSubmit} noValidate>
      <h2 id="consultation-title">Solicitud de consultoría</h2>

      <div className="field">
        <label htmlFor="name">Nombre</label>
        <input
          id="name"
          name="name"
          autoComplete="name"
          aria-invalid={errors.name ? "true" : "false"}
          aria-describedby={errors.name ? "name-error" : undefined}
        />
        {errors.name && (
          <p id="name-error" role="alert">
            {errors.name}
          </p>
        )}
      </div>

      <div className="field">
        <label htmlFor="email">Correo electrónico</label>
        <p id="email-help">Usa una dirección donde podamos responder.</p>
        <input
          id="email"
          name="email"
          type="email"
          autoComplete="email"
          aria-invalid={errors.email ? "true" : "false"}
          aria-describedby={
            errors.email ? "email-help email-error" : "email-help"
          }
        />
        {errors.email && (
          <p id="email-error" role="alert">
            {errors.email}
          </p>
        )}
      </div>

      <button type="submit">Enviar solicitud</button>
    </form>
  );
}

El fallo más frecuente es mostrar el error visualmente pero no asociarlo con el input. Otro fallo es apuntar siempre a un ID de error que todavía no existe. Mantén la ayuda permanente y conecta el error solo cuando aparezca.

Caso 3: Modal, paleta de comandos y menú

Los modales parecen fáciles, pero su dificultad real es el foco. El patrón oficial de diálogo modal de WAI-ARIA indica que el foco entra al diálogo, Tab se queda dentro, Escape cierra y el foco vuelve al elemento que lo abrió.

import { ReactNode, useEffect, useRef } from "react";

type ModalProps = {
  open: boolean;
  title: string;
  onClose: () => void;
  children: ReactNode;
};

const focusableSelector = [
  "a[href]",
  "button:not([disabled])",
  "input:not([disabled])",
  "select:not([disabled])",
  "textarea:not([disabled])",
  '[tabindex]:not([tabindex="-1"])',
].join(",");

export function AccessibleModal(props: ModalProps) {
  const { open, title, onClose, children } = props;
  const dialogRef = useRef<HTMLDivElement>(null);
  const previousFocusRef = useRef<HTMLElement | null>(null);

  useEffect(() => {
    if (!open) return;

    previousFocusRef.current = document.activeElement as HTMLElement;
    const focusable = dialogRef.current?.querySelectorAll<HTMLElement>(
      focusableSelector
    );
    focusable?.[0]?.focus();

    function onKeyDown(event: KeyboardEvent) {
      if (event.key === "Escape") onClose();
      if (event.key !== "Tab" || !dialogRef.current) return;

      const items = [...dialogRef.current.querySelectorAll<HTMLElement>(
        focusableSelector
      )];
      const first = items[0];
      const last = items[items.length - 1];

      if (event.shiftKey && document.activeElement === first) {
        event.preventDefault();
        last?.focus();
      } else if (!event.shiftKey && document.activeElement === last) {
        event.preventDefault();
        first?.focus();
      }
    }

    document.addEventListener("keydown", onKeyDown);
    return () => {
      document.removeEventListener("keydown", onKeyDown);
      previousFocusRef.current?.focus();
    };
  }, [open, onClose]);

  if (!open) return null;

  return (
    <div className="modal-backdrop">
      <div
        ref={dialogRef}
        role="dialog"
        aria-modal="true"
        aria-labelledby="modal-title"
        className="modal-panel"
      >
        <h2 id="modal-title" tabIndex={-1}>
          {title}
        </h2>
        {children}
        <button type="button" onClick={onClose}>
          Cerrar
        </button>
      </div>
    </div>
  );
}

Para menús, comprueba primero si necesitas un patrón ARIA. El Menu Button Pattern encaja en menús de acciones. Una navegación normal del sitio suele funcionar mejor con nav y enlaces.

Color, foco y móvil

Los problemas de contraste aparecen durante el pulido visual. No elimines el contorno de foco sin reemplazo. No expreses errores solo con color.

.primary-link {
  background: #0f766e;
  border-radius: 6px;
  color: #ffffff;
  display: inline-flex;
  font-weight: 700;
  min-height: 44px;
  padding: 0.75rem 1rem;
}

.primary-link:focus-visible,
button:focus-visible,
input:focus-visible {
  outline: 3px solid #f59e0b;
  outline-offset: 3px;
}

.field [role="alert"] {
  border-left: 4px solid #b91c1c;
  color: #7f1d1d;
  margin-top: 0.5rem;
  padding-left: 0.75rem;
}

Comprueba también móvil. Botones de cierre pequeños, encabezados fijos que cubren el foco o CTA demasiado estrechos suelen pasar desapercibidos en escritorio.

axe y revisión manual

axe detecta muchos problemas estructurales, pero no garantiza que el texto sea claro o que el orden de lectura tenga sentido. Úsalo como puerta de calidad, no como auditoría completa.

npm install -D @axe-core/playwright @playwright/test
npx playwright install --with-deps chromium
import AxeBuilder from "@axe-core/playwright";
import { expect, test } from "@playwright/test";

test("consultation form has no serious accessibility issues", async ({ page }) => {
  await page.goto("/contact");

  const results = await new AxeBuilder({ page })
    .include("main")
    .withTags(["wcag2a", "wcag2aa", "wcag22aa"])
    .analyze();

  expect(results.violations).toEqual([]);
});

La revisión manual debe empezar por rutas críticas: CTA de producto, formulario, checkout, modal, navegación y recuperación de errores. En Windows, NVDA es una buena base. En macOS, VoiceOver ya viene instalado.

Fallos concretos que debes buscar

  • role="button" sin soporte para Enter y Space.
  • Botón solo con icono y sin nombre accesible.
  • alt="image" o texto alternativo inútil.
  • aria-hidden="true" ocultando el modal o una región viva.
  • Error visible sin conexión con aria-describedby.
  • Foco visual eliminado sin alternativa.
  • Modal que cierra pero no devuelve el foco al disparador.
  • Prueba automática que revisa solo la página pública, no el formulario real.

Pega esta lista en Claude Code después del parche y pide referencias de archivo y línea. Obtendrás una revisión mucho más útil que con “revísalo otra vez”.

CTA y nota de verificación

Para convertir esta checklist en flujo repetible, empieza por los recursos de Claude Code products. Si repites revisiones, compra las 50 plantillas de prompts de Claude Code. Si el equipo necesita permisos, hooks y recibos de verificación, la consultoría es el siguiente paso.

En esta actualización probé tres fallos frecuentes: una tarjeta CTA implementada como div clicable, un formulario cuyos errores no se anunciaban y un modal que no devolvía el foco. El mejor flujo con Claude Code fue pedir hallazgos primero, parche mínimo después y comprobación final con teclado y VoiceOver. axe ayudó, pero la claridad real del flujo se verificó manualmente.

#Claude Code #accessibility #WCAG #a11y #React
Gratis

PDF gratis: cheatsheet de Claude Code

Introduce tu email y descarga una hoja con comandos, hábitos de revisión y flujos seguros.

Cuidamos tus datos y no enviamos spam.

Masa

Sobre el autor

Masa

Ingeniero enfocado en workflows prácticos con Claude Code.