Icons
The BDS Icon wrapper — Phosphor icons from a bundled offline subset, bold by default.
BDS ships its own Icon component — an offline-first wrapper around @iconify/react that renders Phosphor (ph:*) icons from a bundled subset. Icons render as inline SVGs with no runtime CDN fetch, no flash-of-unstyled-icon, and no bundle bloat from importing the full set. It is API-compatible with @iconify/react's <Icon>, and defaults ph:* icons to bold weight (BDS's standard line density).
Setup
No consumer setup is required — importing Icon from @brikdesigns/bds auto-registers the bundled Phosphor subset (components/icons.generated.json) into Iconify's global store on module load, so ph:* icons resolve synchronously with no network call. A ph:* icon not yet in the subset falls through to Iconify's default behavior (a runtime CDN fetch). To bring your own icon collections offline, call addBrikIcons(collection) once at app start.
Use it for
- Inline icons in buttons, badges, tooltips, fields
- Decorative icons in cards and headers
- Status / state markers paired with text
- Anywhere a small (≤ 32px) glyph carries meaning
For larger illustrations and brand marks, use a static SVG asset directly. For non-icon visual primitives (ServiceTag, avatar fallbacks), use the dedicated component.
Import
import { Icon } from '@brikdesigns/bds';addBrikIcons is also exported from @brikdesigns/bds for registering extra offline collections.
Usage
Direct string reference
The icon prop accepts a collection:name string. Phosphor icons use the ph: prefix.
<Icon icon="ph:star-fill" />
<Icon icon="ph:check" />
<Icon icon="ph:x-circle" />Find available icons at phosphoricons.com.
BDS icon constants
Inside the BDS repo, component authors prefer the named constants in components/icons.ts — they protect against typos and centralize the icon vocabulary. These are not part of the public package export; they're imported by relative path from within components/:
import { Star, Check, XCircle } from '../../icons';
<Icon icon={Star} />Constants follow the pattern export const Name = 'ph:icon-name'. Consumers of @brikdesigns/bds use the ph:* string reference directly (above).
Sizing
Iconify icons inherit font-size from their parent or accept explicit width / height props.
{/* Inherits parent font-size — recommended */}
<span style={{ fontSize: 24 }}>
<Icon icon="ph:star-fill" />
</span>
{/* Explicit size */}
<Icon icon="ph:star-fill" width={24} height={24} />For inline icons inside text, use 1em sizing so the icon scales with the surrounding text:
<button>
<Icon icon="ph:plus" style={{ fontSize: '1em' }} />
Add item
</button>Icon weight
BDS renders ph:* icons at bold weight by default — BDS's standard line density (Phosphor's own regular weight reads as a hairline at small UI sizes). Set the weight prop to change it. An icon whose name already encodes a weight (ph:star-fill) keeps it; the weight prop only rewrites otherwise-unweighted ph:* names, and non-ph:* icons ignore it.
Canonical convention: bold is the BDS UI icon weight on every surface — navigation, form controls, and general glyphs alike. There is no per-surface split; fill is reserved for active/selected states and status. Because the atom applies bold by default, routing a glyph through <Icon> is enough to inherit the canonical weight — only reach for the weight prop to opt a specific icon back to regular (or another Phosphor weight) for a deliberate reason.
<Icon icon="ph:star" /> {/* bold (default) */}
<Icon icon="ph:star" weight="regular" /> {/* Phosphor's thin default stroke */}
<Icon icon="ph:star-fill" /> {/* solid — explicit weight, left as-is */}weight accepts the six Phosphor weights:
weight | Renders | Use |
|---|---|---|
bold | ph:name-bold | Default. Most contexts. |
regular | ph:name | Phosphor's thin default stroke |
fill | ph:name-fill | Active/selected states, status badges |
light | ph:name-light | Decorative or quiet contexts |
thin | ph:name-thin | Rare; very specific design intent |
duotone | ph:name-duotone | Empty states, illustrations |
Color and theming
Iconify icons render with fill="currentColor", so they inherit color from the CSS color of any ancestor — no per-icon fill prop needed. To recolor an icon, set color on it or a parent, ideally to a BDS text token rather than a raw hex:
{/* inherits the surrounding text color */}
<span style={{ color: 'var(--text-muted)' }}>
<Icon icon="ph:info" /> Quiet hint
</span>
{/* status icon */}
<Icon icon="ph:check-circle-fill" style={{ color: 'var(--text-positive)' }} />Text tokens commonly used for icon fill:
| Token | Use |
|---|---|
--text-primary | Default icon next to body text |
--text-secondary / --text-muted | Quiet / secondary icons |
--text-link | Icons inside links |
--text-positive / --text-negative / --text-info | Status icons |
--text-inverse | Icons on dark / inverted surfaces |
BDS text tokens are mode-aware, so an icon colored with one retones automatically in brik-dark — prefer them over hardcoded colors.
Service-line glyphs
ServiceTag's per-service glyphs are not Phosphor icons — they're custom SVG assets under components/ui/ServiceTag/icons/. They render via CSS mask-image with background-color: currentColor (not <img src>), so the fill is CSS-recolorable and inherits the tag's service text token:
.bds-service-tag--marketing { color: var(--text-service-marketing-on-light); }
.bds-service-tag__icon { background-color: currentColor; /* masked glyph */ }Each service line carries a mode-aware --text-service-{line}-on-light / -on-dark pair (brand, marketing, information, product, back-office), so the glyph retones per theme. Reach for ServiceTag rather than a raw <img> whenever you need a service-line glyph — Phosphor does not cover every icon need.
Adding icons to BDS
- Find the icon name at phosphoricons.com — copy the kebab-case name.
- Add a named constant to
components/icons.tsusing theph:icon-nameformat. - (Optional) Add the icon to a category in the Storybook stories so other contributors can find it.
// components/icons.ts
export const Heart = 'ph:heart';
export const HeartFill = 'ph:heart-fill';When not to use
Don't use raw SVG inline. Iconify provides 200,000+ icons across multiple collections — you almost never need a one-off SVG. If the design calls for a non-Phosphor icon, evaluate whether a Phosphor alternative is acceptable before committing a custom asset.
- Don't use Icons for brand marks. Logos and brand-specific illustrations are static SVG assets, not icon-system components.
- Don't use Icons without semantic context. An icon-only button needs an
aria-label; an icon next to text doesn't.
Accessibility
- Iconify icons render
<svg aria-hidden="true">by default — appropriate for decorative use next to text. - For icon-only triggers (icon-only buttons), pair with IconButton which adds the required
aria-label. - For meaningful icons that aren't paired with text, set
aria-labeldirectly on the surrounding element.
Related
- IconButton — accessible wrapper for icon-only buttons
- Avatar — for person identity
- ServiceTag — for Brik service-line icons (use
variant="icon") - Storybook icon catalog
- External: phosphoricons.com — find icon names