What is drawing with code?
All the knick-knacks on my homepage, from the teacup to the radio, are made of plain old <div>s. No images, no SVGs, just CSS. CSS art lies at the intersection of vector illustration and frontend development. Instead of drawing with a pen tool, you style empty HTML elements with properties like height, border-radius, box-shadow and background-color until they turn into shapes, and eventually pictures. With countless properties to play with, you can create surprisingly intricate illustrations without ever opening Illustrator.
It’s all about the <div>
The <div> element is the building block of every pure CSS composition. A div is simply an empty container, often used to house other elements and create structure on a web page. On its own, a div has no color, border or shape, so it’s a blank slate. Here’s a simple example. Let’s break down the HTML.
<!-- Circle Example -->
<div class="circle">
<div class="circle__outer">
<div class="circle__inner"></div>
</div>
</div>Parent <div>
Every CSS composition starts with a parent div. Think of it as your canvas. In the example above, <div class="circle"> acts as an invisible box that encapsulates all other elements.
Child <div>
Styled child divs, which are nested within parent elements, give your composition structure and substance. Nesting elements is useful because you can position a child div relative to its parent. As your CSS skills improve, you’ll be able to create elaborate pieces with a single div. For now, break your vision down into simple parts and create a child div for every shape.
Custom classes
Each div needs a custom class. Your stylesheet uses these references to differentiate between elements. Be concise and descriptive for your styling sanity. In the snippet above, if both circles shared the same class, any style you gave one would apply to both. Naming them circle__outer and circle__inner lets you color and size each one separately. Remember, assigning a class doesn’t do anything by default. Styles need to be applied to give a div character.
A little tip. Name each part after the thing it belongs to. On my homepage, the cake stand’s top is dessert__stand-top and each squiggle of tea steam is steam__squiggle. The name before the double underscore tells me where a piece lives, so I never lose track of it. This convention is called BEM (opens in a new tab).
CSS, a little recap
CSS is short for Cascading Style Sheets. It’s how you give HTML elements their looks, written as a series of rules, usually in a separate .css file. Each rule has two parts, a selector, which points to the element you want to style, and a declaration block inside the curly braces, which says how it should look.
.circle__outer {
width: 180px;
height: 180px;
border-radius: 50%;
background-color: #67CAF8;
}Each line in the block is a declaration, made of a property and a value. In width: 180px;, width is the property (what you’re changing) and 180px is the value (what you’re changing it to).
The selector above, .circle__outer, matches the div in our HTML with class="circle__outer". The period at the start tells CSS to look for a class with that name. You only write the period in your CSS, never in the HTML.
An element can also have more than one class. On my homepage, each pencil in the cup has two, class="pens__pencil pens__pencil--2". The first gives every pencil its shared shape, and the second gives this one its own color. You might also see ID selectors, which start with a #, but an ID can only be used once per page, so stick with classes for CSS art.
Beginner’s CSS toolkit
You can make a lot with just the basics. Start small and add new properties to your toolkit as you go. Reach for a solid background-color before a linear-gradient, and get comfortable with border-radius before carving custom shapes with clip-path. Get the flat version of your drawing working first, then add depth with shadows and highlights. Here are the properties I use most to get started.
height, width
These set your element’s size. An empty div stretches the full width of its parent but has no height, so it’s invisible until you give it one. Use px for exact sizes, like the 180px circle above, or % to size an element relative to its parent, which keeps your drawing in proportion when you scale it.
background-color
This fills your element with color. You can write colors as hex codes (#67CAF8) or rgb() values. I keep my palette in CSS variables, like var(--sky-bold), so I can recolor a whole drawing by changing one line. Your browser’s dev tools also have a handy color picker built in.
border-radius
This rounds your element’s corners. One value rounds all four, and border-radius: 50% turns a square into a circle, like our circle__outer. You can also give up to four values to round each corner separately, starting at the top left and going clockwise. Mixing values like this is how you get ovals, leaves and petals.
position: relative / absolute
This is how you place a piece exactly where it belongs. Give the parent position: relative and the piece position: absolute, then use top, left, right and bottom to place the piece relative to its parent’s edges. The teacup we’ll build below works this way. The cup is relative, so its handle and saucer can sit exactly where I put them.
transform: translateX / translateY
transform moves, rotates and scales elements. translateX() slides an element left or right, and translateY() slides it up or down, without pushing anything else around. That makes it perfect for fine-tuning a shape’s spot. Start with small px values and nudge until it looks right.
z-index
Once shapes start overlapping, z-index decides which one sits on top. Higher numbers come forward, and a negative number tucks a piece behind its parent. My teacup’s handle has z-index: -1 so the cup hides its inner edge. It only works on positioned elements, so pair it with position.
::before / ::after
Every element comes with two extra shapes for free, its ::before and ::after pseudo-elements. Give them content: "", a size and a color, and they behave like extra divs you never had to write. In the teacup we’ll build below, ::before draws the handle and ::after draws the saucer.
Honestly, pseudo-elements took me a while to wrap my head around. Don’t worry if this doesn’t click yet. We’ll use both of them when we build a teacup below.
box-shadow
Shadows add depth, but box-shadow is also a sneaky drawing tool. You can stack as many as you like, separated by commas, and a shadow with no blur draws a crisp copy of your shape. The rainbow in the playground below is one div with seven stacked shadows!
Let’s build a teacup
Time to put the toolkit to work. We’ll draw a little purple teacup in three steps, and the whole thing is just one div.
<div class="teacup"></div>Step 1 · The cup
Give the div a size, a dark outline and a color. Rounding only the bottom two corners with border-radius turns a plain box into a cup. I keep the colors in variables at the top so the whole teacup is easy to recolor later.
.teacup {
--cup: #B8B5FF;
--cup-dark: #8B87F5;
--saucer: #D7BAFF;
--outline: #8884f4ff;
position: relative;
width: 110px;
height: 100px;
border: 3px solid var(--outline);
border-radius: 0 0 40px 40px;
background: var(--cup);
}Step 2 · The handle
The handle is the cup’s ::before. A thick border with border-radius: 50% makes a ring, and position: absolute places it on the right side. z-index: -1 tucks it behind the cup so only the outer half shows. The two box-shadows draw its outline, one outside the ring and one inside.
.teacup::before {
content: "";
position: absolute;
top: 12px;
left: 85px;
z-index: -1;
width: 40px;
height: 40px;
border: 10px solid var(--cup);
border-radius: 50%;
box-shadow:
0 0 0 3px var(--outline),
inset 0 0 0 3px var(--outline);
}Step 3 · The saucer
The saucer is the cup’s ::after, a long, flat, rounded bar pinned to the bottom. A negative left value lets it stick out past both sides of the cup.
.teacup::after {
content: "";
position: absolute;
bottom: -16px;
left: -26px;
width: 164px;
height: 10px;
border: 3px solid var(--outline);
border-radius: 12px;
background: var(--saucer);
}That’s it! One div and two pseudo-elements. Try copying the code into the playground below or a new CodePen and giving it your own colors.
Tips from trial and error
Get ready for some serious trial and error. Drawing with code takes a lot of patience. I’ve lost entire evenings to details smaller than my thumbnail... Stick with it! These habits made it hurt much less.
Sketch it first
Before writing any code, draw your idea on paper and break it into simple shapes, like a circle here, a rectangle there and a half-circle on top. Each shape becomes a div. It saves a lot of guesswork once you start coding, and you’ll know exactly which piece to build next.
Outline everything
I’d constantly lose track of where a shape actually was. Poof! Now I add * { outline: 1px solid red; } while I build, and every shape shows up. Use outline instead of border, because an outline doesn’t take up space and won’t shift anything around.
Make dev tools your best friend
Right-click your drawing in the browser, choose Inspect, and edit values right in the Styles panel. You’ll see shapes move as you type. Click a value like 180px and press the arrow keys to nudge it by 1, or hold Shift to nudge by 10. Once it looks right, copy the value back into your CSS.
Keep your colors in variables
I change my mind about colors constantly, so I name each one once, like --handle: #F472B6;, and use var(--handle) everywhere it shows up. Swapping in a new palette means editing a few lines in one spot instead of hunting through my whole stylesheet. It’s how I tried a dozen color combos on my homepage knick-knacks without losing my mind.
Watch out for sneaky borders
By default, a border adds to an element’s size, so a 180px circle with a 3px border is really 186px wide. Add box-sizing: border-box and the border stays inside the size you set.
Try it out
Your turn! Change any value in the CSS below and watch the rainbow update as you type.
Keep going
CSS art started as a little side project, and it ended up teaching me more about CSS than anything else I’ve built. Positioning, pseudo-elements, gradients and animation all clicked once I had something I actually wanted to make. It’s made me a better frontend engineer, and it’s still the most fun I have with code.
I hope you enjoy the challenge of bringing your designs to life with HTML and CSS. Look out for more CSS art content and share your creations with the frontend community on CodePen. Happy coding!
Where to go next
- CSS Shapes, my step-by-step guide to twelve basic shapes, from circles to stars.
- CodePen (opens in a new tab), for more CSS art and little experiments, with the code right there to poke at.
- CSS Battle (opens in a new tab), daily puzzles where you recreate a target image with CSS.
- MDN’s CSS reference (opens in a new tab), for looking up any property when you get stuck.