Icon system · v1.0 · coastal tide & weather station

Tideline

Eighteen icons that have to look like one hand: swells and moon phases, anemometers and moorings, filters and alerts. One 24px grid, one 1.5 stroke, round caps and joins on everything, nothing filled.

Icons
18
Grid
24 × 24
Stroke
1.5
Max nodes
– / 24
Sprite, gzip
… B

01The row test

All eighteen at 24px, one line, no labels. Organic, mechanical and interface forms should read as a single weight and a single density. Same sprite, same markup, on light and on dark.

02The set

Three families, one set of rules. Node counts are measured live from the sprite (every path segment counts, including implicit repeats and reused paths). Icons marked “small form” ship a second drawing for 16px.

03Grid & construction

Drawn on a 24×24 viewBox. The diagram renders the actual anemometer symbol at its true stroke over the live area and keylines.

24 by 24 grid with 2 unit padding, live area, and four keyline shapes, with the anemometer icon overlaid
  • Grid. 24×24 viewBox, integer and half-unit coordinates, never more than two decimals.
  • Padding & live area. 2px padding on every side; live area 20×20 (x, y 2–22). The outer edge of every stroke stays inside it.
  • Stroke. 1.5 on the centreline, no exceptions. Set from CSS with --sw; the symbols carry no presentation attributes.
  • Caps & joins. Round both. A dot is a zero-length segment (h.01), so even dots are strokes at the same weight.
  • Corners. Containers (tray, frame, staff, alert) use radius 2; free corners take the 0.75 radius of the round join.
  • Clearance. At least one stroke width of clear space between parallel strokes, so 16px renders don’t clot.
  • Keylines (outer size). Circle Ø20 · square 17.5 · landscape 18.5×15.5, portrait 15.5×18.5.
  • Node budget. 24 path segments per icon at most; the heaviest here is –.
Nominal: both 18 across. The circle looks small.
Corrected: circle r9.25, square 16. They read as equals.
  • Optical correction. Circles overshoot squares by 2.5px overall. Star and funnel points run to the live-area edge rather than being inset, because points read smaller than their bounding box.
  • Centroid. Icons that end in a point (pin, funnel, anchor) hang to y=21 and start at 3 so their mass, not their bounds, sits on centre.

04Stroke & colour

Stroke weight and colour are consumer-side CSS. The same symbols render at three weights and any colour, with no second copy.

--sw: 1
--sw: 1.5 (default)
--sw: 2
color: #0e6c83

05Scale

Strokes scale with the icon, so 96px stays proportionate rather than turning to wire. At 16px the stroke is 1px, which is fine for most forms. Three icons are too dense at that size, so each ships a deliberate small drawing and components swap to it at 16px or below.

Anemometer: the mast drops out and the cups grow. Tide gauge: three ticks become two and two waves become one. Pressure: the dial’s dots go, leaving needle and case.

06Light & dark

Everything inherits currentColor. Nothing in the sprite knows what the background is.

High water 07:52 · 2.9 m · wind NW 18 kn

High water 07:52 · 2.9 m · wind NW 18 kn

07In context

A station panel from the dashboard. Icon-only buttons are named on the button; icons beside text are hidden from assistive technology. Favourite fills with fill: currentColor when pressed.

Harbour Mouth · Stn 14

Updated 06:40 UTC · 2.1 nm from Buoy B-4

  • Wind18 kn NW
  • Gust31 kn
  • Swell1.8 m · 9 s
  • Pressure1008 hPa
  • Water11.4 °C
  • Rain2.4 mm/h
  • High water · 2.9 m
  • Gauge now · 1.42 m
  • Waxing crescent · springs in 3 d
  • Berth 7 mooring load
  • Buoy B-4 · on station

08Usage

Inline the sprite once. Reference symbols by id. Size comes from --s, weight from --sw.

<!-- 1 · sprite, once, at the top of <body> -->
<svg width="0" height="0" style="position:absolute" aria-hidden="true">…<symbol id="i-wind" viewBox="0 0 24 24"><title>Wind</title>…</symbol></svg>

<!-- 2 · decorative: a visible label already says it -->
<button><svg class="ic" aria-hidden="true"><use href="#i-export"/></svg> Export</button>

<!-- 3 · icon-only control: name the button, hide the glyph -->
<button aria-label="Filter readings"><svg class="ic" aria-hidden="true"><use href="#i-filter"/></svg></button>

<!-- 4 · standalone and meaningful: the svg carries the name -->
<svg class="ic" role="img" aria-label="Wind, 18 knots"><use href="#i-wind"/></svg>

<!-- 5 · 16px or below: use the small form where one exists -->
<svg class="ic" style="--s:16px" aria-hidden="true"><use href="#i-gauge-s"/></svg>

/* consumer CSS: the only place stroke is defined */
.ic{width:var(--s,24px);height:var(--s,24px);fill:none;stroke:currentColor;
    stroke-width:var(--sw,1.5);stroke-linecap:round;stroke-linejoin:round}
DecorativeBeside visible text, aria-hidden="true". The tiles in §02 and the metric cards in §07 do this.
Named controlThe name goes on the <button>. Filter and Favourite in §07 are icon-only, and Favourite exposes its state with aria-pressed.
StandaloneUse role="img" with aria-label on the outer svg. The symbol’s <title> is for authors and tooling, not a reliable accessible name through <use>.