Saltar al contenido

CSS, de a poco

Curso de CSS

Formas y máscaras

Lección 15 de 37 · 5 min

Toda caja es un rectángulo, pero no hace falta que se vea así. Con CSS podés recortarla, desvanecerla, hacer que el texto la rodee y cambiar la forma de sus esquinas, sin exportar ni una imagen más.

Recortar con clip-path

clip-path define una forma y oculta todo lo que queda afuera. Lo recortado no se pinta y tampoco recibe clics, pero la caja sigue ocupando el mismo lugar en el layout. El recorte se aplica a todo el elemento: también se lleva su box-shadow y su outline.

Las formas básicas se miden en porcentajes de la caja, así que se adaptan a cualquier tamaño:

  1. Rectánguloinset(12% round 16px)Un rectángulo metido hacia adentro, con esquinas redondeadas opcionales.
  2. Círculocircle(40% at 50% 50%)El porcentaje del radio se mide sobre la diagonal de la caja dividida por √2, no sobre el ancho.
  3. Elipseellipse(46% 40%)Un radio horizontal (sobre el ancho) y uno vertical (sobre el alto).
  4. Polígonopolygon(…)Una lista de puntos x y que se unen con rectas.

Elegí una forma y mové el control. La línea naranja es el borde del recorte y la parte gris es lo que se oculta.

clip-path
midiendo…
foto.css
.foto {
clip-path: circle(40% at 50% 50%);
}
foto.html
<img class="[clip-path:circle(40%_at_50%_50%)]" src="lago.jpg" alt="Lago entre montañas">

Para curvas existe path(), que recibe el mismo texto que el atributo d de SVG. Funciona en todos los navegadores, pero sus coordenadas son píxeles fijos: la forma no se adapta si la caja cambia de tamaño.

La función nueva shape() resuelve eso: dibuja con comandos legibles (line to, curve to, arc to) y acepta porcentajes y calc(). Es Baseline desde febrero de 2026 (Chrome 135, Safari 18.4, Firefox 148), así que en navegadores un poco más viejos conviene tener un plan B.

foto.css
.foto {
/* respaldo: un rectángulo con esquinas redondeadas */
clip-path: inset(0 round 16px);
}
@supports (clip-path: shape(from 0 0, line to 100% 0, close)) {
.foto {
/* un arco arriba que se estira con la caja */
clip-path: shape(
from 0% 100%,
line to 0% 32%,
curve to 100% 32% with 50% -32%,
line to 100% 100%,
close
);
}
}

Animar el recorte

clip-path se puede animar, y no recalcula el layout: solo cambia lo que se pinta. Hay una condición: las dos formas tienen que usar la misma función y, en un polygon(), la misma cantidad de puntos. De inset() a inset() interpola; de circle() a inset() salta de golpe.

Un recorte que crece desde un borde revela una imagen sin moverla. Como cualquier panel, abre en 400ms y cierra un poco más rápido. Con movimiento reducido, el recorte se reemplaza por un fundido. Elegí desde dónde se revela y ocultá la foto: la línea naranja sigue el borde del recorte.

Revelar con
visible
portada.css
.portada {
transition: clip-path 350ms cubic-bezier(0.22, 1, 0.36, 1);
}
.portada[data-shown] {
transition-duration: 400ms;
}
@media (prefers-reduced-motion: no-preference) {
.portada { clip-path: inset(100% 0 0 0); }
.portada[data-shown] { clip-path: inset(0 0 0 0); }
}
/* movimiento reducido: sin recorte, solo un fundido */
@media (prefers-reduced-motion: reduce) {
.portada { opacity: 0; transition-property: opacity; }
.portada[data-shown] { opacity: 1; }
}
portada.html
<img data-shown class="transition-[clip-path] duration-350 data-shown:duration-400 ease-[cubic-bezier(0.22,1,0.36,1)]
motion-safe:[clip-path:inset(100%_0_0_0)] motion-safe:data-shown:[clip-path:inset(0_0_0_0)]
motion-reduce:opacity-0 motion-reduce:transition-opacity motion-reduce:data-shown:opacity-100"
src="glaciar.jpg" alt="Bloques de hielo de un glaciar">

Si el recorte crece desde el centro con circle(), el círculo final usa 75% y no 50%: las esquinas de la caja están al 70,7% del radio de referencia, y con menos quedarían cortadas.

Máscaras con mask-image

Una máscara usa una imagen como canal de opacidad. Donde la máscara es opaca, el elemento se ve; donde es transparente, desaparece; en el medio, se ve a medias. El color no importa, solo el alfa. Por eso un degradado de black a transparent sirve para desvanecer un borde.

A diferencia de clip-path, que corta en seco, la máscara puede ser gradual. Si el corte de transparent queda antes que el de black, el navegador lo empuja hasta el anterior y el fundido se vuelve un corte seco.

