Formas con CSS

Cómo crear formas con HTML/CSS


En muchas situaciones puede ser interesante crear formas geométricas o formas orgánicas para representar ciertos elementos, y que se adapten a nuestro gusto. Hacerlo con código nos permite que sea sencillo adaptarse a situaciones cuando hagamos modificaciones y evitamos diseños más rígidos creados con imágenes.

Cuadrado

Probablemente, la forma más sencilla que ya estaremos acostumbrados a crear es un cuadrado. Sin embargo, aprovechemos para organizar bien el código y observar como podemos hacer que nuestro código sea más legible y fácil de comprender y modificar:

body {
  background: #111;
}

.box {
  --size: 200px;
  --gradient: linear-gradient(indigo, hotpink);

  width: var(--size);
  height: var(--size);
  background: var(--gradient);
}
<div class="box"></div>

En este caso, utilizamos las variables CSS --size para el tamaño del cuadrado, que es el mismo de ancho que de alto, y --gradient para guardar el gradiente de colores.

Rectángulo

En el caso de querer generar un rectángulo, la diferencia es que no utilizamos el mismo tamaño de ancho y de alto, sino que utilizaremos la variable --w para el width (ancho) y --h para el height (alto):

body {
  background: #111;
}

.rect {
  --w: 450px;
  --h: 200px;
  --gradient: linear-gradient(indigo, hotpink);

  width: var(--w);
  height: var(--h);
  background: var(--gradient);
}
<div class="rect"></div>

Como en el anterior, también tenemos la variable --gradient para el degradado del elemento.

Círculo

Volvamos al ejercicio del cuadrado donde usamos el mismo tamaño para ancho y alto. Ahora, utilizamos la propiedad border-radius para redondear las esquinas del elemento. Si lo hacemos utilizando , al 50% conseguiremos el valor máximo para redondear, independientemente del tamaño que tenga el elemento:

body {
  background: #111;
}

.circle {
  --size: 200px;
  --gradient: linear-gradient(indigo, hotpink);

  width: var(--size);
  height: var(--size);
  background: var(--gradient);
  border-radius: 50%;
}
<div class="circle"></div>

Si además cambiamos el tamaño de ancho con un alto diferente, tendríamos un óvalo o círculo ovalado.

Semicírculo

Ahora, si al círculo anterior, le hacemos algunas modificaciones en el border-radius podemos conseguir un semicírculo. La idea es tener también la varible --half-size que es la mitad de --size.

Además de esto, observa que border-radius utiliza el formato de x e y, es decir, border-radius: x x x x / y y y y. Los valores colocados a la izquierda del slash / son los valores de redondeo de esquinas en x, mientras que los valores a la derecha del slash / son los valores de redondeo de esquinas en y:

body {
  background: #111;
}

.circle {
  --size: 400px;
  --half-size: calc(var(--size) / 2);
  --gradient: linear-gradient(indigo, hotpink);

  width: var(--size);
  height: var(--half-size);
  background: var(--gradient);
  border-radius: 50% 50% / var(--half-size) var(--half-size) 0 0;
}
<div class="circle"></div>

Obviamente, modificando la posición de estos valores, podríamos conseguir semicírculos en diferentes posiciones.

Triángulo

Este ejemplo resulta más interesante y complejo. Esta es una forma antigua de crear un triángulo con CSS. Se aprovecha de que los navegadores, si creas un border con diferentes colores los une formando una unión triangular. Esto, unido a que le reducimos el tamaño a 0, hace que se forme una especie de triángulo:

body {
  background: #111;
}

.triangle {
  --size: 200px;

  width: 0;
  height: 0;
  border-left: var(--size) solid transparent;
  border-right: var(--size) solid transparent;
  border-bottom: calc(var(--size) * 1.5) solid indigo;
}
<div class="triangle"></div>

Sólo restaría cambiar los bordes que no interesan a transparent y ya lo tendríamos. La limitación de este método es que estamos obligados a utilizar sólo un color, ya que es un color de borde.

A continuación tenemos otra forma de generar un triángulo, que puede ser interesante en otras situaciones. En este nuevo ejemplo, creamos una forma con la función polygon(), en forma de triángulo con 3 puntos clave, y la recortamos con clip-path:

body {
  background: #111;
}

.triangle {
  --triangle-shape: polygon(50% 0, 100% 100%, 0 100%);
  --gradient: linear-gradient(indigo, hotpink);

  background: var(--gradient);
  width: 350px;
  height: 300px;
  clip-path: var(--triangle-shape);
}
<div class="triangle"></div>

Lo bueno de este método es que si que permite utilizar imágenes o gradientes de fondo.

Bocadillo (Message globe)

Veamos ahora como hacer un globo de texto, un mensaje o bocadillo, de los que se utilizan en comics para que los personajes muestren un diálogo. Esto sería tan fácil como crear un cuadrado (también se podría redondear con border-radius si se desea) y añadirle un triángulo que haga de dirección.

El globo de texto va a ser el elemento con clase .message y el triángulo de flecha va a ser el ::after del .message. Observa el siguiente fragmento de código:

body {
  background: #111;
}

.message {
  background: white;
  color: black;
  max-width: 200px;
  min-height: 100px;
  padding: 1rem;
  position: relative;

  &::after {
    --size: 25px;

    content: "";
    display: block;
    width: var(--size);
    height: var(--size);
    position: absolute;
    background: white;
    bottom: calc(var(--size) * -1);
    clip-path: polygon(0 0, 100% 0, 0 100%);
  }
}
<div class="message">Un mensaje que para un globo de texto (bocadillo).</div>

