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.
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" />