Brik Design System
Components

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:

weightRendersUse
boldph:name-boldDefault. Most contexts.
regularph:namePhosphor's thin default stroke
fillph:name-fillActive/selected states, status badges
lightph:name-lightDecorative or quiet contexts
thinph:name-thinRare; very specific design intent
duotoneph:name-duotoneEmpty 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:

TokenUse
--text-primaryDefault icon next to body text
--text-secondary / --text-mutedQuiet / secondary icons
--text-linkIcons inside links
--text-positive / --text-negative / --text-infoStatus icons
--text-inverseIcons 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

  1. Find the icon name at phosphoricons.com — copy the kebab-case name.
  2. Add a named constant to components/icons.ts using the ph:icon-name format.
  3. (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-label directly on the surrounding element.

On this page

💬