Responsive Art

Over the holiday weekend I started playing with an idea that’s been bouncing around my head for a little while: could you use CSS Grid to draw “pixels” on a resizable, responsive canvas? (“Canvas” as in 🎨, not as in <canvas> ☺️)

Sure! Why not? After all, that’s precisely what CSS Grid is good at (especially when auto-fill and minmax are in the mix): laying out box-shaped elements on uniform tracks. But I ended up making something I think is far more beautiful and engaging than I imagined, that embraces the nature of CSS; something you might call “responsive art.”

A responsive canvas

Here’s an example of my initial idea — a resizable grid that contains 21 grid elements (“pixels,” for lack of a better term), all with the same dark background color:

If you’re using a desktop browser go ahead and grab the lower right corner and resize this canvas to be narrower or wider

By using grid-template-columns: repeat(auto-fill, minmax(...)), resizing the canvas allows the “pixels” to grow and shrink accordingly. Once the grid can no longer accommodate the current number of elements in a row—or, conversely, if the row width allows for another element to squeeze in while still maintaining the minimum element width—the grid elements reflow into a new layout.

Here’s a snippet of what the relevant CSS looks like:

.canvas {
	--pixel-min: clamp( 30px, 6vmax, 60px );

	display: grid;
	grid-template-columns: repeat(
		auto-fill,
		minmax( var( --pixel-min ), 1fr )
	);
	overflow-x: hidden;
	resize: horizontal;
}

.canvas > * {
	background-color: #000;
	padding-bottom: 100%;
}

It’s a little tricky to see what exactly is happening when all the pixels share the same background color, so let’s alternate between light and dark to get a better sense of the layout.

To achieve this we could figure out a way to style each pixel element individually—maybe by creating a light or dark class and adding it to the appropriate elements, or we could use an :nth-child pseudo-class to select every other element with a single CSS rule:

.canvas > *:nth-child(2n) {
	--color: #000;
}
The same grid figure as above, but with alternating light and dark backgrounds

Better! Simple and straightforward to implement, and we can see that the canvas’s elements are reflowing the way we want. (Also, you might notice there’s some fun stuff happening at different widths, but let’s come back to that in just a bit... 😉)

For some extra flavor, let’s toss a proper palette into the mix using successively greater :nth-child values:

.canvas > * {
	--color: #a6035d;
}
.canvas > *:nth-child(2n) {
	--color: #f27405;
}
.canvas > *:nth-child(3n) {
	--color: #d91e41;
}
A brightly colored canvas with pixels in magenta, orange, and red

Now we’re getting somewhere! But even though it’s more colorful it still isn’t particularly artful. Adding a bunch of extra pixel elements might get us a little closer to the kind of engaging design I was originally hoping to achieve, but my gut tells me it would just end up a muddy mess of colors.

The :nth-child selector approach got me thinking, though: would extending that to other properties beyond just background-color produce anything interesting, especially at the intersections of multiple rules?

Embracing the cascade

With our colors already defined, let’s see what happens as we start adding additional rules to give shape to our pixels.

First, let’s add a rounded corner to the top left corner of every element on the canvas:

.canvas > * {
	border-top-left-radius: 100%;
}
The same brightly colored canvas, with every pixel rounded at the top left corner

Kind of neat, but not much more interesting than the previous iteration of colored squares. Let’s round the top right corner on every other pixel and see what happens:

.canvas > *:nth-child(2n) {
	border-radius: 0;
	border-top-right-radius: 100%;
}
The same brightly colored canvas, with every second pixel rounded at the top right corner

Things are getting a little more interesting! A row of scallops in varying colors is starting to feel more art-y, and depending on the width of the canvas you either get uniform distribution or a nice offset between rows.

(Adding border-radius: 0 to subsequent :nth-child rules to reset and prevent ever-rounder pixels is an aesthetic choice I made after playing around a bit while building this example, but you could absolutely let every previous rule continue to cascade for weirder effects.)

You can probably guess where we’re headed next ☺️ Let’s round the bottom left corner of every third pixel:

.canvas > *:nth-child(3n) {
	border-radius: 0;
	border-bottom-left-radius: 100%;
}
The same brightly colored canvas, with every third pixel rounded at the bottom left corner

Wowowow! By adding just one more rule, we’ve jumped pretty far outside the strict visual boundaries of our grid to form new shapes woven together by multiple pixels.

Another exciting change with the new rule is that the design starts to change pretty radically at different widths. Try resizing this canvas down to three columns and then up to eleven. They’re obviously all related, but—to me, at least—they evoke very different feelings.

Okay, let’s round out our set by adding a fourth and final rule:

.canvas > *:nth-child(4n) {
	border-radius: 0;
	border-bottom-right-radius: 100%;
}
The same brightly colored canvas, with every fourth pixel rounded at the bottom right corner

Voilà! With just three rules to set color and four to determine overall shape, we have a beautiful little piece of geometric artwork with nine variations that reveal themselves when you resize the canvas.

(My absolute favorite variation pops out when you shrink this final canvas to five columns 🥰)

Beauty in the grain

One thing I really love about these responsive pieces is the dramatic change in appearance that comes from playing with their dimensions. A single nudge can transform a series of braided, almost organic strands that crisscross the canvas into a rigid repeating pattern with little variation from row to row.

And even though we can fine-tune rules here and there, we’re really letting go of pixel precision and going with the grain of CSS. Moreover, I can’t say I would have thought to put those elements with those shapes in those places using those color distributions; these little canvases truly fall somewhere between handcrafted and generative art.

If you’re interested in playing with more of these, I’ve started to collect them over on my personal site. Here are two I’ve published so far:

(And if you start playing around with this kind of approach or have been already, please let me know! I’d absolutely love to see what kind of cool and creative things other people are making with responsive art.)

Influences

You know when you have fragments and snippets of ideas people have said or written just marinating in your head, then a few disparate thoughts bubble up together at just the right moment? 😙👌

Miriam Suzanne said this about CSS art on the terrific Word Wrap podcast:

People that are doing just absolutely weird shit with CSS is so cool. [...] I would love to see more CSS art that plays with resilience. How’s it going to fall apart?

Andy Bell echoed Miriam’s idea about resiliency when describing the responsive grid that makes the resizable canvas work:

Embracing the flexible nature of the web gives us resilient user interfaces. Instead of using prescribed, specific sizes, we give elements sensible boundaries and let them auto flow or resize where possible. A good way to think of this is that we provide browsers with some rules and regulations, then let them decide what’s best—using them as a guide.

Finally, Amanda Cifaldi built and maintains a beautiful art bot on Twitter called Tiny Spires, whose geometric designs float down my timeline several times a day.

Designing for a grid-based canvas whose dimensions are controlled by the viewer is, in retrospect, very much influenced by all of these.