Skip to content
HyperDesign System

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.

Icon sizes
ExampleClassUsage
14pxInside xs and 2xs buttons, copy and message actions, dense table rows.
16pxThe default. Buttons, menu items, nav rows, inputs. Button sizes it for you.
20pxEmpty 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.

209 icons

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.

The label names the action (“Close”), not the shape (“X icon”).

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.md when a dump names a lucide-* class.
  • shadcn add emits lucide imports; convert them. npm test fails on any left.
  • Need a partner logo? Add it to brand-icons.tsx as artwork, not to the icon set.