Icon

An icon is a compact visual element that represents actions, objects, or concepts using simplified graphical symbols. The Icon component provides a headless wrapper around icon content with proper accessibility semantics, supporting both meaningful icons (with accessible labels) and decorative icons (hidden from assistive technology).

This component is useful in buttons, menus, toolbars, and navigation where visual cues help users quickly identify functions. The consumer provides the actual icon content (text characters, SVG, or any markup), while the component handles the semantic role and ARIA attributes.

Example

Show demo markup
<span class="icon" aria-hidden="true">⚙️</span>
Usage example
<script lang="ts">
    import Icon from "./Icon.svelte";
</script>

<Icon label="Close">x</Icon>
Show Svelte source
// In your Svelte component:
import Icon from "lily-design-system-svelte-headless/components/Icon/Icon.svelte";

<Icon>
  <!-- content -->
</Icon>

Usage

<!-- Meaningful icon with accessible label -->
<Icon label="Close">x</Icon>

<!-- Decorative icon hidden from screen readers -->
<Icon decorative>*</Icon>

<!-- Icon with custom attributes -->
<Icon label="Search" data-testid="search-icon">Q</Icon>

Props

  • label: string (optional) -- accessible name announced by screen readers for meaningful icons
  • decorative: boolean (default: false) -- when true, hides the icon from assistive technology and removes the img role
  • children: slot (required) -- the icon content (text, SVG, or other markup)
  • ...restProps: unknown -- additional attributes spread onto the <span> element

Keyboard Interactions

None -- this component is a passive container. Keyboard behavior depends on the parent interactive element (such as a button wrapping the icon).

ARIA

  • role="img" -- applied to meaningful icons so assistive technology treats the span as an image
  • aria-label="..." -- provides the accessible name for meaningful icons
  • aria-hidden="true" -- applied when decorative is true, hiding the icon from the accessibility tree

When to Use

  • Use to display a visual symbol representing an action, object, or concept in buttons, menus, toolbars, or navigation.
  • Use with decorative for icons that are purely visual and have adjacent text labels.
  • Avoid using a meaningful icon without a label prop; screen reader users would miss the icon's purpose.

When Not to Use

  • Do not use as the only target of a click — wrap interactive icons in IconButton or another button so they pick up button semantics and focus styles.
  • Do not use to render a single emoji character that already has a textual meaning — use Emoji for that.
  • Do not use as a substitute for a labeled image with caption — use Figure for content imagery.
  • Do not use without label for an icon that conveys meaning standalone, and do not pass a label to a decorative icon.

Headless

This headless component provides a <span> with role="img" and aria-label for meaningful icons, or aria-hidden="true" for decorative icons. The consumer provides all visual content (SVG, text characters, icon fonts) and styling.

Styles

The consumer provides all CSS styling. The component renders with a .icon class for targeting. No default styles are included — this is a fully headless component.

Testing

  • Verify the component renders a <span> element with class icon
  • Verify role="img"` -- applied to meaningful icons so assistive technology treats the span as an image
  • Verify aria-label="..."` -- provides the accessible name for meaningful icons
  • Verify pass-through attributes are applied

Advice

  • Designers: Ensure icons are visually clear at small sizes. Pair icons with text labels when the meaning may be ambiguous. Maintain consistent icon sizing across the interface.
  • Developers: Set decorative={true} when the icon is adjacent to a text label that conveys the same meaning. Always provide label for standalone icons.

Implementation Notes

  • Renders a <span> element as the icon wrapper
  • Uses role="img" for meaningful icons that convey information
  • Uses aria-hidden="true" for decorative icons that should be ignored by screen readers
  • When decorative is true, the role attribute is omitted entirely
  • Supports rest props spread for consumer customization
  • Consumer provides icon content via the children slot

Related components

  • icon-button — a button containing only an icon with a required accessible label
  • emoji — a single emoji character with accessible name
  • qr-code — a QR code image generated from text or URL data

References

  • WAI-ARIA img role: https://www.w3.org/TR/wai-aria-1.2/#img
  • WAI-ARIA Practices - Images: https://www.w3.org/WAI/tutorials/images/decorative/

Canonical contract

Generated from this component's canonical metadata — the machine-checked source the implementations are held to.

Metadata

  • Component: icon
  • PascalCase: Icon
  • Description: a container for displaying an icon
  • Status: stable — exercised in composed page flows under e2e and axe, beyond the per-component checks
  • HTML tag: <span>
  • CSS class: .icon
  • Interactive: no

ARIA

  • role="img" -- applied to meaningful icons so assistive technology treats the span as an image
  • aria-label="..." -- provides the accessible name for meaningful icons
  • aria-hidden="true" -- applied when decorative is true, hiding the icon from the accessibility tree

Keyboard

  • No keyboard interactions — this is a passive element

Props

  • label: string (optional) -- accessible name announced by screen readers for meaningful icons
  • decorative: boolean (default: false) -- when true, hides the icon from assistive technology and removes the img role
  • children: slot (required) -- the icon content (text, SVG, or other markup)
  • ...restProps: unknown -- additional attributes spread onto the <span> element