An icon belongs to a set when it is drawn to the same grammar as the rest of the set. So do not draw from taste. Measure the grammar from the icons the project already ships, draw against those numbers, then check the drawing by rendering it and measuring it the same way. Geometry is checkable this way. Whether the glyph reads as the thing it names is not, and that part stays a model call or a human look.
A Grammar Is a Measured List, Not a Style Word
"Matches our icons" is not actionable. The grammar is the small set of numbers every icon in the set already agrees on, and each one constrains a different way a drawing can go wrong.
| Field | What it records | What it stops |
|---|---|---|
| viewBox | The coordinate box every icon is drawn in | An icon that scales differently from its neighbours |
| Stroke width | The single width used for every line | A glyph that reads heavier or lighter in a row |
| Stroke linecap | butt, round or square | Ends that look cut where the set looks soft |
| Stroke linejoin | miter, round or bevel | Corners that spike or blunt against the set |
| Corner radii | The radii on rects and rounded turns | A hard box in a set of soft ones |
| Inked extent | Width and height of the drawn marks | A glyph that sits too small or too large on the canvas |
| Padding | Clear space from the ink to each viewBox edge | Ink that crowds one edge and floats off another |
| Element mix | Which primitives the set uses, and how many subpaths | A path soup where the set uses two clean shapes |
Record the range, not a single value. Twenty icons give a minimum, a maximum and a median for each numeric field, and the range is what a check can be written against. The categorical fields (cap, join, element mix) are usually one value across the whole set, and a drawing that differs on them is wrong rather than unusual.
Measure in a Renderer, Not in the Path Text
Parsing the d attribute measures the points that define a curve, never the curve. A bell drawn in a prototype measured 17 by 13 units from its anchor points. The browser measured the same bell at 20 by 22, because an arc bulges past the points that place it. That gap is larger than most of the tolerances worth checking, so a text parser does not just add noise, it reports the wrong answer with confidence.
Render the markup and ask the renderer:
const box = element.getBBox();
// box.x, box.y, box.width, box.height in viewBox units
Three rules keep the measurement honest:
- Measure the seeds and the drawings through the same code path. A grammar measured one way and checked another compares two different quantities.
- Decide once whether the extent is the geometry or the ink, and stay with it.
getBBox()returns the geometry box and ignores stroke width, so a stroked mark paints half a stroke wider on every side. Either add half a stroke width at each edge, which grows each total dimension by one stroke width, or leave the stroke out of every measurement. - Measure each icon on its own, then measure the set. A per-icon box gives the extent and padding; the spread across the set gives the envelope.
Solving the curve exactly is the other honest route, and the only one available where there is no DOM, such as inside a worker. Take the extremes of each cubic from the roots of its derivative and each arc from its centre form, rather than sampling the anchor points. Check the solver against a renderer on a real family before trusting it, because the failure it replaces was itself a plausible-looking wrong number.
Let the Seeds Calibrate the Checker
Write the first rule set, run it against the seeds, and treat every seed failure as a bug in the rule. The first rule set in the prototype failed 4 of the 20 real icons. Those icons ship, so the rules were wrong, not the icons. After calibration the seeds scored 20 out of 20, and the calibrated rules still caught 3 drawings that sat too small on the canvas.
That is the whole method, and the order matters:
- Measure the seeds.
- Derive the rules from the measurements.
- Run the rules against the same seeds.
- Loosen or delete any rule a seed fails, then repeat until every seed passes.
- Only then check drawings.
A checker calibrated this way keeps its teeth. Calibration removes the rules that were never true of the set; it does not remove the rules that describe it. Calibrate against the set in front of you and recalibrate whenever the seed set changes. There is no global icon grammar to inherit.
The Sample Sets the Envelope, and the Sample Has Blind Spots
The envelope describes the icons you measured. It is not a law about icons in general. A flat battery drawn in the prototype was rejected for filling 12 units of height, because no seed icon was that flat. The drawing was not wrong about batteries. The sample simply had no flat thing in it.
When a rejection looks like this, choose deliberately:
- Redraw the concept inside the envelope when the proportions are negotiable. Most concepts are.
- Widen the sample when they are not. Add seeds that cover the shape you need, remeasure, and recalibrate.
- Never widen a single rule to let one drawing through. A tolerance loosened for one glyph is a tolerance that no longer describes the set.
Say how many seeds the grammar came from whenever you report it. Twenty seeds gave a usable envelope in the prototype. A smaller sample gives a tighter and more arbitrary one, and nothing about that is visible in the output.
Check the Numbers, Then Look at the Sheet
The two gates catch different faults, so run both. In the prototype, round 1 passed 9 of 12 drawings on geometry. The contact sheet then failed 3 different icons: the key read as a bone, the rocket read as a leaf, and the wrench read as a blob. None of the three broke a single number. Round 2 fixed both sets of faults and 11 of 12 read correctly.
Render the sheet the way the icons will be used:
- One page, every icon at the size it ships at, on the surface it ships on.
- Same stroke colour for all of them, taken from the token they will inherit, for example
oklch(0.2 0.01 260)on a light surface. Colour differences hide shape differences. - No labels next to the glyphs on the first pass. A caption tells you what to see, which is the one thing the sheet is meant to test.
- Keep the failing icons on the sheet across rounds, so a fix that breaks a neighbour is visible.
Recognition Is Not Checkable by Geometry
No measurement tells you whether a glyph reads as the thing it names. Extent, padding, stroke width and element mix were all inside the envelope for the key that read as a bone. The numbers describe how an icon sits in the set. Recognition is about what the shape resembles, and that judgement stays with a model or a person.
So never report a drawn set as finished on a green geometry check. The check says the set is consistent. Someone still has to say it is legible.
Drawing Rules That Follow
- Draw in the seed viewBox. Scaling a drawing into it afterwards changes the stroke width relative to everything else.
- Use one stroke width for the whole icon, the one the seeds use. A second width inside a single glyph reads as a mistake even when it measures fine.
- Paint with
currentColorand leave the fill to the set's convention, so the icon follows the semantic token around it. - Keep the element count near the seed median. A concept that needs three times the subpaths of every neighbour is usually an illustration, not an icon.
- Leave the padding the seeds leave. Ink that reaches the viewBox edge collides with neighbouring glyphs in a row and gets its outer half stroke clipped, because inline SVG viewports clip by default.
Checklist
- The grammar was measured from named seeds, and the seed count is recorded.
- Every measurement came from a renderer, not from parsing path text.
- Seeds and drawings were measured through the same code path, with the same stroke convention.
- Every seed passes the calibrated rules.
- No tolerance was widened to admit one drawing.
- The drawings were rendered as one contact sheet at shipping size and looked at.
- A model or a person confirmed each glyph reads as its name, and that confirmation is separate from the geometry result.