---
title: Icon
description: Render decorative or named icons from utility names or JSX.
package: moraine
version: 0.6.0
repository: https://github.com/subframe7536/moraine
---

# Icon

> Render decorative or named icons from utility names or JSX.

Use Icon for decoration or a named symbol alongside text. If the icon alone conveys meaning, give it an accessible name; otherwise it is hidden from assistive technology.

## Basic usage

```tsx
import { Icon } from 'moraine'

export function Example() {
  return <Icon name="i-lucide:check" />
}
```

## Anatomy

```text
Icon [component; slot=root; <div>]
```

## Usage

### Icon sources

Pass a supported icon name, JSX element, or component source. Use an icon as decoration only when adjacent text already names the meaning; otherwise provide an accessible label on the meaningful control or icon.

Use `<Icon name="icon-search" />` or `<Icon name="icon-success" />` for Moraine's semantic aliases. Editor completion suggests the built-in names, while `<Icon name="i-lucide-search" />` and `<Icon name="app-logo" />` remain valid. `Icon` applies string names as classes; your UnoCSS or Tailwind setup must generate the corresponding CSS for custom names.

Solid control-flow nodes such as `<Show>`, `<For>`, and `<Switch>` are not supported as the top-level `name` value. Keep control flow outside `Icon` so it owns whether the component is rendered:

```tsx
<Show when={visible()}>
  <Icon name={<StatusGlyph />} />
</Show>
```

```tsx
import { Icon } from 'moraine'

export function Sources() {
  return (
    <div class="text-xl flex gap-4 items-center">
      <Icon name="i-lucide:heart" />
      <Icon name="i-lucide:sparkles" class="text-amber-500" />
      <Icon name="i-lucide:check-circle-2" class="text-emerald-500" />
    </div>
  )
}
```

### Root styling

`Icon` has one rendered root. Use `class` and `style` for an individual icon, or configure `icon.base.root` in your Theme for shared defaults. `classes` and `styles` are not instance props because there are no child slots to target. `slotName` only changes the emitted `data-slot` value.

```tsx
import { Icon, MoraineProvider } from 'moraine'
import { defineTheme } from 'moraine/theme'

const theme = defineTheme({
  icon: { base: { root: 'text-emerald-600' } },
})

export function Slots() {
  return (
    <div class="w-full space-y-6">
      <div class="space-y-2">
        <p class="text-sm text-muted-foreground">Instance class and style</p>
        <Icon
          name="i-lucide:info"
          slotName="custom-icon"
          class="text-blue-500"
          style={{ width: '28px', height: '28px' }}
        />
      </div>
      <MoraineProvider theme={theme}>
        <div class="space-y-2">
          <p class="text-sm text-muted-foreground">Local Theme defaults</p>
          <Icon name="i-lucide:info" />
        </div>
      </MoraineProvider>
    </div>
  )
}
```

## Examples

### JSX icon

```tsx
import { Icon } from 'moraine'

export function IconAsJSX() {
  return (
    <div class="flex flex-wrap gap-6 items-center">
      <div class="flex flex-col gap-1 items-center">
        <Icon
          name={
            <svg
              viewBox="0 0 24 24"
              width="24"
              height="24"
              fill="none"
              stroke="currentColor"
              stroke-width="2"
            >
              <circle cx="12" cy="12" r="10" />
              <path d="M8 14s1.5 2 4 2 4-2 4-2" />
              <line x1="9" y1="9" x2="9.01" y2="9" />
              <line x1="15" y1="9" x2="15.01" y2="9" />
            </svg>
          }
        />
        <span class="text-[10px] text-muted-foreground">JSX element</span>
      </div>
      <div class="flex flex-col gap-1 items-center">
        <Icon name={() => <div class="i-lucide-zap size-6" />} />
        <span class="text-[10px] text-muted-foreground">Solid component</span>
      </div>
    </div>
  )
}
```

### Accessibility

```tsx
import { Icon } from 'moraine'

export function Accessibility() {
  return (
    <div class="flex gap-5 items-center">
      <p class="text-sm flex gap-2 items-center">
        <Icon name="i-lucide:circle-check" aria-hidden="true" class="text-success" />
        Changes saved
      </p>
      <Icon name="i-lucide:triangle-alert" aria-label="Warning" class="text-warning size-5" />
    </div>
  )
}
```

## Props

Props for the Icon component.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| name* | Name | — | Icon source. Strings should be Uno icon classes such as `i-lucide-search`<br>or app-config aliases such as `icon-search`.<br>Non-string values can be JSX nodes or Solid components.<br>Wrap Icon in Solid control flow instead of passing a control-flow node as `name`. |
| size | string \| number \| undefined | — | Explicit icon size override. Omit to inherit the surrounding font size.<br>Numbers are interpreted as px. |
| slotName | string \| undefined | 'icon' | Data slot for styling. |
| class | SlotClassValue \| undefined | — | Class applied to the component root or trigger element. |
| classes | Classes \| undefined | — | Family slot class defaults for this instance. |
| style | SlotStyleValue \| undefined | — | Style applied to the component root or trigger element. |
| styles | Styles \| undefined | — | Family slot style defaults for this instance. |
