Volver a las notas

CSS shape(): formas responsive sin SVG

22 de octubre de 2026·
cssclippathshapeanimation
Demo en vivoAbrir a pantalla completa

Ya puedes dibujar una estrella, un blob o un bocadillo en clip-path con porcentajes, px, calc() y custom properties, y pasar de una forma a otra con un solo transition: la función shape() de CSS es Baseline desde febrero de 2026.

Durante años, cualquier clip-path más interesante que circle() o polygon() pasaba por path(), y path() recibe una cadena de trazado SVG en la que cada número es un píxel. Para un icono de tamaño fijo vale, pero en cuanto el elemento cambia de tamaño se rompe: la forma no escala y acabas recalculando la cadena con JavaScript. shape() usa el mismo modelo de dibujo que los trazados SVG, pero con sintaxis CSS, así que cada coordenada puede ser el valor CSS que tenga sentido.

Qué es shape()

shape() es una función <basic-shape>, y MDN la documenta para clip-path, offset-path y la más reciente border-shape. Recibe un punto de partida y una lista de comandos separados por comas:

.triangle {
  clip-path: shape(from 0% 0%, line to 100% 0%, line to 50% 100%, close);
}

Los comandos son los de los trazados SVG, con palabras en lugar de letras:

  • move, line, hline y vline para saltos y líneas rectas.
  • curve ... with ... para curvas de Bézier: con un punto de control es cuadrática; con dos (separados por /), cúbica.
  • smooth para una curva que continúa la anterior, arc para arcos elípticos y close para volver al inicio.
  • to indica coordenadas medidas desde la esquina superior izquierda de la caja de referencia; by, relativas al punto donde terminó el comando anterior.

Los porcentajes se calculan sobre el ancho y el alto de la caja de referencia. Y según MDN, ahí está la diferencia real con path(): path() es una sola cadena limitada a px, mientras que shape() acepta porcentajes, rem, em y funciones matemáticas como calc(), max() o abs().

Mezclar unidades: el bocadillo responsive

La segunda pestaña de la demo de arriba es un bocadillo. El truco está en decidir qué escala y qué no: las esquinas y la flecha van fijas en px, y la caja y la posición de la flecha, en porcentaje. Como shape() es CSS sin más, los valores salen directamente de custom properties:

.bubble {
  --r: 18px; --arrow: 30%; --tail: 16px;
  clip-path: shape(from var(--r) 0,
    hline to calc(100% - var(--r)),
    curve to 100% var(--r) with 100% 0,
    vline to calc(100% - var(--tail) - var(--r)),
    curve to calc(100% - var(--r)) calc(100% - var(--tail)) with 100% calc(100% - var(--tail)),
    hline to calc(var(--arrow) + var(--tail)),
    line to var(--arrow) 100%,
    line to calc(var(--arrow) - var(--tail)) calc(100% - var(--tail)),
    hline to var(--r),
    curve to 0 calc(100% - var(--tail) - var(--r)) with 0 calc(100% - var(--tail)),
    vline to var(--r),
    curve to var(--r) 0 with 0 0,
    close);
}

Léelo como un paseo por el borde: el lado de arriba, una curva cuadrática para la esquina superior derecha (el punto de control es la propia esquina), bajada por la derecha, la esquina inferior derecha, el borde de abajo hasta la flecha, la punta en 100% y vuelta a subir, y lo mismo por la izquierda. Si mueves el control de ancho en la demo, las esquinas siguen redondas en lugar de convertirse en elipses, porque su radio está en px. La propia especificación usa un bocadillo como este para explicar por qué importa mezclar unidades.

Los controles no reconstruyen la cadena: solo cambian --r, --arrow y --tail con style.setProperty(). El navegador vuelve a dibujar la forma.

Morphing gratis

La primera pestaña es la parte vistosa. La especificación dice que dos valores shape() (o un shape() y un path()) se pueden interpolar si sus listas de comandos tienen la misma longitud y los mismos comandos en el mismo orden. Un comando cuenta como "el mismo" si usa la misma palabra clave y el mismo by/to y, en curve y smooth, el mismo número de puntos de control.

Así que, si diseñas todas las formas con el mismo esqueleto, un transition hace el morphing:

.badge {
  clip-path: shape(from 0% 0%, line to 100% 0%, line to 100% 100%, line to 0% 100%, close);
  transition: clip-path .6s ease;
}
.badge:hover {
  clip-path: shape(from 50% 0%, line to 100% 50%, line to 50% 100%, line to 0% 50%, close);
}

En la demo, el círculo, la estrella, la flor y el blob aleatorio son siempre doce comandos curve to X Y with CX CY generados con coordenadas polares. La estrella simplemente coloca cada punto de control en la mitad del segmento, así que la curva parece recta. Como el esqueleto no cambia, cualquier forma se transforma en cualquier otra.

También comprobé en Chromium el caso contrario: si en el estado :hover cambio uno de los line por un curve, la transición deja de interpolar y la forma salta directamente al valor final.

Soporte y trampas

Según los datos de compatibilidad de MDN, shape() funciona en Chrome y Edge 135, Firefox 148 y Safari 18.4. Pasó a Baseline "newly available" el 24 de febrero de 2026, así que todavía no es "widely available". Para navegadores antiguos, la detección es directa:

@supports not (clip-path: shape(from 0 0, line to 100% 0, line to 0 100%, close)) {
  .badge { clip-path: polygon(0 0, 100% 0, 100% 100%, 0 100%); }
}

La demo hace la misma comprobación en JavaScript con CSS.supports(). Si falla, recurre a path() con valores en píxeles calculados a partir del tamaño del elemento y recalculados con un ResizeObserver. Justo el trabajo que shape() te ahorra. Puedes ver ese plan B añadiendo ?fallback a la URL de la demo.

Cosas a tener en cuenta:

  • clip-path solo recorta. Las sombras y los bordes también se recortan, así que el resplandor de la demo es un filter: drop-shadow() en el elemento padre, que sigue la forma recortada.
  • Los valores computados conservan las unidades que escribiste: getComputedStyle() devuelve algo como shape(from 0px 0px, hline to calc(100% - 20px), ...), lo que ayuda al depurar.

Qué vigilar

  • border-shape: también acepta shape(), pero en los datos de compatibilidad de MDN solo aparece en Chrome 147. Es la pieza que permitiría dar forma también al borde, no solo recortar el elemento.
  • Puntos de control relativos: la especificación define from start, from end y from origin para los puntos de control. Los ejemplos de MDN los usan detrás de su propio @supports, así que pruébalos en todos los navegadores antes de depender de ellos.
  • Convertir trazados existentes: si ya tienes una cadena d de SVG, pasarla a shape() es mecánico (M → from/move, L → line, Q/C → curve, Z → close), y es el momento de cambiar píxeles por porcentajes.

Fuentes