Skip to main content
Posts

28-Pixel Icons

A collection of icons formed by animating 28 pixels around an 11x11 grid

Departure Mono

The nice pixel typeface you may notice around here is Departure Mono, designed by Helena Zhang. I first saw it used on Making Software by Dan Hollick. The font has lots of non-text characters built-in, especially some great ones for box drawings and ASCII art.

████████▒▒▒▒▒░░░░░░░░░░
Just look at this progress bar!

I'm using Departure sparingly, and not every page here needs it, but I wanted to thread the pixel motif into a few other places. At first glance, it seemed like we Departure would let me replace my Tabler icons with symbols.

❰ ❮ ❬ < ‹ › > ❭ ❯ ❱
↖ ↗ ↘ ↙ ← ↑ ↓ →
↕ ↰ ↱ ↲ ↳ ↴
× ★ ☆ ♥ ♦ ✦ ✧ √
A few Departure Mono symbols

One issue: all the chevrons are horizontal. I don't want to rotate a single character every time a need a downward-facing one. Time to make some custom icons.

Pixel perfect pixel art

Departure is drawn at an albeit irregular size of 11 pixels, but it scales relatively well. Since characters are pixel-perfect at font-size multiples of 11px, you can use a half-step at 16.5px to make each "pixel" land on a pixel edge on at least one side. This equates to an icon with a 1.5px stroke (like Lucide) and remains decently crisp.

11pxicon beside text
16.5pxicon beside text
22pxicon beside text
A few Departure Mono text sizes

That means we should be able use the trio of 11 / 16.5 / 22px in place of common SVG sizes like 12, 16, 20, and 24px.

For monospacing, the em box of a given character is 8×14, with ascenders or descenders exceeding the 11px bounding box. This gives us some wiggle room for a larger 14px set if we need it.

Direction

I landed on two sets of icons with different constraints. I'll explain why in a moment, but for now, let's take a look at each.

  1. First are static symbolic icons for UI needs, filling in any gaps in Departure Mono. These can use any number of pixels, typically 16 or fewer, and can have open terminals or shapes.
  2. Next are animated pictorial icons that always use 28 pixels. These all have closed shapes for consistency and can animate from one to another.
Static icons

ChevronDown

Chevrons

Loader

Shuffle

Redo

Finder

Animated

click each to morph

Password toggle

Twenty-eight pixels

The 28-pixel constraint started with the light/dark mode toggle. I wanted to do something I'd seen before: a sun icon rotates into a moon icon and call it a day. With a regular SVG, this works just fine.

We can rig this up with Tabler and Motion:

Tabler + Motion

The original idea works naturally with path-based SVGs.

But how might one accomplish this with pixels? They don't rotate. Elements painted with pixels can appearto rotate, but really it's just other pixels along its path lighting up. Pixels are discrete and don't maintain the kind of illusory continuity you'd get from a rotated path, so having a little rectangle spin into place breaks the metaphor.

The two states needed to use the same number of pixels, and the pixels ought to reshuffle rather than rotate.

Nor do pixels slide diagonally, but we're suspending disbelief on that count. I could have each pixel animate using steps or SMIL, but then the animation would be way too slow and jerky.

So, why 28 pixels? That's just how many pixels it took for the sun to look right, and it's the first one I made. That's all. This could work with any number of pixels, and you don't even need a constant number; unused pixels could combine or dissolve. Speaking of which: the moon only needed 24, so it earned a little star. Good job, buddy.

In reality, each "pixel" is a rectelement with a width and height of 1px. Motion animates each's x and y properties to move it to its new position.

Tabler + Motion

Pixels

Rotating paths vs. rearranging pixels

It's self-evident that the path-based approach is smoother, but that's not the point:they shouldn't look polished, the icons are intentionally lo-res. They're inherently rough and imperfect. We can add a hint of gracefulness to their rearranging, but that's more a byproduct of easing.

Going overboard

Then I saw this postfrom Benji Taylor about morphing icons with Claude, in which he's animating between 21 different SVGs made from 3 lines each. That got me wondering how many more icons I could squeeze out of 28 pixels.

A lot, it turns out.

The current 28-rect morphable set.

(Some of these are duplicates or variants of others, mostly different attempts at getting the shapes right. They're still kicking around to test which animate well.)

Codex to the rescue

Instead of copy-pasting from Figma and manually converting to JSX, I started off having Codex use the Figma MCP to grab frames and populate the SVGs as individual components.

It got clever after a few passes and wrote a tiny createPixelIcon helper function, which instead generates icons from the X/Y coordinates of each 1x1 layer per frame. Icons then become a string it parses from Figma, and the helper renders rect elements inside a shared SVG wrapper.

Inspect coordinates
PixelPenToolIcon.tsx

Morph visualizer(s)

With the second batch of icons created, the next step was to see how they animate. Inspired by Benji's post, Codex and I created a visualizer to experiment further. You can select a sequence of icons to see how pixels match up between states, and test different animations, speeds, etc. Give it a spin below.

v1: Sequence builder

Build a sequence, jump between steps, and play transitions from the preview or footer.

Preview
EyeEye Closed
Customize
ms
x

v2: Click-to-morph

Start empty, click an icon to preview it, then click another to morph. Click the active icon again to clear.

Select an icon
ms
x