Foundations
Icons
Filled paths rather than strokes, three size tokens over six sizes in the source, and a set that ships brand marks again.
Every icon in the package comes from @remixicon/react, the Line variants, drawn on a 24px grid. They are FILLED PATHS rather than strokes, which is the one difference worth carrying in your head: there is no strokeWidth to set, so the exception that used to live on this page — seven small marks pushed to 2, 2.5 or 3 because 1.5 thinned below a hairline at twelve pixels — went with the prop that made it possible. A filled path scales its own weight with its box, so a 12px mark is lighter than a 16px one and no API will change that. The two places that leaned hardest on the old escape hatch are the checkbox tick and the dash beside it, both drawn at 12px on a filled accent ground where the contrast carries what the weight no longer can.
Sixteen pixels is the default and carries most of the system: a chevron, a close, a check, a caret. 14px is for a mark inside a control’s own padding, where 16 would crowd the label — a select’s indicator, a popover’s close, a combobox’s clear. 18px is for a leading mark that opens a row rather than sitting inside one: a nav item, an alert’s tone mark, the command palette’s search. 20px is the app shell’s menu toggle, and 24px is reserved for an EmptyState, where the icon is the only thing on the surface. 12px is not really a size but a mark inside an 18px box — a checkbox tick, a chip’s close, a table’s sort caret.
Three of those six sizes are named: --ico-s is 14, --ico-m is 16, --ico-l is 20. There were four. --ico-stroke went when the stroke did, because a token naming a weight nothing in the system can set is worse than no token at all. 12px, 18px and 24px have never had one. Read the three as the specification, not as the mechanism — nothing in the package reads them. Every icon in src/components is written as size={16} in TSX, because the size lands on the SVG as an attribute rather than as a style. So the tokens are what a review checks against, and the numbers are what you will find in the source. Do not go looking for the var() that wires them together; there isn’t one.
An icon aligns by flex, never by baseline. Button sets inline-flex items-center gap-(--control-gap) on every variant, so the glyph centres against its label and the gap comes off the density axis rather than off a margin written at the call site. Centring is right on a line of text and wrong against a block of it: Alert’s tone mark sits beside a title and a paragraph, so it takes mt-px shrink-0 instead. shrink-0 is on most icons in the package and it is not decoration — without it a flex row squashes the glyph before it wraps the label, and a 16px icon compressed to 11 is the artefact everyone sees and nobody can name.
An icon is decoration until proven otherwise, so aria-hidden is the default and nearly every icon in the package carries it. A chevron on an accordion, a tick in a checkbox, a tone mark on an alert whose words already say what went wrong — announcing any of those is the same sentence twice. IT IS NOW WRITTEN RATHER THAN INHERITED. lucide stamped aria-hidden="true" onto any icon it had not been handed an aria- prop or a role for; @remixicon/react adds nothing and spreads what it is given straight onto the svg. Every decorative glyph therefore carries the attribute at its own call site, which makes it a line of review instead of a library default — and the one thing a review must not miss, because a control with no text at all has nothing else to announce. FloatingIconButton makes that the type’s problem: label is a required prop, so the code does not compile without it. Button does not — its JSDoc says an icon-only button REQUIRES aria-label and nothing checks it. An icon-only control with no accessible name is the single most common way a design system ships something unusable.
BRAND MARKS ARE BACK. lucide dropped every one of them: there was no Github export, and no Twitter, Slack, Figma, Gitlab, Linkedin, Youtube, Chrome, Codepen, Framer, Dribbble, Instagram or Facebook either, so import { Github } from "lucide-react" was a compile error rather than a missing glyph. This site paid for that with a hand-drawn octocat in apps/docs/src/components/GithubMark.tsx. Remix Icon ships them — RiGithubFill, RiTwitterXFill, RiDiscordFill, RiFigmaFill, RiSlackFill and a couple of hundred more, most in both a Fill and a Line — so that file is deleted and the masthead imports RiGithubFill like anything else. @misoto22/design depends on @remixicon/react at ^4.9.0 as a dependency rather than a peer, so an app importing it directly resolves its own copy against its own range, and the failure lands on its upgrade rather than on ours.
Read nextTypographySpace & shape
Not a size — a mark inside an 18px box: a checkbox tick, a chip’s close, a table’s sort caret.
Inside a control’s own padding, where 16 would crowd the label: a select indicator, a popover close.
The default, and most of what the package draws: a chevron, a close, a check, a caret.
A leading mark that opens a row rather than sitting inside one: a nav item, an alert’s tone, the palette’s search.
The app shell’s menu toggle.
Reserved for an EmptyState, where the icon is the only thing on the surface.
Twelve pixels, on the accent
There is no strokeWidth on any of these and nothing to keep consistent. A Remix Icon glyph is a filled path on a 24px grid, so its weight comes down with its box rather than staying put and swamping it.
Brands, which are back
Someone else’s trademark, reproduced rather than redrawn in a house stroke — which is the argument for importing one instead of inventing it. This site’s own masthead used to carry a hand-drawn octocat for exactly the want of this row.
Icon tokens
The written-down version of the rule. No component reads these — the numbers are literals at each call site — so treat a token here as the value a review asserts against rather than as the value a component resolves.
| Sample | Token | Value | Notes |
|---|---|---|---|
--ico-s | 14px | ─── Icons ─── three sizes, and no weight: a Remix Icon glyph is a filled path. | |
--ico-m | 16px | — | |
--ico-l | 20px | — |