Saltar al contenido
ssntl/ui

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.

wrap

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.

Incorrecto
balance

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.

Correcto
220px
text-balance reparte el título en líneas parejas.
tsx
<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.

padding: 8px
12px
12px
Incorrecto
20px
12px
Correcto
Mismo radio interior y padding: solo el radio exterior que sigue la fórmula se ve bien.
radio exterior=radio interior+padding
40px
24px
16px
40 = 24 + 16
tsx
<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.

sin animaciónIncorrecto
opacityIncorrecto
opacity + scale + blurCorrecto
Haz clic en cada botón, o cámbialos todos a la vez.
tsx
<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.

subpixel-antialiased

Renderizado subpixel

El suavizado por defecto usa antialiasing subpixel en macOS.

Incorrecto
antialiased

Renderizado antialiased

El antialiasing en escala de grises da un texto más fino y nítido.

Correcto
La diferencia solo se aprecia en pantallas de Mac.
tsx
<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.

proportional

Q 1,248,750

Incorrecto
tabular

Q 1,248,750

Correcto
Cuenta y mira el ancho: los dígitos tabulares no se mueven.
tsx
<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.

CSS keyframe
Incorrecto
CSS transition
Correcto
Haz clic en girar varias veces seguidas para ver la diferencia.
tsx
// 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.

un bloque

Publica tu tienda hoy

Doce productos y un dominio, listos.

Incorrecto
por partes · 100ms

Publica tu tienda hoy

Doce productos y un dominio, listos.

Correcto
por palabras · 80ms

Publica tu tienda hoy

Doce productos y un dominio, listos.

Correcto
Repite y compara: un bloque, por partes y por palabras.
tsx
<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.

salida completa
Incorrecto
salida sutil
Correcto
Cierra y abre: la salida sutil pide menos atención.
tsx
// 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.

geométrico
Incorrecto
óptico
Correcto
El lado del ícono lleva 4px menos de padding; el triángulo se mueve 1px a la derecha.
tsx
<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.

border
Plan ProQ 450 al mes
Incorrecto
border-shadow
Plan ProQ 450 al mes
Correcto
Sobre un fondo teñido el borde conserva su gris; la sombra toma el color de abajo.
tsx
<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.

sin contornoIncorrecto
image-outlineCorrecto
Un contorno de 1px negro o blanco puro le da a la imagen un borde consistente.
tsx
<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
Cada rol tiene su tamaño: el título guía y los metadatos dan un paso atrás.
tsx
<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.

app/globals.css
/* npx shadcn@latest add @ssntl/details @ssntl/radius-concentric */
@import "../styles/ssntl/details.css";
@import "../styles/ssntl/radius-concentric.css";