“¿El card debería tener 4px u 8px de border-radius?”
Fue un hilo de 47 minutos. Cuatro ingenieros, dos diseñadores, un archivo de Figma con seis variantes. La discusión cubrió convenciones de iOS, directrices de Material Design, alineación óptica y, en algún momento, la proporción áurea. Nadie estaba equivocado. Ese era el problema.
Ya habíamos estado aquí antes. No solo con border-radius — con direcciones de gradiente, valores de blur en sombras, niveles de opacidad para estados deshabilitados. Cada decisión era pequeña. Cada debate era razonable. Y en conjunto, estaban devorando nuestra velocidad.
Así que dejamos de intentar encontrar la respuesta correcta. Eliminamos la pregunta.
El coste de las opciones
Cada propiedad visual en un design system es una superficie de decisión. Solo el border-radius genera preguntas: ¿Deberían los botones y las cards usar el mismo valor? ¿Qué pasa con los elementos anidados — el radio interior resta del exterior? ¿El radio cambia en diferentes tamaños?
Ninguna de estas preguntas es difícil. Eso es lo que las hace peligrosas. Son lo suficientemente fáciles para que todo el mundo tenga una opinión, pero de tan poco impacto que ninguna opinión es demostrablemente mejor. Bikeshedding clásico.
Catalogamos manualmente los comentarios de PRs durante un trimestre. Aproximadamente el 30% de los hilos de revisión de nuestro design system eran sobre valores cosméticos sin impacto en la usabilidad. No layout. No accesibilidad. No patrones de interacción. Solo… estética.
Fue entonces cuando escribimos las reglas.
Por qué eliminar en lugar de restringir
La alternativa obvia es mantener las propiedades pero limitarlas. La mayoría de los design systems maduros hacen esto — tres valores permitidos de border-radius, una escala de elevación de sombras, una paleta curada de degradados. Lo consideramos.
El problema es que las opciones restringidas siguen generando debates. “¿Este card debería usar radius-sm o radius-md?” es la misma conversación con menos respuestas. Menos es mejor que muchas, pero cero es mejor que menos. Restringir una propiedad reduce decisiones. Eliminarla suprime la decisión por completo.
También evaluamos el linting — una regla de ESLint que rechaza valores no autorizados. Pero un linter que impone “usa solo 0, 4 u 8px de radio” aún deja tres opciones. El objetivo no era hacer la decisión más fácil. Era hacerla innecesaria.
Cuatro reglas
La biblioteca de componentes de Beaket tiene cuatro restricciones visuales:
- Sin border-radius. Esquinas rectas en todo. Botones, cards, inputs, diálogos, tooltips — todo rectangular.
- Sin degradados. Solo colores planos. Cada superficie es una única variable CSS.
- Sin sombras con blur. Ningún
box-shadowcon radio de desenfoque. Solo sombras con offset. - Sin opacidad decorativa. Los estados deshabilitados usan cambios de color, no transparencia. Nada de atajos con
opacity: 0.5.
Estas no son directrices. Son reglas estrictas, aplicadas en code review y documentadas en las directrices de componentes. Un PR que añade rounded-md se rechaza.
¿Por qué estas cuatro propiedades específicamente? Porque eran las que generaban más debate con menor impacto en la usabilidad. El espaciado y la tipografía afectan la legibilidad. El color afecta el significado. Pero la diferencia entre 4px y 8px de border-radius? ¿Entre una sombra con blur de 4px y una de 6px? No pudimos encontrar un solo caso en que esas elecciones afectaran de forma medible cómo alguien usaba el producto.
Tras adoptar las reglas, nuestros PRs de design system empezaron a avanzar más rápido. Los hilos de revisión se acortaron. Los diseñadores dejaron de producir múltiples variantes del mismo componente que solo diferían en el tratamiento de esquinas o la profundidad de la sombra. Los debates no disminuyeron — desaparecieron.
Qué usamos en su lugar
Eliminar opciones no significa eliminar jerarquía visual. Aún necesitas comunicar profundidad, estado e interacción. Así es como reemplazamos cada propiedad.
Sombras con offset en lugar de blur
En lugar de box-shadow: 0 4px 6px rgba(0,0,0,0.1), usamos sombras con offset rígido definidas como variables CSS:
--shadow-offset: 1px 1px 0px 0px var(--color-chrome);
--shadow-offset-hover: 2px 2px 0px 0px var(--color-chrome);
--shadow-offset-active: 0px 0px 0px 0px var(--color-chrome);
Tres estados de interacción. Cero blur. Ese es todo el sistema de sombras.
El estado por defecto tiene un offset de 1px — el elemento se sitúa ligeramente por encima de la superficie. El hover lo empuja a 2px — una elevación sutil. El active lo encaja en 0px — el elemento se presiona hacia abajo. Es una animación pequeña y mecánica que comunica la interacción con claridad y no deja nada que el desarrollador tenga que configurar.
En la práctica, cada componente interactivo aplica los mismos tres tokens. Aquí está la base de nuestro componente de botón (simplificado de la cadena CVA completa, que también incluye estados disabled y focus):
const buttonVariants = cva(
[
"inline-flex items-center justify-center gap-2",
"font-medium cursor-pointer",
"shadow-offset hover:shadow-offset-hover active:shadow-offset-active",
"transition-shadow duration-100",
// ... disabled, focus-visible, and icon styles
].join(" "),
// ... variants
);
Ninguna decisión sobre qué nivel de sombra “merece” un componente.
Colores planos mediante variables CSS
Nuestra paleta de colores — la llamamos “Porcelain” — tiene 14 tonos neutros que van de graphite (#030509) a paper (#ffffff). Son fríos y con matiz azulado. La descripción del tema en nuestra documentación dice: “Precisión industrial. Grabado en paneles de control en laca sintética negra. Blanco puro, tinta azul-negra fría, sombras fantasmales, acento en teal.”
Cada referencia de color pasa por un token semántico:
--color-text-primary: var(--color-graphite);
--color-text-secondary: var(--color-steel);
--color-bg-primary: var(--color-paper);
--color-border-default: var(--color-chrome);
Evitamos los colores por defecto de Tailwind como bg-white o text-gray-500. Cada color es una variable con nombre, y cada variable se asocia a un rol. Nadie debate si algo debería ser gray-100 o gray-200 — el token semántico ya responde la pregunta.
Esquinas rectas en todas partes
Esta no necesita reemplazo. Simplemente… no añades border-radius.
Lo que nos sorprendió fue lo bien que las esquinas rectas interactúan con las sombras de offset. Una card redondeada con sombra de offset se ve extraña — el borde rígido de la sombra choca con la esquina suave. Pero una card con esquinas rectas y sombra de offset se ve intencional. La geometría es consistente. Las restricciones se refuerzan mutuamente de formas que no anticipamos del todo.
El lenguaje visual se vuelve consistente por sustracción. Cuando todo es recto, nada necesita justificar su forma.
Donde las restricciones se acumulan
El beneficio más claro de este enfoque aparece en las preocupaciones transversales — los patrones que tocan cada componente.
Los estados deshabilitados son notoriamente inconsistentes en las bibliotecas de componentes. Algunos usan opacidad, algunos cambian el fondo, algunos hacen ambas cosas. Nosotros tenemos un único patrón, aplicado a cada componente:
disabled:shadow-none disabled:cursor-not-allowed disabled:border-dashed disabled:border-chrome disabled:bg-frost disabled:text-steel
Botones, inputs, selects, checkboxes, switches — todos idénticos. Sombra eliminada (la affordance interactiva desaparece), borde discontinuo, fondo frost, texto steel. Cuando has visto un componente deshabilitado, los has visto todos. Las restricciones eliminaron la tentación de hacer que los estados deshabilitados “encajen” con la personalidad visual de cada componente. Cuando los componentes no tienen personalidad visual, los estados deshabilitados convergen de forma natural.
Los anillos de foco funcionan igual. Cada componente enfocable usa:
focus-visible:outline-2 focus-visible:outline-signal-blue focus-visible:outline-offset-2
Un color. Un ancho. Un offset. Un usuario de teclado navegando por un formulario ve exactamente el mismo anillo en cada elemento. Ningún componente lo sobrescribe. Ninguna variante lo elimina. Combinado con el patrón uniforme de disabled, el usuario siempre sabe exactamente cómo se ven “inactivo” y “enfocado” — independientemente del componente con el que esté interactuando.
La única excepción
Los radio buttons usan rounded-full.
Eso es todo. Esa es la única excepción en toda la biblioteca de componentes. Está documentada en las directrices de componentes junto con las reglas que rompe, porque incluso las excepciones deben ser decisiones, no accidentes.
Un radio button cuadrado confundiría a los usuarios. La forma circular está tan profundamente arraigada en las convenciones de interfaz que eliminarla sería sacrificar usabilidad por pureza estética. Decidimos que era un mal intercambio.
Podrías preguntar: si la usabilidad justifica una excepción para los radios, ¿por qué no para avatares, tags o toggle switches? Lo probamos. Los avatares cuadrados con sombras de offset parecen tarjetas de perfil — se leen como intencionales. Los tags y toggles están menos atados a convenciones que los radio buttons. La affordance circular es excepcionalmente fuerte para los radios; nada más superó el listón.
El objetivo nunca fue “brutalismo por sí mismo.” Era eliminar decisiones visuales que no justifican su complejidad. Un radio button redondeado justifica su forma. Una card redondeada no.
Lo que esto realmente cuesta
Sería deshonesto decir que este enfoque no tiene desventajas. Las tiene.
“Parece inacabado.” Esa es una reacción real de pruebas con usuarios. Las esquinas rectas y los colores planos no transmiten el mismo acabado que las interfaces redondeadas y sombreadas. Hemos tenido que explicar el razonamiento más de una vez.
Algunos componentes se sienten rígidos. Los tooltips y popovers, en particular, se ven más pesados con esquinas rectas. Las sombras con blur existen por una razón — crean una sensación de flotación que las sombras de offset no replican del todo.
Los componentes de terceros rompen el patrón. Date pickers, editores de texto enriquecido, bibliotecas de gráficos — todos vienen con border-radius y sombras con blur integrados. Sobrescribimos lo que podemos y aceptamos cierta inconsistencia en los bordes. Esta es una tensión continua, no un problema resuelto.
La expresión del diseñador está restringida. Si eres el tipo de diseñador que disfruta creando micro-interacciones con overlays de degradado y profundidad sutil, este sistema se sentirá limitante. Ese es el punto — pero sigue siendo un coste real para la satisfacción creativa.
Es polarizante. Algunos ingenieros aman la simplicidad. Otros piensan que es dogmático. No hemos resuelto esta tensión por completo. Solo decidimos que el beneficio de la consistencia supera el coste estético para nuestro producto.
Lo que aún no sabemos
No hemos probado este sistema a verdadera escala — cientos de componentes, interfaces con alta densidad de datos, integraciones con terceros que necesitan parecer nativas. Tablas, gráficos y dashboards podrían necesitar indicaciones de profundidad que las sombras de offset no pueden proporcionar.
Estamos genuinamente inciertos sobre si los usuarios desarrollan la misma comprensión intuitiva de nuestros estados de sombra (por defecto → hover → active) que tienen con los sistemas convencionales de elevación. No hemos observado regresiones de usabilidad, pero no hemos realizado pruebas A/B formales específicamente sobre esto. Está funcionando hasta ahora. Pero “hasta ahora” es una muestra pequeña.
Restricciones como decisiones
Cada restricción en este sistema es una decisión tomada una vez para no tener que tomarla de nuevo. Sin border-radius no es una preferencia estética — es un ADR (Architecture Decision Record) para toda la biblioteca de componentes.
Si tu equipo dedica tiempo a debatir propiedades visuales que no afectan la usabilidad, considera eliminar la opción por completo. Puede que la eches de menos menos de lo que crees.