Eighteen marks,
one hand.
Organic, mechanical and interface forms drawn on a single 24-unit grid with one stroke, one cap, one join and one radius — shipped as a <symbol> sprite that takes its colour and weight from CSS.
The row
All eighteen at 24px, in one line, in category order — the arrangement most likely to expose a drift in weight between dials, weather and actions. Squint to read mass instead of detail.
The set
Command counts are read from the live sprite (implicit repeats counted, shared parts counted where used). Select an icon to inspect its construction and source.
- Symbol
- Commands
- Elements
- 16px form
Construction
Geometry is specified on the stroke centreline. The shaded band is padding: ink may reach the live-area edge, never past it.
- Canvas
- 24 × 24 viewBox, live area 20 × 20, padding 2u measured on the ink. Centrelines therefore stay inside 2.75–21.25.
- Stroke
- 1.5u, centred — 1px at 16, 1.5px at 24, 6px at 96. Never baked into the sprite: set by
--i-stroke, so it survives CSS. Dark surfaces take ×0.92 to offset halation. - Caps · joins
- Round · round. Dots are zero-length round-capped strokes, so they track the stroke weight exactly.
- Corners
- 2u on every orthogonal corner. All other vertices — triangle, funnel, star, hull, bolt, chevrons — are carried by the round join, so no acute point reads blunter than a right angle.
- Keyshapes
- Circle Ø18 · square 16 · portrait 16×18 · landscape 18×16.
- Optical
- The circle overshoots the square by 1u a side. Triangle and star run to the full live area because they read smaller than their box. Open horizontals run to the live edge. Narrow verticals (staff, thermometer) carry side furniture to match the row's horizontal mass.
- Spacing
- Unconnected marks keep ≥2.5u centre-to-centre. Below that they fuse at 16px — which is why Anemometer and Barometer ship a small form.
- Shared forms
- One wave (half-period 3u, amplitude 2u). One cloud, defined once in
<defs>. Arrowheads are 90° chevrons, 3.5u; arc-terminal heads 3u; pointers inside frames 2.5u. - Source
- Two-decimal coordinates, no transforms, no groups, ≤24 commands per icon, a
<title>on every symbol.
Size ramp
The full row at each supported size. At 16px the sprite swaps in the two small forms; everything else holds as drawn.
Anemometer · -sm
The hub ring (the cup facing you) closes up at 16px. The small form drops it and opens the cups to r3.
Barometer · -sm
Ticks and pivot ring become 1px specks at 16px. The small form keeps dial, needle and window only.
Light & dark
One sprite. Colour is currentColor; weight is a custom property. The dark surface trims stroke by 8% so light-on-dark lines don't bloom heavier than their light-mode twins.
In context
A station dashboard using all eighteen. Toggle annotations to see which instances are hidden from assistive technology and which carry a name.
Tide & weather
- Tide3.42m ↑
- Swell · 11 s WSW1.8m
- Wind · NW22kn
- Gust · peak 13:4034kn
- Pressure · falling1004hPa
- Water11.6°C
- Rainfall2.4mm/h
- Lightning · 20 km0strikes
aria-label on the control or role="img"Usage
Include the sprite once per document, reference symbols with <use>, and name icons at the point of use: a <title> inside a referenced symbol is not reliably exposed through <use>.
1 · Sprite, once
Zero-size rather than display:none, so shared parts in <defs> always resolve.
<svg class="sprite" aria-hidden="true">
<defs><path id="g-cloud" d="M16.5 14H9a5 5 0 1 1 4.58-7h2.92a3.5 3.5 0 0 1 0 7Z"/></defs>
<symbol id="i-wind" viewBox="0 0 24 24">
<title>Wind</title>
<path d="M3 8h7.5a2.5 2.5 0 1 0-2.5-2.5M3 12h15a3 3 0 1 0-3-3M3 16h11a2.5 2.5 0 1 1-2.5 2.5"/>
</symbol>
</svg>2 · One rule carries the paint
Symbols hold geometry only; every paint property inherits through <use>.
.sprite{position:absolute;width:0;height:0;overflow:hidden}
.i{width:24px;height:24px;flex:none;fill:none;
stroke:currentColor;stroke-width:var(--i-stroke,1.5);
stroke-linecap:round;stroke-linejoin:round}
.s16{width:16px;height:16px} /* pair with -sm forms */
[data-theme=dark]{--i-stroke:1.38}Decorative — the text names it
Hide the icon so the label isn't read twice.
<button class="btn"> <svg class="i s20" aria-hidden="true"><use href="#i-filter"/></svg> Filter </button>
Icon-only control — name the control
The button gets the name; the icon stays hidden.
<button class="ibtn" aria-label="Export data"> <svg class="i s20" aria-hidden="true"><use href="#i-export"/></svg> </button>
Meaningful image — name the icon
When the icon is the only carrier of a noun, it is an image.
<svg class="i s20" role="img" aria-label="Wave buoy"> <use href="#i-buoy"/> </svg>
State — fill, don't swap
Favourite fills on its pressed state; the outline geometry is unchanged.
<button class="ibtn fav" aria-label="Favourite station" aria-pressed="true">…</button>
.fav[aria-pressed=true]{color:var(--warn)}
.fav[aria-pressed=true] .i{fill:currentColor}