Icons
One library: Tabler, on a 24-unit grid with a 2-unit round stroke. Colour comes from the text around it. The full convention is in docs/brand/icons.md.
Usage
Import named exports from the package root. Next rewrites the barrel to per-icon files, so no deep paths.
import { IconArrowLeft, IconSearch, type TablerIcon } from "@tabler/icons-react"; // Decorative, next to a label: hidden from assistive tech.<Button variant="ghost"> <IconPlus aria-hidden="true" /> New thread</Button> // Icon-only: the control carries the name, the icon stays hidden.<Button size="icon-xs" aria-label="Close"> <IconX aria-hidden="true" /></Button>Sizes
Three sizes, set with a size-* class. The stroke is in grid units, so it thins as the icon shrinks: 2px at 24, 1.33px at 16.
| Example | Class | Usage |
|---|---|---|
| 14px | Inside xs and 2xs buttons, copy and message actions, dense table rows. | |
| 16px | The default. Buttons, menu items, nav rows, inputs. Button sizes it for you. | |
| 20px | Empty states, section leads, the 40px icon tile. |
Library
Every Tabler icon the app renders today. Search by name, switch the size, and click a tile to copy its export name.
Rules
Distinct glyphs for distinct jobs, and a second signal when two glyphs differ only by rotation.
DoA chevron reveals in place; an arrow goes somewhere.
AvoidOne chevron for both expanding and navigating.
DoMirror-image pairs carry a label.
AvoidTwo arrows told apart only by direction.
DoA Filled export only where filled means a state: starred, chosen.
AvoidHand-tuned strokes, or a 24-grid icon pushed past 32px.
Accessibility
Tabler adds no aria attributes of its own. Decorative icons say aria-hidden; icon-only controls put the name on the control.
Artwork is not an icon
Partner logos and illustrations are inline SVG artwork. They keep their own colours and sit outside the Tabler rule; partner logos live in src/components/app/brand-icons.tsx. An icon takes the text colour around it; artwork never does.
Icon. One stroke, one colour: currentColor, set by the parent’s text class.
Artwork. Its own fills and proportions; the same muted parent leaves it untouched.
Best practices
When to use
- Beside a label, to speed up scanning a list or a toolbar.
- Alone only for universal actions (close, search, more) and always with an aria-label and a tooltip.
- Not for save, export or share without text: an icon alone is not a label.
Layout
- 6 to 8px between an icon and its label (gap-1.5 or gap-2).
- Whole pixels: size-4 inside h-8, never a fractional offset.
- Leave colour to currentColor; set it with text-* on the icon or its parent.
Accessibility
- Decorative icons carry
aria-hidden="true". - A toggle’s label says what it does next and adds aria-pressed or aria-expanded.
- No title attribute for a control’s name.
Adding an icon
- Check the table in
docs/brand/icons.mdwhen a dump names alucide-*class. - shadcn add emits lucide imports; convert them.
npm testfails on any left. - Need a partner logo? Add it to brand-icons.tsx as artwork, not to the icon set.