Components · Text & content
Icon
Material Symbols Outlined at weight 300, on a four-step size scale (12 / 16 / 20 / 24px). Each size pairs with its matching optical size so glyphs stay crisp. Functional icons inherit currentColor; only true identity icons take the brand lime.
Preview
Build with the skill
No package to install — hand this to your AI to generate or migrate the component in your stack.
Apply the Zetta "Icon" component to this project using the zetta-design-md skill.
WHEN TO USE
- Migrating or fixing existing code (the common case): audit the current implementation against the `icon` spec (variants: sm, md, lg, xl) and CORRECT every deviation — replace off-spec values, don't only add new ones.
- New component: build it to the `icon` spec in this project's stack.
AUDIT & FIX (when the component already exists)
Compare it to the skill's spec and fix, don't just append: hardcoded colors/hex, off-scale radius, wrong fonts, dark/navy fills, shadows used in place of borders (or missing borders), brand lime used as a CTA or interaction (must be primary indigo), and any missing or wrong hover / focus / active / disabled / invalid state or missing 2px focus ring. Replace each with the correct Zetta token.
ALWAYS
- Pull the authoritative spec and tokens from the skill — never guess or hardcode values.
- Use this project's own component conventions and framework idioms.
- Honor the Zetta guardrails (brand = identity only, primary = interaction, shadows for overlays) and verify in Light, Dark, and Accessibility. Anatomy & rules
╔══════╗
║ ⬡ ║ ← Material Symbol (outlined, variable font)
╚══════╝
↑ opsz axis matches rendered px size (12 / 16 / 20 / 24)
↑ wght = 300 (default), grad = 0
Parts:
- Glyph — single Material Symbol rendered via variable font (
@material-symbols/font-500) - Bounding box — square container at token size (sm: 12px, md: 16px, lg: 20px, xl: 24px)
- Color — always
currentColor(inherits from parent text color token)
Icon is non-interactive. No interactive states.
| State | Visual |
|---|---|
| Default | Icon rendered at specified size, inherits currentColor |
| Decorative | aria-hidden="true", no semantic role |
| Semantic | Paired with aria-label or adjacent text |
When to use
- Communicating actions (buttons), states (status), and navigation (labels with icons).
When not to use
- As the sole communicator of meaning — always pair with a label or
aria-label. - Sizes outside the four-token scale — no custom pixel values.
Do
- Always set
opszequal to the rendered size. - Use
{colors.ink-subtle}for secondary or decorative icons. - Use
{colors.muted}for placeholder or inactive icons. - Use
{colors.primary}for active or selected state icons.
Don't
- Don't use filled or rounded icon style variants.
- Don't use raw pixel sizes — always reference a size token.
- Don't place icons smaller than 12px (
sizeMap.sm) in the UI.
- Icons used alongside text:
aria-hidden="true"— text carries the meaning. - Icon-only interactive elements:
aria-labelrequired on the parent button/link. - Decorative icons in content:
aria-hidden="true". - Status icons in semantic context: pair with visible text label — never color or icon alone.
Keyboard
Icon is non-interactive and has no keyboard behavior. When used inside interactive elements (buttons, links), keyboard interaction is inherited from the parent.
Accessibility
Decorative icons (icon accompanies a visible text label):
- Add
aria-hidden="true"— screen readers will skip the icon and read the label.
Meaningful icons (icon communicates information without an adjacent label):
- Add
role="img"andaria-labeldescribing what the icon communicates. - Example:
<span role="img" aria-label="Warning">around a warning icon in a status column.
Spec
Values and token references resolved straight from the Zetta spec.
- size
- {icons.sizeMap.sm}
- opticalSize
- 12
- size
- {icons.sizeMap.md}
- opticalSize
- 16
- size
- {icons.sizeMap.lg}
- opticalSize
- 20
- size
- {icons.sizeMap.xl}
- opticalSize
- 24
- ariaHidden
- true
- role
- img