Los peores antipatrones de UX y cómo solucionarlos
Los peores antipatrones de UX que aún llegan a producción, con correcciones de código antes y después: patrones oscuros, toggles rotos y accesibilidad.

La semana pasada intenté ajustar las preferencias de cookies en la web de una gran aerolínea. El botón 'Aceptar todo' era de un azul brillante, imposible de pasar por alto. ¿El enlace 'Gestionar preferencias'? Texto gris, fuente de 11px, escondido debajo de un párrafo de jerga legal. Cuando por fin lo encontré, llegué a una página con 47 interruptores individuales, todos activados por defecto, y sin ningún botón de 'Rechazar todo' a la vista. Me quedé un minuto entero haciendo clic en interruptores hasta que me rendí y simplemente borré las cookies a mano.
Esto no es un caso aislado. Es la norma. Un mal UX no solo es molesto, es hostil. ¿Y lo peor? La mayoría de estos antipatrones tienen soluciones sencillísimas que cuestan menos tiempo de implementar que la gimnasia de patrones oscuros que reemplazan. Llevo más de una década construyendo aplicaciones web y me sigue desconcertando que sigamos publicando este tipo de cosas. Así que hablemos de los peores culpables y de cómo acabar con ellos.
Patrones oscuros que tratan a los usuarios como víctimas
Los patrones oscuros no son accidentes. Alguien se sentó en una reunión, miró las métricas de conversión y decidió que engañar a los usuarios era una estrategia de negocio aceptable. Eso es lo que los hace tan exasperantes: son deliberados.
Confirmshaming: la culpa como elemento de interfaz
Los has visto. Aparece un modal pidiéndote que te suscribas a un boletín. El botón de aceptar dice '¡Sí, quiero ahorrar dinero!'. El de rechazar dice 'No, gracias, prefiero pagar precio completo'. Esto es confirmshaming, y está por todas partes: tiendas online, flujos de onboarding de SaaS e incluso algunas herramientas para desarrolladores. Funciona con un porcentaje minúsculo de usuarios y aleja a todos los demás.
La solución es vergonzosamente sencilla: usa un lenguaje neutro para ambas opciones.
<!-- BEFORE: Manipulative confirmshaming -->
<div class="modal">
<p>Join 50,000 smart developers!</p>
<button class="btn-primary">Yes, sign me up!</button>
<button class="btn-link text-sm">
No thanks, I don't care about my career
</button>
</div>
<!-- AFTER: Respectful, neutral options -->
<div class="modal">
<p>Get weekly frontend tips — no spam, unsubscribe anytime.</p>
<button class="btn-primary">Subscribe</button>
<button class="btn-secondary">No thanks</button>
</div>
Fíjate en que la versión 'después' también elimina la prueba social exagerada y añade una promesa clara sobre el spam. El respeto genera más confianza que la manipulación. Tu conversión a largo plazo te lo agradecerá.
El Roach Motel: registro con un clic, cancelación en siete pasos
Registrarse lleva 30 segundos. Cancelar exige navegar por Ajustes > Cuenta > Suscripción > Gestionar plan > Cancelar plan > Cuéntanos por qué > ¿Estás seguro? > Hablar con retención > Cancelar de verdad. Algunos servicios todavía exigen una llamada telefónica. En 2026. Para cancelar una suscripción que empezaste con un solo clic.
Me da igual lo que digan tus métricas de retención. Los usuarios atrapados no son clientes fieles: son rehenes que generan contracargos y reseñas de una estrella. Haz que cancelar sea exactamente tan fácil como registrarse.
- Pon la opción de cancelar en Ajustes de la cuenta, donde la gente la espera
- Dos pasos como máximo: '¿Estás seguro?' y luego 'Listo, tu cuenta está cancelada'
- No escondas el botón de cancelar tras un enlace de 'Contactar con soporte'
- Si ofreces un descuento de retención, perfecto, pero no lo conviertas en un paso obligatorio del flujo
- Envía un correo de confirmación con un enlace de reactivación en lugar de hacer que la cancelación parezca irreversible
Interruptores rotos y controles de interfaz confusos
Un experimento divertido: ve a los ajustes de tu móvil y cuenta cuántos interruptores no puedes distinguir de inmediato entre activados y desactivados. Espero.
El interruptor ambiguo es uno de los fallos de interfaz más comunes que encuentro en las revisiones de código. Un interruptor debería comunicar una sola cosa: el estado actual. ¿Está encendido o apagado? Eso es todo. Pero los desarrolladores siguen publicando interruptores en los que ambos estados se ven casi iguales, o donde el código de colores es ambiguo, o donde el interruptor muestra lo que va a pasar a continuación en lugar de lo que está pasando ahora.
/* BEFORE: Both states look nearly identical */
.toggle {
width: 48px;
height: 24px;
background: #d1d5db;
border-radius: 12px;
cursor: pointer;
}
.toggle.active {
background: #9ca3af; /* barely different from inactive */
}
/* AFTER: Unmistakable state difference + accessible */
.toggle {
width: 48px;
height: 24px;
background: #d1d5db;
border-radius: 12px;
cursor: pointer;
position: relative;
}
.toggle[aria-checked="true"] {
background: #2563eb; /* strong contrast from inactive */
}
.toggle .label {
font-size: 10px;
font-weight: 600;
color: white;
}
.toggle[aria-checked="true"] .label::after { content: "ON"; }
.toggle[aria-checked="false"] .label::after { content: "OFF"; }
Tres reglas para interruptores que no confunden a la gente: colores distintos para cada estado (con suficiente contraste para usuarios con daltonismo), una etiqueta de texto dentro o junto al interruptor, y atributos aria-checked correctos para que los lectores de pantalla anuncien el estado. Si dependes solo del color, has fallado en UX y en accesibilidad de un solo golpe.
Deja de reinventar los controles de formulario nativos
Reviso muchísimos PR de frontend, y hay un patrón que me saca de quicio: los desplegables hechos a medida que rompen la navegación por teclado. Un desarrollador pasa dos días construyendo desde cero un menú select muy vistoso con un montón de divs y manejadores de clic. Se ve genial en la demo. Luego un usuario intenta recorrer un formulario con el tabulador, llega al desplegable personalizado y no pasa nada. Sin soporte para las flechas. Sin búsqueda por escritura. No funciona con los gestores de contraseñas. Se rompe en móviles.
Mientras tanto, el elemento nativo <select> o una biblioteca bien probada como Radix UI o Headless UI resuelven todo esto gratis. La API de popover de HTML y el elemento <selectlist> ya tienen un soporte sólido en los navegadores. Úsalos. A tus usuarios no les importa que tu desplegable tenga una animación personalizada. Les importa que funcione.
Antipatrones de accesibilidad que excluyen a usuarios reales
La accesibilidad no es un extra deseable. No es una funcionalidad que añades en un sprint futuro. Más de mil millones de personas en todo el mundo viven con alguna forma de discapacidad, y el informe WebAIM Million encuentra de forma constante que más del 96 % del millón de sitios web más visitados presenta fallos de WCAG detectables. No son casos extremos oscuros: son botones rotos, etiquetas que faltan e imágenes sin texto alternativo.
Esto es lo que veo con más frecuencia:
- Imágenes sin texto alternativo: los lectores de pantalla dicen 'imagen' y pasan de largo
- Ratios de contraste por debajo de 4,5:1: el texto resulta ilegible para usuarios con baja visión
- Sin navegación por teclado: si no puedes recorrer tu aplicación con el tabulador, los usuarios de teclado y de dispositivos de conmutación quedan excluidos
- Trampas de foco en los modales: el usuario abre un diálogo y no puede salir de él sin ratón
- Etiquetas de formulario ausentes: el lector de pantalla no tiene ni idea de para qué sirve un campo de entrada
- Vídeo con reproducción automática y sin botón de pausa: desorienta a usuarios con trastornos vestibulares
La mejora más rápida es añadir comprobaciones automáticas de accesibilidad a tu pipeline de CI. No lo detectará todo (las herramientas automáticas encuentran quizá entre el 30 y el 40 % de los problemas), pero sí atrapa lo evidente antes de que llegue a producción.
// BEFORE: No accessibility testing at all
test('homepage loads', async ({ page }) => {
await page.goto('/');
await expect(page.locator('h1')).toBeVisible();
});
// AFTER: Accessibility violations fail the build
import AxeBuilder from '@axe-core/playwright';
test('homepage loads and is accessible', async ({ page }) => {
await page.goto('/');
await expect(page.locator('h1')).toBeVisible();
const a11yResults = await new AxeBuilder({ page })
.withTags(['wcag2a', 'wcag2aa'])
.analyze();
expect(a11yResults.violations).toEqual([]);
});
Cinco minutos de configuración. Se ejecuta en cada PR. Detecta automáticamente texto alternativo ausente, roles ARIA rotos, contraste insuficiente y decenas de otros fallos. No hay excusa para no hacerlo.
Sobrecarga cognitiva: la página de ajustes del infierno
La memoria de trabajo humana retiene unos cuatro a siete elementos a la vez. No es una sugerencia: es un límite cognitivo estricto. Cuando tu interfaz vuelca 200 opciones en una sola página sin jerarquía, no le das control al usuario. Le das parálisis por decisión.
Una vez conté los ajustes de una herramienta de gestión de proyectos que usábamos en el trabajo. 347 opciones. Una sola página. Sin búsqueda. Sin agrupación más allá de categorías vagas como 'General' y 'Avanzado'. La mitad del equipo nunca había cambiado un solo ajuste, porque la página era tan abrumadora que la cerraban de inmediato.
La solución es la divulgación progresiva. Muestra los cinco ajustes que la gente realmente cambia. Pon todo lo demás tras secciones desplegables claramente etiquetadas. Añade una barra de búsqueda. Y por el amor de Dios, ofrece valores por defecto sensatos para que la mayoría de usuarios nunca necesite la página de ajustes.
Si tu página de ajustes necesita su propia documentación, el problema es tu página de ajustes.
La sobrecarga de notificaciones enseña a los usuarios a ignorarlo todo
Cuando cada evento dispara una notificación (un compañero se unió a un canal, alguien reaccionó con un emoji, un despliegue terminó, un evento del calendario empieza en 30 minutos), los usuarios aprenden a ignorarlas todas. Has convertido tu sistema de notificaciones en ruido blanco. ¿La única alerta crítica que de verdad importa? Enterrada bajo 47 notificaciones sin leer.
La respuesta son los valores por defecto conservadores. Los usuarios nuevos deberían recibir solo notificaciones críticas. Agrupa lo no urgente en un resumen diario. Y siempre (siempre) permite silenciar o personalizar desde la propia notificación, no desde alguna página de preferencias escondida a tres clics de distancia.
Una interfaz lenta es una interfaz rota: el rendimiento como problema de UX
Un botón que tarda dos segundos en responder no es lento. Está roto. Los usuarios no piensan 'ah, el servidor está procesando mi petición'. Piensan '¿se ha registrado mi clic?' y vuelven a hacer clic. Ahora tienes envíos duplicados, un estado confuso y un usuario que no confía en tu app.
Todo lo que supera los 100 ms se siente lento. Todo lo que supera el segundo rompe el hilo del pensamiento del usuario. Y aun así seguimos publicando paquetes de JavaScript de varios megas, scripts de terceros que bloquean el renderizado y cambios de diseño que hacen que la gente pulse el elemento equivocado. La solución no es complicada: solo requiere que te importe.
// BEFORE: User clicks, waits 2 seconds, nothing happens
async function handleLike(postId: string) {
const result = await fetch(`/api/posts/${postId}/like`, {
method: 'POST',
});
if (result.ok) {
setLikeCount(prev => prev + 1); // UI updates after network round-trip
}
}
// AFTER: Optimistic update — UI responds instantly
function handleLike(postId: string) {
// Update UI immediately
setLikeCount(prev => prev + 1);
// Sync with server in background
startTransition(async () => {
const result = await fetch(`/api/posts/${postId}/like`, {
method: 'POST',
});
if (!result.ok) {
// Rollback only on failure
setLikeCount(prev => prev - 1);
toast.error('Something went wrong. Try again.');
}
});
}
Actualizaciones optimistas, pantallas esqueleto en lugar de spinners, JavaScript no crítico con carga diferida y monitorizar los Core Web Vitals como métricas reales, no como ideas de última hora. Estas no son técnicas avanzadas. Son expectativas básicas.
Antipatrones de UX móvil que se niegan a morir
El móvil tiene su propia categoría especial de pecados de UX, sobre todo porque los desarrolladores siguen probando en un iPhone recién estrenado y dándolo por bueno.
Áreas táctiles que exigen precisión quirúrgica
Las guías de Apple piden un mínimo de 44x44 píxeles CSS para las áreas táctiles. WCAG 2.2 coincide. Y aun así sigo viendo botones de cierre de 20 píxeles en los modales, enlaces diminutos apelotonados en los pies de página y botones de icono que son prácticamente imposibles de pulsar en un teléfono. Para usuarios con discapacidades motrices, es todavía peor.
/* BEFORE: Tiny, frustrating touch targets */
.close-btn {
width: 20px;
height: 20px;
font-size: 12px;
}
.footer-links a {
padding: 2px 4px;
font-size: 11px;
}
/* AFTER: Actually tappable elements */
.close-btn {
min-width: 44px;
min-height: 44px;
display: flex;
align-items: center;
justify-content: center;
}
.footer-links a {
padding: 12px 8px;
font-size: 14px;
/* spacing between adjacent targets */
margin: 4px 0;
}
Intersticiales a pantalla completa que bloquean el contenido
Tocas un resultado de búsqueda. La página carga. Antes de que puedas leer una sola palabra, un modal a pantalla completa te pide tu correo. Ni siquiera has visto el contenido. ¿Por qué ibas a suscribirte?
Google penaliza los intersticiales intrusivos en los rankings de búsqueda desde 2017. Siguen existiendo porque alguien, en algún sitio, mira un panel que muestra una tasa de captación de correos del 2 % y lo celebra como un éxito, mientras ignora la tasa de rebote del 40 % que provoca. Usa banners integrados u hojas inferiores. Espera a que el usuario haya interactuado de verdad con tu contenido antes de pedirle nada. Un lector que ha terminado tres artículos se suscribirá con gusto. Un lector al que han interrumpido tres veces se irá y no volverá jamás.
Cómo construir una cultura de calidad de UX (no solo arreglar errores sueltos)
Arreglar los antipatrones uno a uno es necesario, pero no suficiente. Si tu proceso sigue produciéndolos, necesitas un proceso mejor. Esto es lo que realmente funciona:
- Añade axe-core a tu pipeline de CI: comprobaciones de accesibilidad automáticas en cada PR
- Haz que la revisión de UX forme parte de la revisión de código, no un proceso aparte solo de diseño
- Prueba con 5 usuarios reales antes de lanzar funcionalidades importantes: 5 usuarios detectan alrededor del 85 % de los problemas de usabilidad
- Registra las tasas de finalización de tareas y de error, no solo las visitas y el tiempo en el sitio
- Usa un sistema de diseño con componentes accesibles ya construidos para que cada desarrollador no tenga que resolver desde cero los mismos problemas de interacción
- Usa tu propio producto sin descanso: cuando el desarrollador que construyó la página de ajustes tiene que usarla a diario, se arregla rápido
La mejora de UX más valiosa que he hecho empezó cuando intenté usar nuestro propio flujo de pago desde el móvil y acabé escribiendo mensajes de rabia al canal del producto a las 11 de la noche.
Cada antipatrón de este artículo tiene una solución directa. Ninguno requiere tecnología de vanguardia ni rediseños masivos. Requieren que te importe. Empieza con una solución hoy. Haz una auditoría de accesibilidad esta semana. Prueba con un usuario real este mes. Un buen UX no va de grandes gestos: consiste en elegir de forma constante el respeto por tus usuarios frente a las métricas a corto plazo. La distancia entre el software que la gente tolera y el que la gente adora es más pequeña de lo que crees. Son solo mil pequeñas decisiones, bien tomadas.


