Tool: Layers and Groups

July 10, 2026

A group is a promise. It says: these five shapes are one thing now, and from here on you may treat them as one thing.

Almost everything about groups follows from taking that promise seriously, and the most instructive bug I have shipped came from not taking it seriously enough.

Here is what happened. I gave an AI agent a link to a project and asked it to lay out a logo — a mark and a wordmark, side by side, vertically centred against each other. The mark was seven shapes: a circle, some bars, a couple of carved negative spaces. Sensibly, the agent grouped those seven into one object first. Then it asked to vertically centre the group against the wordmark.

And the composition exploded.

Every one of the seven shapes centred itself individually against the wordmark. They all piled up on the same horizontal axis, on top of each other, and the mark — which had taken twenty commands to build — was gone. Not corrupted, exactly. Each shape had done precisely what it was told. The result was a heap.

My first instinct was that the agent had made a mistake, and I sat down to write a line in the documentation explaining that you should ungroup before aligning. Which is, on reflection, an insane sentence. It says: our grouping feature is a trap, and here is the ritual for avoiding it.

The agent hadn’t made a mistake. It had believed the promise. It grouped seven shapes into one thing, and then it asked the one thing to move, and we atomized it. The bug was ours, and it had been sitting there in the human UI too — select a group, hit align, watch your composition detonate — where it had presumably been annoying people who assumed they’d done something wrong. [1]

The fix is a function called units(). Before an align or a distribute runs, it resolves the selection into units: a loose shape is a unit, and a group is a unit — one unit, with one bounding box, computed across everything inside it. Then the operation runs on units. A group’s bounding box moves, and everything inside travels with it, rigidly, keeping every internal relationship exactly as it was.

Which is what every design tool does, and what any person would expect, and what the word “group” plainly means. We just hadn’t implemented the meaning. We’d implemented a list.

A round blue mark with two white bars inside it, sitting above and left of the word justdraw. The same mark, now vertically centred against the word justdraw, with the two white bars still in their original places inside it.
The mark is a group of three shapes, vertically centred against the wordmark. It travels as one rigid piece: the bars inside it never move relative to the circle. Before units(), this operation centred all three shapes individually and destroyed the mark.

That’s the general shape of the lesson, and it’s the reason this post exists. There is what a group is in the data — a node with children, a container, a list of ids. And there is what a group means to the person — one thing. Those are not the same, and the gap between them is where bugs of this class live. Whenever an operation reaches into a group and touches the children individually, ask whether it should have been talking to the parent. Usually it should.

Layers are the same idea pointed at a different axis. A group is about which things belong together. A layer is about what is in front of what — the stacking order, plus the ability to lock or hide an entire stratum of the drawing at once. The reason both exist is that “these things are one object” and “these things are at the same depth” are genuinely different claims, and a program that conflates them will make you choose between organising your artwork and organising your z-order.

Groups nest, so a mark can be a group of a group. Order (front, back, forward, backward) works on the same units — sending a group backward sends the whole group backward, as a slab, rather than interleaving its members with whatever they land among.

And the CLI and the agent API get all of this for free, because they don’t implement alignment. They send arrange.align to the editor, and the editor is the only thing in the system that knows what alignment means. Fix units() once and the button, the keyboard shortcut, the shell command and the AI all become correct in the same commit. That is the entire argument for having one implementation, and I would not have believed it as strongly if I hadn’t watched the alternative destroy a logo. [2]

[1] I never got a bug report about this from a human. I think people assumed they had mis-clicked, undid it, and quietly formed the habit of ungrouping first. A superstition instead of a complaint. It took a machine — which has no pride, and no habit of blaming itself — to do the obvious thing and reveal that the obvious thing was broken.

[2] The other half of the fix was documentation: the briefing now tells an agent to group what belongs together, precisely because a group aligns as one piece. The behaviour became something you can rely on, so the docs can now recommend relying on it.

justdraw.fyi is a vector editor that opens in a tab. No account, nothing to install, and every drawing is a link you can send to anyone.

← All posts