Icons
Two different problems wear the same word. The functional glyphs come from a library; the ones only your product has are drawn — and this is what they are drawn to.
Which problem you have
Conflating these is what makes iconography feel unanswerable.
| Layer | Example | Who owns it |
|---|---|---|
| UI glyphs | A library. @phosphor-icons/react already does. | |
| Domain glyphs | You. No library can carry a seat, a cascade, a lapsed record. | |
| The mark | a logo, a favicon | Drawn once per venture. Not a set — see @shuttering/error-pages/mark. |
This package ships no art
The contract
One weight, one box, one live area. Mixing two stroke weights is the single most legible sign that a set was assembled rather than drawn.
Do: Draw at 24 and judge at 16. Most glyphs render at 16, so a set only ever looked at large is a set whose problems ship.
Don’t: Mix stroke weights across a set — it is the most legible sign a sheet was assembled rather than drawn. checkGlyph fails a file carrying two, and a file declaring none.
Accessibility
Decorative by default, because that is the common case — a glyph beside a label is announced twice if it carries a name.
The default runs the other way in most icon sets
Glyph hides unless you name it, and applies that treatment after the prop spread so a call site cannot undo it by accident.Checking art you did not draw
The point of a written contract is a set commissioned from someone outside the repo. checkGlyph says whether the files are drawn to it before they are committed.
view-box— viewBox is "0 0 32 32", not "0 0 24 24" — the 24 drawing boxstroke— stroke-width is 2, not 1.5 — one weight per set, or the sheet reads as assembledstroke-linecap— no stroke-linecap; every glyph declares "round"stroke-linejoin— no stroke-linejoin; every glyph declares "round"currentColor— literal colour stroke="red" — a glyph paints in currentColor so one drawing serves every groundlive-area— x="1", y="1" outside 3–21; a glyph that fills the box sits heavier than its neighbours
Also available as a Vitest matcher — import '@shuttering/icons/vitest', then expect(svg).toBeDrawnToContract() over a directory of delivered files. The checker entry is React-free, so a build script can import it without crossing a client boundary.
API
| Prop | Type | Default | Description |
|---|---|---|---|
| children (required) | ReactNode | — | The drawn shapes, on a 24 box, inside the live area. |
| label | string | — | An accessible name. Omit for a decorative glyph — the common case, and the default. |
| size | number | 24 | Rendered box in px. The drawing itself is resolution-independent. |