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

PropTypeDefaultDescription
nameMaterialSymbolIconNameThe Material Symbol to render. Ignored when children is set.
childrenReact.ReactNodeA 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.
colorSparkAccentColor'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.
classNamestringMerges 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.

On this page