Advice for Complex CSS Illustrations
Tags: css, webdev, design
Published: 2020-07-19 16:09:34

If you were to ask me what question I hear most about front-end development, I’d say it’s“How do I get better at CSS?”. That question usually comes up after sharing a CSS illustration I have made. It’s something [I love to do over on CodePen](https://codepen.io/jh3y).

To many, CSS is this mythical beast that can’t be tamed. [This tweet](https://twitter.com/chriscoyier/status/1248088743002677248) from Chris made me chuckle because, although ironic, there’s a lot of truth to it. That said, what if I told you that you were only a few properties and techniques away from creating anything you wanted? The truth is that you are indeed that close.

I’ve been wanting to compose an article like this for some time, but it’s a hard topic to cover because there are so many possibilities and so many techniques that there’s often more than one way to accomplish the same thing. The same is true with CSS illustrations. There’s no right or wrong way to do it. We’re all using the same canvas. There are simply so many different tools to get those pixels on the page.

While there is no “one size fits all” approach to CSS illustration, what I can offer is a set of techniques that might help you on your journey.

{% codepen https://codepen.io/jh3y/pen/mLaXRe %}


# Time and practice
CSS illustration takes lots of time and practice. The more accurate you want to be and the more complicated the illustration, the longer it’s going to take. The time-consuming part isn’t usually deciding on which properties to use and how, but the tinkering of getting things to look right. __Be prepared to get very familiar with the [styles inspector](https://developers.google.com/web/tools/chrome-devtools/css) in your browser dev tools!__ I also recommend trying out [VisBug](https://chrome.google.com/webstore/detail/visbug/cdockenadnadldjbbgcallicgledbeoc?hl=en) if you haven’t.

Two fantastic CSS artists are Ben Evans and Diana Smith. Both have recently talked about time consumption when referring to CSS illustration.

![Screenshot of a photo-realistic woman made with CSS](https://dev-to-uploads.s3.amazonaws.com/i/247oh2uort7p6kpgd11g.png)
<figcaption>Diana’s PureCSS Gaze took her two long weekends to complete. She talks about some of her techniques here and here. “If you have the time, patience, and drive, it is certainly possible,” she says.
</figcaption>

{% twitter 1040638809770143744 %}

I posted a meme-like picture about a cup and [Ben’s response](https://twitter.com/ivorjetski/status/1259893362129350658) summed things up perfectly:

> I was tempted to create this in CSS when I first saw the  tweet but then thought my reply would take about a month.

It takes time!

{% twitter 1259487385554911233 %}

# Tracing is perfectly acceptable

We often have an idea of what it is that we want to illustrate. This article isn’t about design, after all. It’s about taking an image and rendering it with the DOM and CSS. I’m pretty sure this technique has been around since the dawn of time. But, it’s one I’ve been [sharing](https://twitter.com/jh3yy/status/1258130582841757700) the last few months.

- Find or create an image of what it is you want to illustrate.
- Pull it into your HTML with an `<img>` tag.
- Position it in a way that it will sit underneath your illustration.
- Reduce the image opacity so that it’s still visible but not too overpowering.
- Trace it with the DOM.

To my surprise, this technique [isn’t common knowledge](https://twitter.com/therealpaulcook/status/1256283143587405824). But it’s invaluable for creating accurate CSS illustrations.

See this trick in action here:

{% twitter 1256281143244136448 %}

And try it out here:

{% codepen https://codepen.io/jh3y/pen/OJMNVZR %}

# Pay attention to responsiveness

If there are two takeaway techniques to take from this article, let it be the “Tracing” one above and this next one. 

There are some fantastic examples of CSS illustration out there. But the one unfortunate thing about some of them is that they aren’t styled — or even viewable — on small screens. We live in an age where first impressions with tech are important. Consider the example of a keyboard illustrated with CSS. Someone comes across your work, opens it up on their smartphone, and they’re greeted with only half the illustration or a small section of it. They probably missed the coolest parts of the demo!

Here’s my trick: __leverage viewport units for your illustrations and create your own scaled unit.__ 

For sizing and positioning, you either have the option of using a scaled unit or percentage. This is particularly useful when you need to use a box shadow because the property accepts viewport units but not percentages.

Consider the CSS [egghead.io](https://egghead.io/instructors/jhey-tompkins) logo I created above. I found the image I wanted to use and popped it in the DOM with an `img` tag.

```html
<image src='egghead.png'/>
```
```css
img {
  height: 50vmin;
  left: 50%;
  opacity: 0.25;
  position: fixed;
  top: 50%;
  transform: translate(-50%, -50%);
}
```
The height, `50vmin`, is the desired size of the CSS illustration. The reduced opacity allows us to “trace” the illustration clearly as we progress.

Then, we create a scaled unit.

```css
/**
  * image dimensions are 742 x 769
  * width is 742
  * height is 769
  * my desired size is 50vmin
*/
:root {
  --size: 50;
  --unit: calc((var(--size) / 769) * 1vmin);
}
```

With the image dimensions in place, we can create a uniform unit that’s going to scale with our image. We know the height is the largest unit, so we use that as a base to create a fractional unit.

We get something like this:

```css
--unit: 0.06501950585vmin;
```

That looks awkward but, trust me, it’s fine. We can use this to size our illustration’s container using `calc()`.

```css
.egg {
  height: calc(769 * var(--unit));
  position: relative;
  width: calc(742 * var(--unit));
  z-index: 2;
}
```

If we use either percentages or our new `--unit` custom property to style elements within the container of our CSS illustration, we will get responsive CSS illustrations… and all it took was a few lines of math using CSS variables!

Resize this demo and you’ll see that everything stays in proportion always using `50vmin` as the sizing constraint.

{% codepen https://codepen.io/jh3y/pen/rNOzYJZ %}

# Measure twice, cut once

Another tip is to measure things. Heck, you van even grab a tape measure if you’re working with a physical object!

{% codepen https://codepen.io/jh3y/pen/BaNGKPw %}

This may look a little funky but I measured this scene. It’s the TV combo unit I have in my lounge. Those measurements equate to centimeters. I used those to get a responsive unit based on the actual height of the TV. We can give that number — and all others — a name that makes it easy to remember what it’s for, thanks to custom properties.

```css
:root {
  --light-switch: 15;
  --light-switch-border: 10;
  --light-switch-top: 15;
  --light-switch-bottom: 25;
  --tv-bezel: 15;
  --tv-unit-bezel: 4;
  --desired-height: 25vmin;
  --one-cm: calc(var(--desired-height) / var(--tv-height));
  --tv-width: 158.1;
  --tv-height: 89.4;
  --unit-height: 42;
  --unit-width: 180;
  --unit-top: 78.7;
  --tv-bottom: 114.3;
  --scaled-tv-width: calc(var(--tv-width) * var(--one-cm));
  --scaled-tv-height: calc(var(--tv-height) * var(--one-cm));
  --scaled-unit-width: calc(var(--unit-width) * var(--one-cm));
  --scaled-unit-height: calc(var(--unit-height) * var(--one-cm));
}
```

As soon as we calculate a variable, we can use it everywhere. I know my TV is `158.1cm` wide and `89.4cm` tall. I checked the manual. But in my CSS illustration, it will always scale to `25vmin`.

# Use absolute positioning on all the things

This one will save you a few keystrokes. More often than not, you’ll be looking to absolutely position elements. Save yourself and put this rule somewhere.

```css
/* Your class name may vary */
.css-illustration *,
.css-illustration *:after,
.css-illustration *:before,
.css-illustration:after,
.css-illustration:before {
  box-sizing: border-box;
  position: absolute;
}
```

Your keyboard will thank you!

Positioning is a tricky concept in CSS. You can read up on it in the CSS Almanac for more information on how to use it.

Or, have a play with this little positioning playground:

{% codepen https://codepen.io/jh3y/pen/pogyJbw %}

# Stick to an approach
This is by far the hardest thing to do. How do you approach a CSS illustration? Where do you even start? Should you start with the outermost part and work your way in? That doesn’t work so well.

Odds are that you’ll try some approaches and find a better way to go about it. You’ll certainly do a little back-and-forth but, the more you practice, the better you’ll get at spotting patterns and developing an approach that works best for you.

I tend to relate my approach to how you’d go about creating a vector image where illustrations are made up of layers. Split it up and sketch it on paper if you need to. But, start from the bottom and work your way up. This tends to mean larger shapes first, and finer details later. You can always tinker with the stacking index when you need to move elements around.

# Maintain a solid structure for your styles

That leads us to the structure. Try to avoid a flat DOM structure for your illustration. Keeping things atomic makes it easier to move parts of your illustration. It will also make it much easier to show and hide parts of the illustration or even animate them later. Consider the CSS Snorlax demo. The arms, feet, head, etc. are separate elements. That made animating the arm a lot easier than if I had tried to keep things together since I could simply apply the animation to the `.snorlax__arm-left` class.

{% codepen https://codepen.io/jh3y/pen/yLYXVJa %}

Here’s a timelapse shot of me creating the demo:

{% twitter 1255207715137339396 %}

# Handling awkward shapes
There’s a [pretty good article](https://css-tricks.com/the-shapes-of-css/) on CSS-Tricks for creating shapes with CSS. But what about more “awkward” shapes, like a long curve or even an outer curve? In these scenarios, we need to think outside the box. Properties such as `overflow`, `border-radius`, and `clip-path` are big helpers.

Consider this CSS Jigglypuff demo. Toggle the checkbox.

{% codepen https://codepen.io/jh3y/pen/gOaVJMB %}

That’s the key to creating curved shapes! We have an element much larger than the body with a `border-radius` applied. We then apply `overflow: hidden` to the body to cut that part off.

How might we create an outer curve? This one’s a little tricky. But a trick I like to use is a `transparent` element with a thick border. Then apply a `border-radius` and clip the excess, if required.

{% codepen https://codepen.io/jh3y/pen/ZEbgNKO %}

If you hit the toggle, it reveals the element we are using to go across that corner. Another trick might be to overlay a circle that matches the background color. This is fine until we need to change the background color. It’s OK if you have a variable or something in place for that color. But, it could make things a little harder to maintain.

# clip-path is your friend

You might have noticed a couple of interesting CSS properties in that last demo, including `clip-path`. You’ll most likely need clip-path if you want to create complex CSS shapes. It’s especially handy for cutting off bits of elements when hiding parent box overflow doesn’t do.

Here’s a little demo I built some time ago that showcases different `clip-path` possibilities.

{% codepen https://codepen.io/jh3y/pen/XqVQqa %}

There’s also this demo that takes ideas from the [“Shapes of CSS”](https://css-tricks.com/the-shapes-of-css/) article and re-created with `clip-path`.

{% codepen https://codepen.io/jh3y/pen/gOpLBEa %}

# border-radius is your other friend

You’re going to need [border-radius](https://css-tricks.com/almanac/properties/b/border-radius/) to create curves. One uncommon trick is to use a “double” syntax. This allows you to create a horizontal and vertical radius for each corner.

Play with this demo to really appreciate the power of `border-radius`. I advocate using percentages across the board in order to keep things responsive.

{% codepen https://codepen.io/jh3y/pen/XWmvwYg %}

# Shading techniques

You’ve got all the shapes, everything is nicely laid out, and all the right colors are in place… but something still looks off. Odds are that it’s a lack of shading.

Shading adds depth and creates a realistic feel. Consider this recreation of a Gal Shir illustration. Gal is fantastic at using shades and gradients to make beautiful illustrations. I thought it would be fun to do a recreate it and include a switch that toggles the shading to see just what a difference it makes.

{% codepen https://codepen.io/jh3y/pen/OJMNyVg %}

Shading effects are often created with a `box-shadow` and `background-image` combination.

The key thing with these properties is that we can stack them in a comma-separated list. For example, the cauldron in the demo has a list of gradients that are being used across the body.

```css
.cauldron {
  background:
    radial-gradient(25% 25% at 25% 55%, var(--rim-color), transparent),
    radial-gradient(100% 100% at -2% 50%, transparent, transparent 92%, var(--cauldron-color)),
    radial-gradient(100% 100% at -5% 50%, transparent, transparent 80%, var(--darkness)),
    linear-gradient(310deg, var(--inner-rim-color) 25%, transparent), var(--cauldron-color);
}
```

Note that `radial-gradient()` and a `linear-gradient()` are being used here and not always with perfectly round numeric values. Again, those numbers are just fine. In fact, you’ll spend a lot of time tweaking and tinkering with things in the style inspector.

It’s generally the same working with [box-shadow](https://css-tricks.com/snippets/css/css-box-shadow/). However, with that, we can also use the `inset` value to create tricky borders and additional depth.

```css
.cauldron__opening {
  box-shadow:
    0 0px calc(var(--size) * 0.05px) calc(var(--size) * 0.005px) var(--rim-color) inset,
    0 calc(var(--size) * 0.025px) 0 calc(var(--size) * 0.025px) var(--inner-rim-color) inset,
    0 10px 20px 0px var(--darkness), 0 10px 20px -10px var(--inner-rim-color);
}
```

There are certainly times where it will make more sense to go with [filter: drop-shadow()](https://css-tricks.com/almanac/properties/f/filter/) instead to get the effect you want.

Lynn Fisher’s [a.singlediv.com](https://a.singlediv.com/) is a brilliant example of these properties in action. Have a poke around on that site and inspect some of the illustrations for great ways to use `box-shadow` and `background-image` in illustrations.

`box-shadow` is so powerful that you could create your entire illustration with it. I once joked about creating a CSS illustration of a dollar.

{% twitter 1252924614281486336 %}

I used a generator to [create the illustration with a single div](https://codepen.io/jh3y/details/eYpBwgN). But Alvaro Montoro [took it a little further](https://dev.to/alvaromontoro/drawing-a-single-element-dollar-bill-with-css-and-javascript-3mj4) and wrote a generator that does it with `box-shadow` instead.

# Preprocessors are super helpful
While they aren’t required, using preprocessors can help keep your code neat and tidy. For example, Pug makes writing HTML faster, especially when it comes to using loops for dealing with a bunch of repeating elements. From there, [we can scope CSS custom properties](https://css-tricks.com/the-power-and-fun-of-scope-with-css-custom-properties/) in a way that we only need to define styles once, then overwrite them where needed.

{% codepen https://codepen.io/jh3y/pen/xJXvjP %}

Here’s another example that demonstrates a DRY structure. The flowers are constructed with the same markup, but each has its own index class that is used to apply scoped CSS properties.

{% codepen https://codepen.io/jh3y/pen/eYpjXvj %}

The first flower has these properties:

```css
.flower--1 {
  --hue: 190;
  --x: 0;
  --y: 0;
  --size: 125;
  --r: 0;
}
```

It’s the first one, so all the others are based off it. Notice how the second flower is off to the right and up slightly. All that takes is assigning different values to the same custom properties:

```css
.flower--2 {
  --hue: 320;
  --x: 140;
  --y: -75;
  --size: 75;
  --r: 40;
}
```

{% twitter 1262856791873945601 %}

# That’s it!
Go forth, use these tips, come up with your own, share them, and share your CSS masterpieces! And hey, if you have your own advice, please share that too! This is definitely the sort of thing that is learned through lots of trial and error — what works for me may look different from what works for you and we can learn from those different approaches