Saltar al contenido

CSS, de a poco

Curso de CSS

Bordes y sombras

Lección 05 de 37 · 4 min

Un radio bien elegido y una sombra suave hacen que una tarjeta parezca un objeto. Un contorno de foco claro hace que se pueda usar con el teclado.

Borde y radio

El borde (border) es un atajo de tres valores: ancho, estilo y color, como border: 1px solid oklch(0.9 0 0). El estilo es obligatorio: si falta, vale none y el borde no se dibuja.

El radio (border-radius) redondea las esquinas. Acepta de uno a cuatro valores, que arrancan arriba a la izquierda y siguen como un reloj. Con porcentajes, el radio horizontal se mide sobre el ancho y el vertical sobre el alto: 50% da un círculo en una caja cuadrada y una elipse en una rectangular.

Cuatro radios: una tarjeta con 16px en las cuatro esquinas, una pestaña con 16px solo arriba, un avatar cuadrado con 50% que queda círculo y una píldora con 9999px.
styles.css
.tarjeta { border-radius: 16px; }
.pestana { border-radius: 16px 16px 0 0; } /* solo arriba */
.avatar { border-radius: 50%; } /* círculo si es cuadrado */
.pildora { border-radius: 9999px; } /* el navegador lo achica */

Cuando una caja redondeada tiene otra adentro, los radios tienen que ser concéntricos: el radio interior es el exterior menos la distancia entre los dos bordes (el relleno, más el ancho del borde si lo hay). Con 16px de radio y 8px de relleno, la caja de adentro lleva 8px. Si el relleno supera al radio, el interior queda en 0.

Mové los controles y desmarcá el radio concéntrico: con el mismo radio en el bloque de color, la franja entre el bloque y el borde de la tarjeta se ensancha en las esquinas. Los círculos naranjas trazan cada radio, y la cota de arriba mide el relleno.

box-shadow
radio interior: 16px

Muestra de pintura con radio de 28px y relleno de 12px; el bloque de color de adentro tiene radio de 16px. Sombras en capas, sin anillo de foco.

radio de la tarjeta
28px
relleno
12px
radio interior
28 − 12 = 16px

mismo centro, curvas paralelas

sombras.css
.tarjeta {
border-radius: 28px;
padding: 12px;
box-shadow:
0 1px 2px oklch(0 0 0 / 6%),
0 4px 8px oklch(0 0 0 / 6%),
0 12px 24px oklch(0 0 0 / 8%);
}
@media (prefers-color-scheme: dark) {
.tarjeta {
box-shadow:
0 0 0 1px oklch(1 0 0 / 10%),
0 1px 2px oklch(0 0 0 / 6%),
0 4px 8px oklch(0 0 0 / 6%),
0 12px 24px oklch(0 0 0 / 8%);
}
}
.muestra {
border-radius: 16px; /* 28 − 12 */
}
sombras.html
<button class="rounded-[28px] p-3 shadow-[0_1px_2px_oklch(0_0_0/6%),0_4px_8px_oklch(0_0_0/6%),0_12px_24px_oklch(0_0_0/8%)] dark:ring-1 dark:ring-white/10">
<div class="rounded-2xl"></div>
…
</button>

Sombras

La sombra (box-shadow) toma desplazamiento horizontal, desplazamiento vertical, desenfoque, extensión y color. El desenfoque y la extensión son opcionales; la extensión agranda la sombra antes de desenfocarla, o la achica si es negativa. Con inset se dibuja adentro de la caja. La sombra sigue el radio y no ocupa lugar: no mueve nada en el layout.

Separadas por comas entran varias sombras, y la primera queda arriba. Una luz real deja una sombra corta y marcada pegada al objeto y otra larga y difusa. Apilar dos o tres sombras chicas con poca opacidad imita eso mejor que una sola grande.

Tres tarjetas: con una sola sombra grande se ve plana y sucia; con tres sombras chicas en capas se ve como bajo una luz real; sobre fondo oscuro, la línea de 1px de 0 0 0 1px marca el borde sin ocupar lugar.
styles.css
.tarjeta {
box-shadow:
0 1px 2px oklch(0 0 0 / 6%),
0 4px 8px oklch(0 0 0 / 6%),
0 12px 24px oklch(0 0 0 / 8%);
}
@media (prefers-color-scheme: dark) {
.tarjeta {
box-shadow:
0 0 0 1px oklch(1 0 0 / 10%),
0 1px 2px oklch(0 0 0 / 6%),
0 4px 8px oklch(0 0 0 / 6%),
0 12px 24px oklch(0 0 0 / 8%);
}
}

En modo oscuro una sombra negra casi no contrasta con el fondo. Por eso se suma 0 0 0 1px: sin desplazamiento ni desenfoque y con 1px de extensión, dibuja una línea clara de 1px que marca el contorno como un borde, pero sin ocupar lugar.

Contorno y foco

El contorno (outline) se dibuja por fuera del borde y, como la sombra, no ocupa lugar: aparecer o desaparecer no mueve nada. Desde 2023 todos los navegadores principales lo dibujan siguiendo el radio. outline-offset lo separa del borde; con un valor negativo lo mete adentro.

  1. El borde ocupa lugar en el box model; el contorno no.
  2. El borde se puede definir lado por lado; el contorno rodea toda la caja.
  3. En el modo de alto contraste de Windows (forced-colors) las sombras desaparecen, pero el contorno se mantiene.

Por eso el foco se marca con el contorno. No uses outline: none sin un reemplazo visible: quien navega con el teclado pierde de vista dónde está. Con :focus-visible el contorno aparece al navegar con Tab, pero no al hacer clic en un botón.

El mismo botón: al hacer clic no aparece el anillo; al llegar con Tab, :focus-visible dibuja un contorno de 2px separado 2px del borde, que sigue el radio.
styles.css
:focus-visible {
outline: 2px solid currentColor;
outline-offset: 2px;
}

En Tailwind

En Tailwind el radio usa una escala con nombre: rounded-lg son 8px y rounded-2xl son 16px. La sombra, el anillo y el contorno tienen sus propias clases. Los valores fuera de la escala van entre corchetes, con guiones bajos en lugar de espacios.

CSSTailwind
border-radius: 16pxrounded-2xl
border-radius: 16px 16px 0 0rounded-t-2xl
border-radius: 9999pxrounded-full
border: 1px solidborder
box-shadow: 0 1px 2px …shadow-[0_1px_2px_oklch(0_0_0/6%)]
box-shadow: 0 0 0 1pxring · ring-white/10
outline: 2px · outline-offset: 2pxoutline-2 outline-offset-2
tarjeta.html
<!-- 16px de radio y 8px de relleno: adentro, 16 − 8 = 8px -->
<article class="rounded-2xl p-2 shadow-md dark:ring-1 dark:ring-white/10">
<img class="rounded-lg" src="foto.jpg" alt="…">
</article>
<!-- foco visible solo con teclado -->
<button class="rounded-lg px-4 py-2 focus-visible:outline-2
focus-visible:outline-offset-2">
Guardar
</button>