Skip to main content
Photography of Alvaro Montoro being a doofus
Alvaro Montoro

Sofia's Father

screenshot of a login form

Building an interactive form: CSS image

html css webdev tutorial

Note: There are unlimited ways of drawing a character. This is my character and my way to draw it. It is simple to animate and easy to draw (which is convenient for a tutorial). You can make a character as complex and complete as you like, and using the technology that you want. Own your character, don't limit yourself to this example.

In this post, we will explain how to draw a cartoon face in CSS while answering any question that may pop up. Like for example...

Why a CSS image and not an SVG?

That is a great question -thanks for asking-, unfortunately, there's not a clear-cut answer. SVG is better for drawing than CSS, mainly because SVG is designed for drawing and CSS is not... but using CSS for simple drawings has some advantages:

  • It doesn't require any additional learning (still you should learn SVG because it is interesting).
  • It is lightweight and scalable (as SVG is).
  • Some browsers don't fully support the animation of SVGs (it would require a library like GreenSock)

Also, a selfish reason: it is my tutorial! I want to keep it simple and not introduce new concepts. And I like playing with CSS drawings so making a small one for this tutorial is fine :)

Let's get back on track...

Designing the character

The person is going to be a vectorial looking character made primarily of circles and rectangles (check this article for the many shapes of CSS):

  • The neck and shirt are going to be rectangles
  • The body is going to be an ellipse
  • And everything else is basically going to be a circle: head, ears, eyes... even the mouth!

Note: add eyebrows to your character. They give a ton of expression and personality to the drawing (even more when animated). I kind of regret not adding them to mine.

Here it is a schema of what we are going to do:

Schema of the CSS character with the different parts highlighted and explained

And here it is the HTML code for it:

  <figure aria-hidden="true">
    <div class="person-body"></div>
    <div class="neck skin"></div>
    <div class="head skin">
      <div class="eyes"></div>
      <div class="mouth"></div>
    </div>
    <div class="hair"></div>
    <div class="ears"></div>
    <div class="shirt-1"></div>
    <div class="shirt-2"></div>
  </figure>

We added aria-hidden="true" to the figure because it is only decorative and we want it to be ignored by screen readers (it didn't have any content to read, but just in case).

The different parts of our character are organized in order from back to front: the person's body will be in the background and the shirt will be on top. And everything will be wrapped in a circle (the figure) with hidden overflow.

Drawing the character

We will not speak about everything in the CSS drawing (you can see the code in the fiddle below), but some parts may be interesting or need a deeper explanation, and we will dive into them.

Setting the "canvas"

We use the figure as the "canvas" for our drawing, make it circular, and hide the overflow. Also, we set the position to relative so we can position the different elements in an absolute way (something that we set right below).

Here is the CSS for the figure:

figure {
  --skinH: 30;
  --skinS: 100%;
  --skinL: 87%;
  --hair: rgb(180, 70, 60);
  background: hsl(var(--fgColorH), calc(var(--fgColorS) * 2), 95%);
  border: 1px solid rgba(0, 0, 0, 0.0625);
  border-radius: 50%;
  height: 0;
  margin: auto auto;
  margin-bottom: 2rem;
  order: 1;
  overflow: hidden;
  padding-top: 60%;
  position: relative;
  width: 60%;
}

figure div {
  position: absolute;
  transform: translate(-50%, -50%);
}

We double down in the use of HSL and CSS variables. This may be convenient for things like changing the color of the skin for example. the character is white, but you could easily change the skin tone by updating the light (a smaller percentage means darker skin) or adjusting the hue (beware as this could lead to strange results too.)

Hair

All the other elements are drawn directly using an element and giving it shape, but the hair is special.

In this case, the shape is used to limit the hair, and a pseudo-element is used to draw the hair itself with a box-shadow (for the right and left sides):

figure .hair {
  top: 40%;
  left: 50%;
  width: 66.66%;
  height: 66.66%;
  border-radius: 100%;
  overflow: hidden;
}

figure .hair::before {
  background: var(--hair);
  border-radius: 50%;
  box-shadow: 4rem 0 var(--hair);
  content: "";
  display: block;
  height: 100%;
  left: -50%;
  position: absolute;
  top: -60%;
  width: 100%;
}

Again CSS variables to be able to easily change the hair color just by changing a small part of the code.

Mouth

Something is interesting about the mouth. Many people that draw in CSS, if they want a rounded line they will draw a circle, and only add one border. That is perfectly fine, but it has some issues: if no other borders are specified, they will be zero, and our line will become thinner at the edges.

I honestly don't like that. Instead, I prefer to add a transparent border to the element, and then change the color of the side I want to use. That way the line will always have the same width (although it may end too "abruptly" for some people's liking.)

figure .head .mouth {
  border: 0.125rem solid transparent;
  border-bottom: 0.125rem solid var(--borderDarker);
  width: 25%;
  border-radius: 50%;
  transition: all 0.5s;
  top: 75%;
  left: 50%;
  height: 10%;
}

Shirt

For the shirt, we use two skewed rectangles, one opposite to the other. The code for this is not especially difficult or interesting, but it may be worth a look if you haven't combined selectors in CSS before, or to see how you can apply multiple transforms in one property:

figure .shirt-1,
figure .shirt-2 {
  width: 12%;
  height: 7%;
  background: hsl(var(--bgColorH), var(--bgColorS), var(--bgColorL));
  top: 76%;
  left: 36.5%;
  transform: skew(-10deg) rotate(15deg);
}

figure .shirt-2 {
  left: 52.5%;
  transform: skew(10deg) rotate(-15deg);
}

Animating the eyes

So far our character is static: it doesn't move at all, and it looks like a still image. To change this, we are going to add some CSS animation to the mix. We will make our character blink. It is something simple -readjusting the height of the eyes-, but it will bring our little drawing to life.

This is the CSS for the blinking animation:

@keyframes blink {
  0%, 90%, 100% {
    height: 10px;
  }
  95% {
    height: 0px;
  }
}

figure .head .eyes::before,
figure .head .eyes::after {
  animation: blink 5s infinite;
}

You may say now: "Hey! you use percentages and rem everywhere, why is the animation using pixels?" And that's another great question. When we tested on Safari, the animation didn't work as expected when using rem, but changing to its px equivalent worked just fine. It could be a bug with Safari with the rem unit on animations or specifically on pseudo-elements' animations. Whatever the case, using pixels will fix it.


With the animated CSS character, the form looks like this on JSFiddle:

We had an ugly but functional form in HTML, then we added styling and a friendly-looking character on top using CSS. It's time to get our hands dirty with JavaScript, and connecting everything... after that, let's give some final touches in CSS to add some fallback interactivity.

Article originally published on