Docs

This page isn't translated yet

next

Icon

Decorative and linked icons with optional background.

On this page

Icon sizes and colors an icon from the icon tokens, with an optional filled background. It wraps whatever you put inside, an inline SVG, an <img> or a glyph, and Icon.a turns it into a link with a hover state.

Icon
import { Icon } from '@systhemaui/next'

export default function Demo() {
  return (
    <div className="flex flex-wrap items-center gap-6">
      <Icon aria-hidden="true">
        <svg viewBox="0 -960 960 960" fill="currentColor" focusable="false">
          <path d="m422-232 207-248H469l29-227-185 267h139l-30 208ZM320-80l40-280H160l360-520h80l-40 320h240L400-80h-80Z" />
        </svg>
      </Icon>
      <Icon hasBackground aria-hidden="true">
        <svg viewBox="0 -960 960 960" fill="currentColor" focusable="false">
          <path d="m422-232 207-248H469l29-227-185 267h139l-30 208ZM320-80l40-280H160l360-520h80l-40 320h240L400-80h-80Z" />
        </svg>
      </Icon>
    </div>
  )
}

ImportLink to this section

import { Icon } from '@systhemaui/next'

In a React app without Next.js, import it from @systhemaui/react. There Icon.a renders a plain anchor.

Variants and tagsLink to this section

Icon renders a <span> by default. It has one shorthand, Icon.a, which renders a link (the same shape as Button.a, Chip.a and MediaWrapper.a). Use as for any other element, for example as="div" or as="svg".

hasBackground adds the icon-has-background class: a filled, bordered tile sized by --icon-has-background-size, with the icon inside at --icon-has-background-icon-size.

Plain, with background, linked
import { Icon } from '@systhemaui/next'

const mail =
  'M160-160q-33 0-56.5-23.5T80-240v-480q0-33 23.5-56.5T160-800h640q33 0 56.5 23.5T880-720v480q0 33-23.5 56.5T800-160H160Zm320-280L160-640v400h640v-400L480-440Zm0-80 320-200H160l320 200Z'

export default function Demo() {
  return (
    <div className="flex flex-wrap items-center gap-6">
      <Icon aria-hidden="true">
        <svg viewBox="0 -960 960 960" fill="currentColor" focusable="false">
          <path d={mail} />
        </svg>
      </Icon>
      <Icon hasBackground aria-hidden="true">
        <svg viewBox="0 -960 960 960" fill="currentColor" focusable="false">
          <path d={mail} />
        </svg>
      </Icon>
      <Icon.a href="#variants-and-tags" aria-label="Email us">
        <svg viewBox="0 -960 960 960" fill="currentColor" aria-hidden="true" focusable="false">
          <path d={mail} />
        </svg>
      </Icon.a>
      <Icon.a href="#variants-and-tags" hasBackground aria-label="Email us">
        <svg viewBox="0 -960 960 960" fill="currentColor" aria-hidden="true" focusable="false">
          <path d={mail} />
        </svg>
      </Icon.a>
    </div>
  )
}

Hover the two linked icons: as an <a>, an icon takes the hover colors from the colorSystem.icon.hover.* tokens. Icons that are not links keep a flat look.

ExamplesLink to this section

The SVG as the iconLink to this section

With as="svg" the icon element is the SVG itself, which saves a wrapper.

SVG as the icon
import { Icon } from '@systhemaui/next'

export default function Demo() {
  return (
    <div className="flex items-center gap-6">
      <Icon as="svg" viewBox="0 -960 960 960" fill="currentColor" aria-hidden="true" focusable="false">
        <path d="m233-120 65-281L80-590l288-25 112-265 112 265 288 25-218 189 65 281-247-149-247 149Z" />
      </Icon>
    </div>
  )
}

An image as the iconLink to this section

Children are sized to fill the icon with object-fit: contain, so a raster or SVG file works as well as inline markup.

Image icon
NorthwindNorthwind
import { Icon } from '@systhemaui/next'

export default function Demo() {
  return (
    <div className="flex items-center gap-6">
      <Icon>
        <img src="/demo-assets/logo-mark.svg" alt="Northwind" />
      </Icon>
      <Icon hasBackground>
        <img src="/demo-assets/logo-mark.svg" alt="Northwind" />
      </Icon>
    </div>
  )
}

SVG markup from the CMSLink to this section

