Icon system

Tidepoint

Eighteen marks for a coastal tide & weather station dashboard — organic readings, mechanical instruments and interface actions, held to one hand on one grid.

24×24 viewBox 2px keyline padding 20×20 live area 1.75 stroke, round cap/join 18 icons · 0 raster
01 — The hardest test

All eighteen, at 24px, in one line

No cherry-picking, no enlarging. If the hand wobbles anywhere, it shows here first.

Organic — swell, wind, moon, rain, sun, fog Mechanical — anemometer, buoy, gauge, mooring, barometer, compass Interface — filter, export, alert, history, compare, favourite
02 — The set

Eighteen concepts, three registers

Readings the sea produces on its own, instruments bolted to a pier to measure them, and the interface chrome that lets someone act on the numbers.

03 — Scale honestly

16px to 96px, same symbol

Stroke width lives in viewBox units, so it scales with the mark instead of staying pinned to a pixel value.

A deliberate simplified form

Two wave-lines read cleanly at 24px and above. Below that they merge into noise, so swell ships a second, one-line symbol swapped in at small sizes rather than a shape that quietly degrades.

swell · 16px
swell-sm · 16px ✓
swell · 48px ✓
swell-sm · 48px
04 — Themeable

One sprite, currentColor throughout

No dark-mode duplicate set. Every stroke and the handful of solid accent dots inherit color from context.

forced light surface
forced dark surface
05 — In context

A plausible dashboard fragment

Every icon in the set, doing an actual job: a station card with live readings, an advisory, instrument status and a toolbar.

Station 14

Sennen Cove

14 ktWind, NW
1.8 mSwell, 9s
1013 hPaPressure, falling
62%Waning gibbous
40%Rain chance
2 nmVisibility
06 — Usage

Decorative vs. named

An icon beside a visible label is decorative — assistive tech should skip it. An icon that is the control needs a spoken name on the interactive element, not buried in the sprite.

<!-- decorative: label carries the meaning -->
<svg class="icon" aria-hidden="true" focusable="false">
  <use href="#icon-filter"></use>
</svg>
<span>Filter</span>

<!-- meaningful: icon-only control -->
<button aria-label="Add to favourites" aria-pressed="false">
  <svg class="icon" aria-hidden="true">
    <use href="#icon-favourite"></use>
  </svg>
</button>

Every <symbol> still carries its own <title> for source legibility and for tooling that inspects the sprite directly — it just isn't relied on as the only accessible name in the UI.

<symbol id="icon-buoy" viewBox="0 0 24 24">
  <title>Buoy</title>
  <path d="M9,15 L9.8,8.4 ..."/>
</symbol>
07 — Construction

The rules that make eighteen icons one family

— solid: 24×24 canvas edge - - dashed: 2px keyline / 20×20 live area stroke: 1.75, round cap + join

Grid

24×24 viewBox, 2px padding on every side. Circular marks (moon, sun, compass rim, barometer dial) are allowed to press to the live-area edge — a curve at the same extent as a straight edge always looks smaller.

Stroke

1.75 units, round caps, round joins, currentColor. Nothing is a baked-in fill except the marker dot described below.

Corners

Round joins stand in for a ~2-unit corner radius on every freeform turn; the two rectilinear marks (filter, export) use an explicit matching curve instead of a hard corner.

Optical correction

Circles are drawn 4–8% larger than a square of the "same" size would need, so the moon, sun and gauges read as equal in weight to squarer marks beside them. The favourite star keeps its inner radius at 40% of the outer so points don't needle at 16px.

Density

Every symbol is primitives (circle/line) plus at most one or two short paths. No path exceeds 11 of the 24-command budget; most use far fewer.

The marker dot

One deliberate solid shape recurs across the set — alert, history, tide gauge and buoy each carry a small currentColor dot, unstroked, 0.8–1.3 units. It's the family's one fill, used the same way every time.