mask-image
linear-gradient()
fundido: 50% del recorrido
foto.css
.foto {
mask-image: linear-gradient(to bottom, black 40%, transparent 90%);
}
foto.html
<img class="mask-b-from-40% mask-b-to-90%" src="dunas.jpg" alt="Dunas">

La máscara también puede ser una imagen, por ejemplo un SVG o un PNG con transparencia. Se acomoda con las mismas ideas que un fondo: mask-size, mask-position y mask-repeat, o todo junto en el atajo mask.

mascaras.css
.icono {
background: currentColor; /* el color sale del texto */
mask: url(estrella.svg) center / 24px no-repeat;
}
.foto {
/* dos capas: todo opaco menos un círculo en el centro */
mask-image:
linear-gradient(black, black),
radial-gradient(circle, black 30%, transparent 31%);
mask-composite: exclude;
}

Con varias capas, mask-composite decide cómo se combinan: add (la suma, el valor inicial), subtract, intersect o exclude. Las máscaras sin prefijo son Baseline desde diciembre de 2023 y están disponibles en todos lados desde junio de 2026. Antes había que escribir -webkit-mask-image.

Texto alrededor de una forma

shape-outside cambia la forma que el texto rodea, no la del elemento. Solo funciona en elementos con float: las líneas de al lado se acortan o se alargan para seguir la curva. La imagen no se recorta; para eso sigue haciendo falta border-radius o clip-path.

La forma se mide sobre la caja de margen salvo que digas otra cosa. Como el disco tiene margin a la derecha y abajo, el círculo se calcula sobre border-box para que coincida con la foto. La distancia entre el texto y la forma la da shape-margin. Cambiá la forma y mové el control: la línea naranja marca el área que el texto esquiva.

shape-outside
rodea el disco a 8px
disco.css
.disco {
float: left;
width: 112px;
aspect-ratio: 1;
margin: 0 16px 16px 0;
border-radius: 50%;
shape-outside: circle(50%) border-box;
shape-margin: 8px;
}
disco.html
<img class="float-left w-28 aspect-square mr-4 mb-4 rounded-full [shape-outside:circle(50%)_border-box] [shape-margin:8px]"
src="disco.jpg" alt="Disco de vinilo">
<p>Un LP de 12 pulgadas gira a 33⅓ rpm…</p>

También acepta una imagen: con shape-outside: url(planta.png) el texto sigue la silueta de los píxeles opacos, y shape-image-threshold define desde qué opacidad cuentan. Es Baseline desde 2020.

Esquinas con corner-shape

border-radius define cuánto ocupa cada esquina; corner-shape define qué curva la dibuja. Sin un radio no hace nada. Los fondos, los bordes, las sombras y el contorno siguen la nueva forma. Cada palabra es un atajo de superellipse(): round es superellipse(1), squircle es superellipse(2) y los valores negativos curvan hacia adentro.

round es la curva de siempre y squircle una más llena, como los íconos de las apps. bevel corta la esquina en recta; scoop la curva hacia adentro, como una entrada de cine, y notch le hace un escalón recto. Probá cada una y cambiá el radio: la línea naranja es la curva que calcula el navegador.

corner-shape
= superellipse(-1)
entrada.css
.entrada {
border-radius: 28px; /* todos: esquina redondeada */
corner-shape: scoop;
}
entrada.html
<div class="rounded-[28px] [corner-shape:scoop]">
Cine · F7
</div>

Soporte: por ahora solo Chrome y Edge 139 o posterior; Firefox y Safari lo tienen en versiones de prueba, y no es Baseline. Como un navegador que no lo entiende deja la esquina redondeada común, se puede usar hoy como mejora progresiva:

entrada.css
.entrada {
border-radius: 20px; /* todos: esquina redondeada */
corner-shape: scoop; /* Chrome y Edge 139+: hacia adentro */
}

En Tailwind

Tailwind trae clases para las máscaras, pero no para clip-path, shape-outside ni corner-shape: esas van como propiedad arbitraria entre corchetes, con guiones bajos en lugar de espacios.

CSSTailwind
clip-path: circle(40% at 50% 50%)[clip-path:circle(40%_at_50%_50%)]
mask-image: … to bottom, black 40%…mask-b-from-40% mask-b-to-90%
mask-image: var(--icon)mask-(--icon) mask-center
mask-composite: excludemask-exclude
shape-outside: circle(50%)[shape-outside:circle(50%)] float-left
corner-shape: squircle[corner-shape:squircle] rounded-2xl
portada.html
<!-- se revela desde abajo; con movimiento reducido, solo un fundido -->
<img data-shown
class="transition-[clip-path] duration-350 data-shown:duration-400
motion-safe:[clip-path:inset(100%_0_0_0)]
motion-safe:data-shown:[clip-path:inset(0_0_0_0)]
motion-reduce:opacity-0 motion-reduce:transition-opacity
motion-reduce:data-shown:opacity-100"
src="glaciar.jpg" alt="…">