Icon
A Material Symbol glyph inside a themed frame, for emphasis and status.
Usage
import { Icon } from '@epilot/spark-ui/icon'<Icon name="solar_power" />An <Icon> is a glyph in a styled box. It is neutral gray in every variant by
default, so it stays quiet unless you ask for colour. Pick the fill weight with
variant, set the shape with the radius knob, and add colour with color (see
Colors).
Reach for a bare MaterialSymbol (from @epilot/spark-ui/icons) when you want the glyph
with no frame, and for a Button (with an icon size) when the icon is clickable.
Icon is for a non-interactive, framed icon: a feature bullet, a list leading icon, or a
status tile.
Examples
Sizes
The small, medium (default), large, and xl sizes render a 24, 32, 40, or 48px
frame. Both the frame and the glyph are spacing-derived, so they grow and shrink with
the scaling and spacing knobs.
Styles
variant picks the fill weight: soft (default), surface, solid, ghost, or
outline. All five paint with the palette color selects (neutral by default — see
Colors). surface and outline add a 1px border.
High contrast
data-high-contrast (on the icon or any ancestor) darkens the glyph (step a11 to
a12 on whichever palette color selected) and strengthens the surface / outline
border for stronger legibility. It does not change the palette, only its depth.
Colors
color picks the palette — accent, neutral (default), success, warning, or
danger — the same axis as Button, Pill, and Avatar. It re-points every variant's
fill, ink, and border together, so a solid danger icon and a soft danger icon both
land on the same red scale without hand-picking tokens. A className fill/ink override
still wins over either, through tailwind-merge.
Shape
The frame corners follow the radius knob. At radius="full" it becomes a circle; at
smaller radii it is a rounded square. Radius must be set through <Theme> (or a
.spark-theme scope), not a bare data-radius, so the corner calc()s re-resolve.
Custom glyph
Pass any node as children to use a non-Material icon (a Lucide icon, an inline SVG).
The glyph takes precedence over name and inherits the frame's ink via currentColor.
API Reference
| Prop | Type | Default | Description |
|---|---|---|---|
name | MaterialSymbolIconName | — | The Material Symbol to render. Ignored when children is set. |
children | React.ReactNode | — | A custom glyph (Lucide, inline SVG). Takes precedence over name and inherits the frame's ink via currentColor. |
variant | 'soft' | 'surface' | 'solid' | 'ghost' | 'outline' | 'soft' | Fill weight. surface and outline add a 1px border. |
color | SparkAccentColor | 'neutral' | Palette axis, matching Button/Pill/Avatar: accent | neutral | success | warning | danger. |
size | 'small' | 'medium' | 'large' | 'xl' | 'medium' | Frame 24 / 32 / 40 / 48px with a 16 / 20 / 24 / 28px glyph. Both track the spacing and scaling knobs. |
className | string | — | Merges with the base styles; a colour, size, or radius utility wins via tailwind-merge. |
The component forwards all span HTML attributes via React.HTMLAttributes<HTMLSpanElement>.
It tracks the neutral/accent, radius, spacing, scaling, and high-contrast knobs. color
re-points the accent scale the same way Button's does (data-accent-color), so it costs
nothing extra to theme.
Accessibility
Icon is decorative by default: the glyph is aria-hidden, and with no label the frame
has no role, so assistive technology skips it. Use this when the icon merely repeats or
decorates adjacent text.
When the icon is the only carrier of meaning, pass aria-label. The frame then becomes
role="img" with that accessible name. A custom SVG glyph passed as children should
carry its own aria-hidden so it does not announce twice.
Colour is not meaning: do not rely on color alone to signal state (success, error).
Pair the icon with text or an aria-label.