Status Glyph

Five glyphs share one chassis so "succeeded", "failed", "loading", "neutral", and "warned" read with identical weight and alignment wherever they appear (list rows, toasts, build logs, request panels). The icon is purely a glyph — the parent decides whether to wrap it in a tooltip, button, or label. Tint flows through currentColor.

Customize
Glyph
check
md
currentColor

Installation

npx shadcn@latest add https://craftbits.dev/r/status-glyph.json

Usage

import { StatusGlyph } from "@craft-bits/core/svg/status-glyph";
 
<span className="text-cb-success">
  <StatusGlyph status="check" />
</span>

Understanding the component

  1. One chassis, five glyphs. All five share a 0 0 16 16 viewBox, the same currentColor fill/stroke discipline, and the same baseline alignment. Swapping status never shifts row layout.
  2. Tint inheritance, not a colour prop. currentColor cascades from the wrapping element, so the same icon reads as success-green inside a text-cb-success row and error-red inside a text-cb-error toast. accentColor is the per-render override when inheritance isn't enough.
  3. Spinner uses injected CSS, not a JS loop. The rotation lives in a single @keyframes rule wrapped in @media (prefers-reduced-motion: no-preference) — the rule is appended to <head> the first time a spinner mounts. Reduced-motion users see a static three-quarter ring with no JS branch.
  4. Generalised from the source. AlgoFlashcards' StatusGlyph mixed authoring metadata (draft / in-design / ready / shipped) with per-user completion and wrapped itself in a tooltip. The library variant keeps only the universally meaningful icons, drops the tooltip wrapping, and forwards the ref to the underlying <svg> — matching every other @craft-bits/core/svg/* primitive.

Variants / Examples

// All five glyphs at the default 16px size.
<StatusGlyph status="check" />
<StatusGlyph status="cross" />
<StatusGlyph status="spinner" />
<StatusGlyph status="dot" />
<StatusGlyph status="warning" />
 
// Size tokens (sm = 12px, md = 16px, lg = 20px).
<StatusGlyph status="check" size="sm" />
<StatusGlyph status="check" size="lg" />
 
// Per-render colour override (wins over currentColor).
<StatusGlyph status="warning" accentColor="#f59e0b" />
 
// Mark decorative — the parent owns the accessible label.
<button aria-label="Retry">
  <StatusGlyph status="spinner" ariaLabel={null} />
</button>

Props

PropTypeDefaultDescription
status"check" | "cross" | "spinner" | "dot" | "warning"Which glyph to render.
size"sm" | "md" | "lg""md"Pixel diameter (12 / 16 / 20). The viewBox stays 0 0 16 16.
accentColorstringCSS colour applied as inline color. Wins over currentColor inheritance.
ariaLabelstring | nullper-status defaultAccessible label. Pass null to mark the glyph decorative (aria-hidden).
classNamestringExtra classes merged onto the root <svg>.
...restSVGAttributes<SVGSVGElement>Forwarded to the root <svg> (except viewBox and fill, which the component owns).

Accessibility

  • Each status ships a sensible default aria-label ("Success", "Error", "Loading", "Status", "Warning") and renders with role="img". Override via ariaLabel.
  • Pass ariaLabel={null} to mark the glyph decorative — the component sets aria-hidden="true" and drops the role, so the parent owns the announcement.
  • The spinner rotation is gated by @media (prefers-reduced-motion: no-preference). Users with reduced motion enabled see a static three-quarter ring, no JS branch needed.
  • Colour tinting flows through currentColor, so the icon inherits whatever contrast the wrapping surface already meets.

Credits

  • Extracted from: AlgoFlashcards (src/platform/ui/StatusGlyph.tsx).