All pages

Foundations

Icons

Icons use the Lucide renderer from lucide-react, drawn in currentColor and coloured with an ink token, so it survives either theme. Import the …Icon-suffixed name, mark it aria-hidden unless it is the whole meaning of its control, and set the tone with text-*.

List and Trend navigation use the mobile app's exact Tabler card-stack and line-chart paths, defined in components/layout/navigation-glyphs.ts with createLucideIcon. They keep the same sizing, ink and stroke conventions as the rest of the website's navigation.

Per-item saving on list details uses the native app's archive box with a plus or checkmark. SaveEntryButton shares those exact paths between desktop rows and the mobile reader sheet; the saved state changes the mark and uses quieter ink.

Lucide

The house stroke is 2.5, set once by the LucideProvider in the root layout; navigation draws at 2.25. Muted glyphs sit at text-icon-muted, lit ones at text-brand-ink. Fill a stroke with fill-current for a pressed state (a kept star, a liked thumb).

Icon paint must be opaque. Alpha in currentColor is applied separately to each SVG path, making overlaps darker in light mode and brighter in dark mode. text-icon-muted mixes ink with the canvas into one solid colour; the ink ladder is opaque in both themes too. When an icon needs transparency over a photo or frosted surface, use a solid colour and apply opacity-* to the SVG as a whole. Keep that opacity off its button so the background stays unchanged.

import { ChevronRightIcon, SearchIcon, XIcon } from "lucide-react";

<SearchIcon aria-hidden className="size-5 text-ink-tertiary" />
<XIcon aria-hidden className="size-5 text-icon-muted" />
<ChevronRightIcon aria-label="Open" className="size-5 text-icon-muted" />

KindIcon

KindIcon draws at strokeWidth 2 and maps each of the eleven library kinds to its glyph — chosen to agree with the mobile app's Tabler set on what each kind looks like, so the same thing seen on a phone and in a browser is not wearing two different signs. An unknown kind falls back to the link glyph: the most general kind there is, never a blank square.

musicpodcastfilmgamebookproductimageplaceapppeoplelink
import { KindIcon } from "@/components/base/kind-icon";

<KindIcon kind="music" className="size-5 text-ink-secondary" />

Provider marks

The OAuth providers' own marks, shared by the login form and the settings screen so a brand mark cannot drift between the two. Apple and GitHub are drawn in currentColor and follow the theme; Google's is the four-colour G, which its brand terms do not allow to be recoloured.

import { ProviderMark } from "@/components/base/provider-mark";

<ProviderMark provider="apple" />
<ProviderMark provider="google" />
<ProviderMark provider="github" />

Wordmark

The MyList wordmark is a single-colour SVG used as a CSS mask: the shape comes from the file, the colour from a token, and there is one asset instead of a white and a black one to keep in step. A surface with a fixed palette opts out by passing its own paint — the chip floating over a list's artwork does not follow the app theme.

import { Wordmark } from "@/components/base/wordmark";

<Wordmark className="h-5 w-[3.99rem]" />
<Wordmark className="h-5 w-[3.99rem] bg-brand-ink" />