All posts

Design

I deleted the legend

Every algorithm visualizer ships a color key — yellow is comparing, red is swapping, green is sorted. I shipped one too, for longer than I'd like to admit, until I stopped naming colors by how they look and named them by what they mean. Then the key explained nothing the picture didn't, so I took it off.

April 25, 20266 min read

Open almost any algorithm visualizer and somewhere in a corner there's a little box: a yellow square next to the word comparing, a red one next to swapping, a green one next to sorted. A legend. I shipped that box for longer than I'd like to admit without once asking what it was doing there.

A legend is a confession. It's the picture admitting its colors don't mean anything on their own — that you have to be told, in words, off to the side, that yellow is the thing being compared. The image needs a caption because the image didn't communicate. This post is about deleting the box, and about how deleting it was the easy part. The work was the year of getting the colors to a place where the box had nothing left to say.

I'd built the answer twice and not noticed

The first version of this project ran on Motion Canvas, and it had a central color file — one colors object, every hue defined once, a stern comment up top forbidding hardcoded hex anywhere else. Good instinct. But the colors were named for what they looked like: red, green, blue, yellowSoft. So when the bubble-sort scene wanted to show a comparison it reached for colors.yellow and decided, right there in the scene, that yellow meant "comparing." The next scene was free to decide yellow meant something else. The meaning lived in my head and got re-entered by hand every time. And because nothing on screen said what yellow meant, the scene drew a legend to tell you. Those two facts are the same fact.

Around then I spent a long weekend building a throwaway animation engine from scratch, and — this is the part that took me a while to appreciate — the throwaway got the other half right. Its colors were named DEFAULT, SELECT, SWAP, SORTED. By state, not by hue. A bar didn't turn red, it turned swapping, and the actual color was a detail you could change in one place without touching a single scene. That was the part I kept needing. But it lived inline in one demo file, for one algorithm, and died with the weekend.

So I had the central palette in one repo and the state names in the other, and for months I didn't see that the two belonged together.

The fix was a rename

When it finally landed it was a rename, not a redesign. Not red → some better red. red → comparing. green → sorted. The palette stopped being a list of paints and became a list of states: idle, comparing, swapping, sorted, matched, eliminated, empty. Central, like the Motion Canvas repo. By-state, like the throwaway engine. And — the new part — shared across every lesson, so the state names became a vocabulary the whole product speaks instead of a convention each scene reinvents.

The day I did that, I deleted the legend. Both files, about a hundred and ten lines of color-key component, gone, and nothing on screen got harder to read. A bar filled with the comparing token doesn't need a legend pinned in the corner spelling out what it is — the status line names the comparison as it happens, and the amber is right there on the two bars doing it. You learn the color once, in context, and from then on it carries the meaning; the label moved off the screen and into the name of the thing.

The payoff is in the lesson you haven't opened yet

The value showed up in the lesson you haven't opened yet.

sorted and matched are the same green. Literally the same hex. One is the name a sorting lesson gives a bar that's reached its final position; the other is the name a search lesson gives the element it just found. They're separate names so each lesson's status line reads right, but they resolve to one color, because to a learner they mean one thing: this is the answer. Green is success.

A learner who watches bubble sort and absorbs, without being told, that green means "done, this one's settled," opens binary search a week later and reads the green hit for free. Then they open a neural-net lesson and the output that lights green reads as the answer before they've parsed anything else around it. That transfer is the entire point, and it only works if green means exactly one thing in all three places — which is precisely what a by-hue, per-scene palette can't promise and a by-state, shared one can. A legend saves you a guess on one screen. A shared vocabulary saves you the guess on every screen you haven't opened yet.

It is not free, on purpose

A shared vocabulary is only worth anything if it stays consistent, so the file that holds it is governed like one. No hardcoded hex in an animation, ever. No per-lesson palette constant that quietly forks a second green. Adding a token is a small ceremony: it has to earn a semantic name — what does this mean to a learner, in a word, independent of the one lesson that wants it? — it has to read as visibly distinct from its neighbors (the muted teal for a cache hit must not be mistaken for the green for an answer), and I have to see it on a real frame at the densest input before I trust it. Every token is a word added to a language every future lesson has to keep speaking. You don't add words to a language casually.

The sharp edges are the collisions. There's a green for "the answer" and a green for a positive weight in a neural net, and in a backprop lesson they land in the same frame; what ended up working there is that the structural green recedes so the only loud green means answer. There's an amber for "currently inspecting" and an orange for "gradient flowing backward," and they can't both shout at once. These aren't hypotheticals. They're comments sitting in the color file, each written the day a lesson made the collision real.

Two axes, kept apart

There's a companion piece on this site about keeping most of the frame quiet — about how only one thing should be loud and the rest should recede. As explained there, color is meaning (which state an element is in) and weight is attention (how much it matters). They must stay independent. If you fade an old cell by turning it gray, you collide its meaning with the existing "empty" and "eliminated" gray states. So you fade with opacity and never with hue: an old cell keeps its color, just drawn at 0.4. Meaning holds; only the volume drops. Deleting the legend and quieting the frame are the same project from two directions — one makes the colors a language, the other makes sure only one sentence is spoken loudly at a time.

The legend was never the problem. It was the symptom. In my own lessons, when the picture needed a box in the corner translating its colors, the colors were not a language yet — they were decoration with a footnote. Once each color meant one thing, everywhere, the footnote fell off by itself.

See it: open bubble sort, then binary search. The settled bars and the found element are the same green, in two lessons that never coordinated — because in both, green means you're done.