I've always had a great interest in design, illustrations and colour palettes, but having spent the last few years focusing on becoming a better front-end developer, I've been left with little time to practice my creative skills. When I was first introduced to CSS images, I couldn't wait to give it a try. At last I could play around with shapes and colors, and explore and develop my creativity while coding!
But first, what is a pure CSS image?
A pure CSS image is an illustration that has been built with HTML and CSS. It excludes the use of image file imports, or code generated by exporting graphics in illustration software. In other words, it's an image that has been entirely manually coded in a code editor, using HTML and CSS only.
The principle is simple: with HTML, you can create as many divs as you like, which will each represent the basic shape of a component of the final image. With CSS properties such as gradients, transform, border-radius, shadows and so on, you can transform these basic shapes and arrange them into a nice illustration.
Why CSS images?
But what’s even the point of CSS images, you ask? CSS is primarily meant to style web pages after all, and there are more adapted and efficient tools to produce quality and lightweight graphics for web design (think SVG). Indeed, not only does using CSS for illustration have some design limitations, the result can be overly complex, time consuming and face some major cross browser/device issues.
So there is definitely a place and time for CSS images. However, creating CSS Illustrations is a great way to get better at CSS and to be introduced to new tools and concepts, such as animations, preprocessors or more obscure CSS properties. I feel my CSS skills have skyrocketed ever since I started coding CSS images, and I have become much more familiar with concepts I didn't know much about, like the wide variety of CSS selectors, 3D transforms and keyframes animations.
Who is it for?
CSS illustrations are great for:
- Illustrators or designers hoping to use their designing skills to learn, or get more confident with html/css
- Front-end developers hoping to work on their creativity and develop a good eye for design
- Anyone wanting to have a bit of fun while strengthening their CSS skills
- Anyone hoping to connect with a community, inspire and be inspired
- Or anyone up for a good challenge
In this series, we'll learn how to create three CSS illustrations, ranging from simple to complex. We'll learn the basics of CSS animations and how to use them to animate our illustrations. Along the way, we'll find out what concepts, tools and techniques can help speed up our workflow.
Part 1: Learning basics and workflow tips with a CSS Smiley Face
Part 2: Intro to CSS animations with a CSS Polaroid
Part 3: More advanced techniques with a CSS Lighthouse Scene
While this tutorial series doesn't require any advanced knowledge of web development, I'm assuming you are familiar at least familiar with HTML and CSS.
A couple of suggestions before we begin:
- Open a CodePen account, if you don't have one already. It'll take away the pain of having to set up a project, especially since we'll be using CSS preprocessors and templating languages.
- Use either Chrome or Firefox, as other browsers have proven to be buggy with CSS illustrations.
Basics
Okay enough talk, let’s get started with our first CSS image!
Here’s a circle:
<div class="circle"></div>
html,
body {
height: 100%;
width: 100%;
overflow: hidden;
padding: 0;
margin:0;
}
body {
background: #FEEE9D;
}
.circle {
position: absolute;
width: 300px;
height: 300px;
transform: translate(-50%, -50%);
top: 50%;
left: 50%;
border-radius: 50%;
background-color: #FBD671;
}
Nothing impressive so far.
We're going to add a few elements to turn this circle into a smiley face. The circle will be the head, and we can add eyes and a mouth:
<div class="head">
<div class="face">
<div class="mouth"></div>
<div class="eye-group">
<div class="eye eye-left"></div>
<div class="eye eye-right"></div>
</div>
</div>
</div>
html,
body {
height: 100%;
width: 100%;
overflow: hidden;
padding: 0;
margin: 0;
}
body {
background: #FEEE9D;
}
* {
position: absolute;
}
.head {
width: 300px;
height: 300px;
transform: translate(-50%, -50%);
top: 50%;
left: 50%;
border-radius: 50%;
background-color: #FBD671;
}
.face {
width: 150px;
height: 170px;
left: 75px;
top: 75px;
}
.mouth {
width: 100%;
height: 75px;
bottom: 0;
left: 0;
background-color: #20184E;
border-radius: 10px 10px 150px 150px;
border: 5px solid #20184E;
}
.eye-group {
width: 150px;
height: 50px;
top: 10px;
left: 0;
}
.eye {
background-color: #20184E;
width: 30px;
height: 50px;
border-radius: 50%;
}
.eye-left {
left: 15px;
}
.eye-right {
right: 15px;
}
Okay, let’s have a look at what’s going on here.
Main container
We have a main container, in this case the .head
element, that contains every other HTML elements. This is, as you would expect, to center our illustration, and serve as a reference for the positioning of everything else.
Absolute positioning
All elements have the position: absolute
property. This allows, with the help of the top/right/bottom/left properties, to precisely position them relatively to their parent. It's important to note that absolute positioning removes elements from the natural flow of the page. It means that the position of an element with that property will not affect the position of other elements, and will not be affected by their position either. Assigning the position: absolute
property to all elements by default will ensure they will all be independent from each other. More on positioning here.
* {
position:absolute;
}
Nesting
Following this logic, we use nesting to group elements together. Observe the .face
element. It is not an actual component of the image, but used only to group the eyes and the mouth together. Both .mouth
and .eye-group
's position (and size, if we use % instead of px) will refer to .face
. If, for example, we decide we want to reposition the face on the head later, we can do it at once instead of having to tweak the properties of both .mouth
and .eyes
. This is basic CSS logic and won't be new to you if you have experience in UI development. As a rule of thumb, nesting allows a clearer structure because it groups elements that belong together, and make the illustration easier to manipulate.
Stacking
Using absolute positioning allow us to have more control on the stacking order of the elements. Naturally, the stacking order follows the flow of the HTML elements. The first object will be at the bottom of the pile (at the far back), while the last one will be on top (closest to you). With the z-index property, we can change the position of each element in the pile and make them overlap how we want to, provided these elements belong to the same stacking context. The higher the value of z-index, the higher it'll be in the pile.
Stacking contexts are a bit tricky and don't always behave as you would expect them to. You can read more about what triggers the creation of a new stacking context here.
Classes only
We only use CSS classes to target and style our HTML elements as we don't want to be bothered by specificity issues.
Phew, that was a lot already. But these are very important concepts of CSS illustrations, and CSS in general. Once you are comfortable with positioning and stacking, your CSS skills will improve drastically.
Now that we have a basic understanding of how to position elements, let’s jazz up our smiley face a little. I want to add a few details like a tongue, pupils and a shadow.
The shadow is straightforward but adds a nice touch:
.head {
width: 100%;
height: 100%;
background-color: #FBD671;
border-radius: 50%;
box-shadow:inset -10px -10px 0px #EFBB42;
}
The tongue is a simple pink circle, but we only want to show part of it to create the illusion that it's inside the mouth. To do this, we nest .tongue
in .mouth
and apply the overflow: hidden
property to .mouth.
<div class="head">
<div class="face">
<div class="mouth">
<div class="tongue"></div>
</div>
<div class="eye-group">
<div class="eye eye-left"></div>
<div class="eye eye-right"></div>
</div>
</div>
</div>
.mouth {
width: 100%;
height: 75px;
background-color: #20184E;
left: 0;
bottom: 0;
border-radius: 10px 10px 150px 150px;
border: 5px solid #20184E;
overflow:hidden;
}
.tongue {
width: 100px;
height: 80px;
left: 25px;
top: 30px;
background-color: #F15962;
border-radius: 50%;
}
And we can add pupils with a simple oval inside our .eye
elements.
<div class="container">
<div class="head">
<div class="face">
<div class="mouth">
<div class="tongue"></div>
</div>
<div class="eye-group">
<div class="eye eye-left">
<div class="pupil"></div>
</div>
<div class="eye eye-right">
<div class="pupil"></div>
</div>
</div>
</div>
</div>
</div>
.pupil {
width: 10px;
height: 15px;
top: 5px;
background-color: #FBD671;
border-radius: 50%;
}
It's looking much better already.
More advanced concepts
Now that we have built our first CSS image, let’s take a step back and look at how we can improve our code. While these steps aren't necessary at all to build CSS images, I find they greatly improve my workflow, and helped me getting much better at CSS in general.
CSS Preprocessors
If you are familiar with CSS preprocessors you know there’s a better way to do this. I won’t delve into how to set up a preprocessor as this is beyond the scope of this article. Instead I strongly recommend you use Codepen, which has all preprocessors built in. You just have to select the one you want in the CSS settings and you’re all set.
I personally like to use SASS/SCSS, but feel free to use any of the options available.
The best known feature of preprocessors is the ability to use variables. Preprocessor variables work just like javascript ones. You declare them and assign them a value once, then you can use and reuse them throughout your code. This is a great tool as it avoids repetition and gives you a lot more flexibility. Let's implement a few variables in our CSS. A good use for variables is to define colors.
A SCSS variable always starts with the $ sign:
$black: #20184E;
You can name it whatever you like, but it must start with $ and be assigned a valid CSS property.
Then you can use it like so:
background-color: $black;
You can see where this would come handy. If we later decide that we want to change this particular color, we only have to do it in one place and it will be reflected everywhere the variable is used in our code. Also when it comes to colors, it's a lot easier to remember the name of a variable you created than a RGB or HEX value.
I've added my new color variables to the top of the code so we can find them easily. Declaring variables in the global scope (as opposed to declaring them in a selector) will make sure we can use them everywhere.
$black: #20184E;
$head-color: #FBD671;
$background: #FEEE9D;
$tongue-color: #F15962;
Now we can change our css properties so they point to the variables instead of the hard coded HEX values.
Eg:
.mouth {
width: 100%;
height: 75px;
bottom: 0;
background-color: $black;
border: 5px solid $black;
border-radius: 10px 10px 150px 150px;
overflow: hidden;
}
CSS now has native variables that pretty much make the preprocessor ones unnecessary. However, preprocessors offer a lot more than variables and are still extremely useful in many other ways.
SCSS also comes with many helper functions, including color functions. These functions are quite powerful as they allow you to manipulate colors very easily.
Let's have a look at the inset shadow applied to the head. Right now we are using a hard coded HEX value, which I've had to manually look up. We are going to use the darken function to generate this value instead:
.head {
border-radius: 50%;
width: 100%;
height: 100%;
background-color: $head-color;
box-shadow: inset -10px -10px 0px darken($head-color, 20%);
}
Basically, the function takes $head-color
as a parameter then and increases the amount of darkness by 20%.
I love using color functions because not only do they eliminate the need to resort to some design software to generate colors, they also help with creating a more harmonious color palette for your illustration.
Another great feature of SASS is nesting. Nesting allows you to nest selectors within parent selectors in order to create shortcuts. It creates a better hierarchy and readability of your CSS, and avoids repeating selectors over and over.
For example
.pink {
background-color: pink;
.purple {
background-color: purple;
}
}
will be compiled to:
.pink {
background-color: pink;
}
.pink .purple {
background-color: purple;
}
SASS also offers the & selector, which basically refers to the parent selector. So
.pink {
background-color: pink;
&.purple{
background-color: purple;
}
}
will be compiled to:
.pink {
background-color: pink;
}
.pink.purple {
background-color: purple;
}
Using the & selector will not target child elements that have a class .purple
like in the example above. It will target the parent element that has the classes .pink
AND .purple
.
Beware of too much nesting as it can make your code overly complex and less readable, which would be the opposite of what we're aiming for. As a rule, I tend to go for a maximum of 3 levels of nesting.
With this in mind, let's implement nesting in our code. For example:
.eye-group {
top: 10px;
left: 0;
width: 150px;
height: 50px;
}
.eye {
background-color: $black;
width: 30px;
height: 50px;
border-radius: 100%;
}
.eye-left {
left: 15px;
}
.eye-right {
right: 15px;
}
becomes:
.eye-group {
top:10px;
left:0;
width: 150px;
height: 50px;
.eye {
background-color: $black;
width: 30px;
height: 50px;
border-radius: 100%;
&.eye-left {
left:15px;
}
&.eye-right {
right:15px;
}
}
}
Preprocessors are a great asset in building CSS images and will drastically speed up your workflow. They also offer more advanced options like function, loops, mixins etc. These will come in handy in more intricate illustrations, as we'll see in the last part of this series.
:before and :after
Until now, we’ve created a div for each shape in our smiley face. While this is totally fine, there is a way to reduce the number of HTML elements. This is where the pseudo-selectors :before
and :after
come in.
These pseudo-selectors come automatically with any HTML element you create. So for any element in your HTML, you’ll be given two extra containers within which to add content. Although they are not initially present in the DOM, you can target them with the content property, like so:
.some-class:before, .some-class:after {
content: "";
}
This property can be used to insert content such as text or images. In CSS illustrations, we don't want any of that, but we still want to use the selector, which we can do by using empty quotes. While it might look like a unnecessary step, this is the property that will ensure these selectors are available, so make sure it's there. A good way to not forget it, as well as avoiding repetition, is to assign it to all :after
and :before
elements by default:
*:before, *:after {
position: absolute;
content: '';
}
As you would expect, :before
and :after
depend on their parent for size and positioning.
Let's look at how we can use these in our code. Consider the .mouth
element. It has a child: the .tongue
element. We're going to replace the .tongue
selector with an :after
pseudo-selector:
.mouth {
width: 100%;
height: 75px;
bottom: 0;
background-color: $black;
border: 5px solid $black;
border-radius: 10px 10px 150px 150px;
overflow: hidden;
&:after {
background-color: $tongue-color;
width: 100px;
height: 80px;
border-radius: 50%;
left:25px;
top:30px;
}
}
The same logic can be applied to replace the .pupil
elements:
.eye-group {
top: 10px;
width: 150px;
height: 50px;
.eye {
background-color: $black;
width: 30px;
height: 50px;
border-radius: 100%;
border: 5px solid $black;
&:after {
width: 10px;
height: 15px;
top: 5px;
background-color: #FBD671;
border-radius: 50%;
}
&.eye-left {
left: 15px;
}
&.eye-right {
right: 15px;
}
}
}
Now we can get rid of the .tongue
and .pupil
divs in the html:
<div class="head">
<div class="face">
<div class="mouth"></div>
<div class="eye-group">
<div class="eye eye-left"></div>
<div class="eye eye-right"></div>
</div>
</div>
</div>
While this isn’t absolutely necessary, I find this practice helps to keep my code cleaner, and avoids overcrowding my HTML. Again, only use pseudo-selector when it makes sense to do so. (eg: to create an element visually related to its parent container).
HTML templating language
Another step we can take to improve our code is to use an HTML templating language. I like to use Pug, which is also built in to the Codepen editor.
Here’s how it works: Pug uses tag names to represent a full HTML element. For example, <div></div>
will simply translate to div
in Pug. It uses indentation to represent nesting, recreating the tree structure of HTML.
In CSS images, it is recommended to use div elements only, and assign them a class to be able to easily target them. The way to represent a div with a class in Pug is as follows:
Normal html:
<div class="container"></div>
```
Pug:
```html
div.container
```
Or, because `div` is the default tag name, it can be omitted:
```html
.container
```
I find this syntax to be much cleaner, and it allows me to focus on the class. Since I already know that all my elements are divs, I don't need any useless syntax polluting my code.
Let's convert our HTML to Pug:
```html
.head
.face
.mouth
.eye-group
.eye.eye-left
.eye.eye-right
```
Much cleaner.
Similarly to CSS preprocessors, there are many things you can do with Pug as it is powered by JavaScript: mixins, includes etc. When it comes to CSS images, one of the most powerful features is loops. We'll see a bit later how to use them.
Here's the final project in CodePen. You can see that none of our latest changes have affected the illustration at all.
Right, we’ve learned a lot already! How to create various shapes and assemble them into a cohesive illustration, how to use hacks to emulate more complex shapes, how to use preprocessors, templating languages and pseudo-selectors to improve our workflow and clean up our code. In the second part of this series, we'll practice these new concepts and build a CSS Polaroid. Then, we'll learn how to animate it.
[Part 2: Intro to CSS animations with a CSS Polaroid](https://dev.to/agathacco/how-to-create-pure-css-illustrations-and-animate-them---part-2-1ao4)
Top comments (48)
Splendid article! Very straight forward and neat. Thank you for this!
Just one question
Is this how you make element to be fixed in the position? I am always confused with absolute, fixed and relative
Thank you, glad you liked it!
Yeah CSS positioning is weird. Here's roughly how it works:
By default, all elements have a static position, which means the normal flow of the page controls their positioning. It basically means that all elements are interdependent and will make room for one other.
With position: relative, the normal flow of the page still decides where the element is positioned, but gives you the option to nudge it with the top/right/bottom/left properties. So if you set for example, top:100px, it will move the element from its original position
Absolute positioning removes the element from the flow of the page and basically gives you full control on its position on the document. BUT an element that has absolute positioning will always refer to another element for its position. By default, its the document, so if you start using the top/right/bottom/left properties, it will position your element relatively to the document.
In order to have a child respond to the position of its parent, you need to set position absolute (relative works too) to the parent as well. If the direct parent doesnt have a position absolute (or relative), the element will go up the html chain and check its grand parent, great grand parent etc, until it finds an ancestor that has the position absolute value. then it will refer to it for its own positioning.
Position fixed also remove the element from the page and lets you position it with the top/right/bottom/left properties, but it will always do so relatively to the document. That's not very useful with CSS images, but is a great way to keep a navbar at the top of the document for example, even when scrolling occurs.
I like to use the code snippet above in CSS images, because it makes sure that an element will always be positioned relatively to its parent.
I hope this makes some sense? Let me know!
Thank you for the detailed explanation, I have a clearer idea of how this actually works,
In a sense, relative position will still be interdependent on the rest of the elements in the flow but allow me to move it around with the properties.
Absolute will always find a parent position to take reference from before applying the properties and fixed position does not care about anyone and will immediately apply the properties by taking reference to the document!
I shall go and experiment with this to firm up my foundation, and after I managed to draw something out, I shall show it to you! :D
That's it!
Have fun experimenting, I'm looking forward to seeing your first piece of css art!
*
selects everything and make their position absolute because, we need absolute positioning in order to set the elements at desired place.You can learn more about CSS Positioning on MDN CSS Docs.
Sweet! okay let me google on that as well! Thank you very much! :)
This is an awesome tutorial, thanks!
My friends and I made an entire book of ABCs with CSS3 Animals for babies sometime back: bubblin.io/book/abcd-animal-book-b...
Thank you!
I love the baby book!! Great work!
I was wondering if you'd be interested to do an OSS book of just fruits with CSS3 in your spare time? Like whenever!
Yeah that sounds like a fun project!
Cool… I'll set up a project on Github shortly. Excited!
Updates: Here we go!: github.com/marvindanig/fruits-and-...
Let's connect over email perhaps… I'm at marvin*marvindanig*com!
Wow, really great walk-through!
I had tried a few earlier.
But I'm very much inspired by your post & want to try out one daily.
Just created Chrome logo art.
Here: codepen.io/rajatkantinandi/full/GX...
Although it's not perfect but want reach the level of perfection that you showcased eventually.
Thanks for the great post.
This is an awesome walkthrough!
I'm really interested in HOW you draw something like that.
At least with drawing software, there's a stylus or mouse that's sort of like a pencil.
But with numbers in CSS for position, size, etc., are you just trial-and-erroring things live until they look right? Nudging the positions, radii, aspect ratios around?
And how do you think up "oh, I need a red, clipped circle for the tongue, but elevated a bit from the mouth's bottom edge"?
CSS comes easily to me but for that stuff I'd appreciate some hints on how you think!
Many thanks.
I've been practicing CSS images for a while now so it comes more easily, but I didn't know where to start either when I first tried.
You can start by replicating an existing illustrations. Go on dribbble.com for example, and pick something simple to begin with. It'll let you focus on the actual process without having to worry about the creative aspect.
And yes, often play around a lot and try out different things before I'm happy with the result. Most of the time, I only have a rough idea of what I'm going to build, and my project takes shapes as I give it more thoughts. I also look at a lot of illustrations, drawings and photography, which keeps me inspired.
Hope this helps :)
Awesome, really great stuff!
Be sure to check for browser compatibility though 😶
Oh yes! There are definitely some bugs in safari that I'm still working on, totally forgot to mention it. Safari is a real pain with css illustrations. Thanks for bringing it up!
The beautiful things frontend-devs could create if people would actually update their browsers 😃
You haven't suffered until you try to implement a colored box-shadow in IE8 😭
Haha, true. I'm glad I don't have to worry about IE anymore in my current job. And certainly not in CSS images, they're only meant to be FUN! :D
Straight into my reading list
Thank you for this fun and detailed article! Going on with Part 2 now...
Just one thing. - What you do with this...
...is to give every element on the page a :before and :after pseudo element. If you check the markup in the dev tools this doesn't look very nice. ;)
Maybe it would be better to create a mixin like this...
...and then call it in your pseudo element like so:
Very good point!
I think if you have SEO or performance in mind, then going for a lighter markup is a great idea. I admit than in the case of CSS images, I am more interested in avoiding repetition. But for real life projects, I would definitely think of using a @mixin.
Thanks for your input.
This is so inspiring, Agathe. I used to wonder what's the point of all those CSS illustrations on Codepen. Visually dazzling, yes, but I didn't get the point. Then recently I started playing with making CSS icons, and it dawned on me: It doesn't need to have a point. I have discovered aspects of CSS that I never knew, or never got the opportunity to practice. I'm excited by what cool drawings can be made with pure codes, and that's enough.