The Icon block stores an icon as an SVG string. Render it with dangerouslySetInnerHTML, and only for markup you trust.

import { Icon } from '@systhemaui/next'

export function CmsIcon({ svg }: { svg: string }) {
  return <Icon hasBackground aria-hidden="true" dangerouslySetInnerHTML={{ __html: svg }} />
}

Overriding size and colorLink to this section

Every icon rule is wrapped in :where(), so it has no specificity and any utility class wins without !.

Utility overrides
import { Icon } from '@systhemaui/next'

const bolt =
  'm422-232 207-248H469l29-227-185 267h139l-30 208ZM320-80l40-280H160l360-520h80l-40 320h240L400-80h-80Z'

export default function Demo() {
  return (
    <div className="flex items-center gap-6">
      <Icon className="size-6" aria-hidden="true">
        <svg viewBox="0 -960 960 960" fill="currentColor" focusable="false">
          <path d={bolt} />
        </svg>
      </Icon>
      <Icon className="size-12 text-amber-500" aria-hidden="true">
        <svg viewBox="0 -960 960 960" fill="currentColor" focusable="false">
          <path d={bolt} />
        </svg>
      </Icon>
    </div>
  )
}

PropsLink to this section

Icon takes the props of the element it renders. Icon.a takes link props.

PropTypeDefaultDescription
asElementType'span'
classNamestring-
hasBackgroundbooleanfalse
childrenReactNode-
…and all <span> attributes

HTML and CSSLink to this section

Without React, add icon (and icon-has-background) to any element around the SVG:

<span class="icon" aria-hidden="true">
  <svg viewBox="0 -960 960 960" fill="currentColor" focusable="false"><path d="…" /></svg>
</span>

<a class="icon icon-has-background" href="mailto:hello@example.com" aria-label="Email us">
  <svg viewBox="0 -960 960 960" fill="currentColor" aria-hidden="true" focusable="false">
    <path d="…" />
  </svg>
</a>

The hover rules match .icon:is(a):hover, so only a link icon changes color on hover.

ClassStyles
icon:where(.icon) { display: inline-block; width: var(--icon-size); height: var(--icon-size); font-size: var(--icon-size); line-height: var(--icon-size); color: var(--color-icon-normal-color); }:where(.icon) * { display: inline-block; width: 100%; height: 100%; object-fit: contain; object-position: center; color: currentColor; }+ 4 more rules
icon-has-background:where(.icon.icon-has-background) { --icon-shadow-color: var(--color-icon-normal-has-background-shadow); --icon-shadow-x: var(--icon-has-background-shadow-x); --icon-shadow-y: var(--icon-has-background-shadow-y); --icon-shadow-blur: var(--icon-has-background-shadow-blur); --icon-shadow-spread: var(--icon-has-background-shadow-spread); display: inline-grid; place-items: center; border-style: solid; width: var(--icon-has-background-size); height: var(--icon-has-background-size…+ 2 more rules

Next.jsLink to this section

The @systhemaui/next Icon keeps the React API (Icon, Icon.a and as) but renders Icon.a through the Next-aware LinkHelper instead of a plain <a>. Internal links prefetch and navigate on the client, and hash links smooth-scroll, as with Button.a and Card.link. The social icons of the simple footer use Icon.a, so they get the same behavior.

import { Icon } from '@systhemaui/next'

export function SettingsShortcut() {
  return (
    <Icon.a href="#cookie-settings" hasBackground aria-label="Cookie settings">
      <svg viewBox="0 -960 960 960" fill="currentColor" aria-hidden="true" focusable="false">
        <path d="M480-80q-83 0-156-31.5T197-197q-54-54-85.5-127T80-480q0-83 31.5-156T197-763q54-54 127-85.5T480-880q83 0 156 31.5T763-763q54 54 85.5 127T880-480q0 83-31.5 156T763-197q-54 54-127 85.5T480-80Z" />
      </svg>
    </Icon.a>
  )
}

AccessibilityLink to this section

  • Icon adds no ARIA of its own. Mark a decorative icon aria-hidden="true", and give a real <svg> focusable="false".
  • A linked Icon.a has no visible text, so it needs an aria-label that names the destination. The Icon block derives one from the link when the editor leaves it empty.
  • Give an icon one name only: an aria-label on the link, or alt text on an <img> inside it, not both.

See Icons and icon-only controls.