Detalles
Las interfaces que se sienten bien rara vez vienen de una sola cosa. Son detalles pequeños que se acumulan. Cada componente de ssntl/ui pasa por estos doce.
Basado en “Details that make interfaces feel better”, de Jakub Krehel (se abre en otra pestaña)01 · Texto que se reparte bien
text-balance reparte un título en líneas parejas; text-pretty evita que un párrafo termine con una palabra sola. Van juntos: balance en el título, pretty en la descripción. En ssntl/ui ya vienen en la base.
Diseñar interfaces que se sientan naturales e intuitivas
El buen diseño es invisible. Guía a las personas sin que lo noten nunca.
Diseñar interfaces que se sientan naturales e intuitivas
El buen diseño es invisible. Guía a las personas sin que lo noten nunca.
<h2 className="text-balance">…</h2>
<p className="text-pretty">…</p>02 · Radios concéntricos
Una superficie dentro de otra toma el radio exterior menos el espacio entre ambas, y las esquinas quedan paralelas. Mueve los controles: a la izquierda el radio sigue la fórmula, a la derecha se repite y la esquina interior se ve hinchada.
<div className="rounded-outer-lg p-concentric-2">
<div className="rounded-inner">…</div>
</div>03 · Íconos que cambian con contexto
Cuando un ícono reemplaza a otro, se cruzan con opacidad, escala y desenfoque (0.25 → 1, 4px → 0) en lugar de saltar. Ambos viven en el DOM, así el cambio tiene entrada y salida. Es lo que hace el botón de copiar de este sitio.
<span className="icon-swap" data-swapped={copied}>
<Copy />
<Check />
</span>04 · Texto nítido
En macOS el texto puede verse más grueso de lo diseñado. antialiased en el layout lo afina para toda la app. La diferencia solo se aprecia en pantallas de Mac.
Renderizado subpixel
El suavizado por defecto usa antialiasing subpixel en macOS.
Renderizado antialiased
El antialiasing en escala de grises da un texto más fino y nítido.
<body className="font-sans antialiased">…</body>05 · Números tabulares
Si un número cambia, sus dígitos deben medir lo mismo para que nada salte. Cuenta de nuevo y mira el lado derecho: el ancho no se mueve.
Q 1,248,750
IncorrectoQ 1,248,750
Correcto<span className="tabular-nums">Q 1,248,750</span>06 · Animaciones que se pueden interrumpir
Las transiciones van hacia el último estado y se pueden interrumpir; los keyframes corren en una línea fija y reinician. Transiciones para interacciones, keyframes para secuencias que corren una vez. Haz clic varias veces mientras gira.
// Interacción: transition, se redirige a mitad de camino.
<div className="transition-[rotate] duration-(--duration-menu) ease-out" />07 · Entradas por partes
En vez de animar un bloque, el título, la descripción y las acciones entran por separado, 100ms entre cada uno. Un título puede dividirse en palabras a 80ms. Solo para entradas raras, nunca para interacciones frecuentes.
Publica tu tienda hoy
Doce productos y un dominio, listos.
Publica tu tienda hoy
Doce productos y un dominio, listos.
Publica tu tienda hoy
Doce productos y un dominio, listos.
<h2 className="animate-enter stagger-0">…</h2>
<p className="animate-enter stagger-1">…</p>
<div className="animate-enter stagger-2">…</div>
// Por palabras: enter-words cambia el paso a 80ms.
<span className="inline-block animate-enter enter-words stagger-3">tienda</span>08 · Salidas sutiles
Lo que sale pide menos atención que lo que entra. En lugar de deslizar todo el ancho, el panel viaja 30% y deja que la opacidad y el desenfoque hagan el resto.
// Con Motion: la salida viaja menos que la entrada.
<motion.div
initial={{ x: "calc(-100% - 4px)" }}
animate={{ x: 0 }}
exit={{ x: "-30%", opacity: 0, filter: "blur(4px)" }}
transition={{ type: "spring", duration: 0.45, bounce: 0 }}
/>09 · Alineación óptica
A veces centrar con números se ve mal. Un botón con ícono lleva 4px menos de padding del lado del ícono, y el triángulo de reproducir se mueve un poco a la derecha porque su peso está a la izquierda. Button lo hace solo con data-icon.
<Button>
<Play data-icon="inline-start" />
Reproducir
</Button>10 · Sombras en lugar de bordes
Una sombra transparente de tres capas da profundidad y toma el color de cualquier fondo; un borde sólido conserva su gris y se ve pegado. En oscuro se lee como un borde de 1px. Pasa el puntero por las tarjetas.
<div className="border-shadow hover:border-shadow-strong">…</div>11 · Contorno en imágenes
Un contorno de 1px negro (blanco en oscuro) al 10% le da a las imágenes un borde consistente sobre cualquier superficie. Negro o blanco puros: un gris teñido se ve como suciedad.
Incorrecto
Correcto<img className="image-outline" … />12 · Escala tipográfica
Cuando el texto de apoyo es tan grande como el título, el lector no sabe por dónde empezar. Asigna cada rol a un paso de la escala del sistema: heading 22, body 17, caption 13. La jerarquía sale de tamaño, peso y color juntos.
Diseña para un vistazo
Alex Morgan · 12 de junio de 2026 · 4 min de lectura
La gente recorre una página antes de leerla. Un título claro les dice qué esperar, una introducción corta da contexto y los detalles responden sus preguntas.
- Título:
- 15px
- Apoyo:
- 17px
- Párrafo:
- 17px
<h3 className="text-heading">Diseña para un vistazo</h3>
<p className="text-caption text-muted-foreground">Alex Morgan · 12 de junio de 2026</p>
<p className="text-body text-muted-foreground">…</p>Instalar las utilidades
border-shadow, image-outline, icon-swap y animate-enter vienen en un solo archivo. Los radios concéntricos tienen el suyo.
/* npx shadcn@latest add @ssntl/details @ssntl/radius-concentric */
@import "../styles/ssntl/details.css";
@import "../styles/ssntl/radius-concentric.css";