En este caso, utilizamos un position: absolute y lo bajamos con bottom para que la flecha aparezca por debajo del elemento.

Hoja (Leaf)

La siguiente forma simula la hoja de un árbol, donde simplemente utilizamos border-radius alternando el redondeo para que afecte sólo a dos esquinas. De esta forma, y junto a un gradiente de colores verdes, le dan el aspecto de hoja de árbol:

body {
  background: #111;
}

.leaf {
  --size: 200px;
  --gradient: linear-gradient(limegreen, darkgreen);

  background: var(--gradient);
  width: var(--size);
  height: var(--size);
  border-radius: 50% 0 50% 0;
}
<div class="leaf"></div>

Gota (Tear)

En esta nueva forma, conseguimos una gota dibujando un rectángulo que tiene las esquinas inferiores redondeadas. Luego, aplicamos un recorte con clip-path que recorta en forma triangular por la parte superior, y mantiene todo por la parte inferior:

body {
  background: #111;
}

.circle {
  --size: 200px;
  --half-size: calc(var(--size) / 2);
  --gradient: linear-gradient(steelblue, darkblue);

  width: var(--size);
  height: calc(var(--size) * 1.5);
  background: var(--gradient);
  border-radius: 50% 50% / 0 0 var(--half-size) var(--half-size);
  clip-path: polygon(50% 0, 99% 62%, 100% 65%, 100% 100%, 0 100%, 0 65%, 1% 62%);
}
<div class="circle"></div>

Otra forma de dibujar la gota podría ser un rectángulo recortado, que tenga un ::after o ::before circular, que haga de la parte de la gota.

Corazón (Heart)

En este caso, creamos un corazón, que estará compuesto por tres elementos: el elemento con clase .heart y dos elementos más, el ::after y el ::before. Con el primero, crearemos un cuadrado rotado, para el "cuerpo" del corazón, y con el ::after y el ::before crearemos dos círculos que superpondremos sobre el cuadrado rotado, consiguiendo el corazón:

body {
  background: #111;
}

.heart {
  --size: 100px;
  --half-size: calc(var(--size) / 2);

  background: red;
  width: var(--size);
  height: var(--size);
  margin: 4rem 2rem;
  transform: rotate(45deg);

  &::before,
  &::after {
    content: "";
    background: red;
    display: block;
    width: var(--size);
    height: var(--size);
    border-radius: 50%;
    position: absolute;
    transform: translate(var(--x, 0%), var(--y, 0%));
  }

  &::before { --y: -50% }
  &::after { --x: -50% }
}
<div class="heart"></div>

Hemos utilizado las variables --x e --y para simplificar código y generalizar.

Huevo (Egg)

En este caso, jugamos con border-radius para conseguir la forma perfecta para crear un huevo. El truco está en conseguir el tamaño perfecto que coincida con la forma de los redondeos de las esquinas:

body {
  background: #111;
}

.egg {
  --size: 200px;
  --half-size: calc(var(--size) / 2);
  --gradient: linear-gradient(#e3b882, #3f2a10);

  width: var(--size);
  height: calc(var(--size)* 1.35);
  background: var(--gradient);
  border-radius: 50% / 65% 65% var(--half-size) var(--half-size);
}
<div class="egg"></div>

Luna (Moon)

Por último, vamos a crear una luna en un eclipse. En este ejemplo, observa que hemos creado las variables --size donde le damos un tamaño a la luna. Luego, en su interior, aplicamos una máscara con mask-image que será un gradiente radial en forma circular, que aplicaremos en el círculo para restar su interior y quedarnos con el borde de la luna.

Con los sliders de las variables --x e --y, podemos modificar la posición del gradiente utilizado en la máscara y comprobar como quedaría:

body {
  background: #111;
  color: #fff;
}

.moon {
  --size: 150px;
  --offset: 100%;
  --x: 70%;
  --y: 50%;

  background: white;
  width: var(--size);
  height: var(--size);
  border-radius: 50%;
  mask-image: radial-gradient(
    circle calc(var(--size) * 0.45) at var(--x) var(--y),
    transparent var(--offset),
    black calc(var(--offset) + 1%)
  );
}
const moon = document.querySelector(".moon");
const [x, y] = document.querySelectorAll("input");
const [xo, yo] = document.querySelectorAll("output");

x.addEventListener("input", () => {
  xo.textContent = x.value + "%";
  moon.style.setProperty("--x", xo.textContent);
});

y.addEventListener("input", () => {
  yo.textContent = y.value + "%";
  moon.style.setProperty("--y", yo.textContent);
});
<p>
  Valor de <code>--x</code>:
  <input type="range" min="0" max="100" value="70">
  <output>70%</output>
</p>
<p>
  Valor de <code>--y</code>:
  <input type="range" min="0" max="100" value="50">
  <output>50%</output>
</p>
<div class="moon"></div>

Como ves, todo se basa en descomponer la forma que deseas en formas geométricas básicas. Si quieres crear una forma concreta, lo mejor es que hagas esa tarea de separación y luego las unas. En algunos casos puede ser más complejo porque pueden añadirse ciertas limitaciones, como que sólo permite utilizar colores planos o similares.

¿Quién soy yo?

Soy Manz, vivo en Tenerife (España) y soy streamer partner en Twitch y profesor. Me apasiona el universo de la programación web, el diseño y desarrollo web y la tecnología en general. Aunque soy full-stack, mi pasión es el front-end, la terminal y crear cosas divertidas y locas.

Puedes encontrar más sobre mi en Manz.dev