# Icon Box

A square with a border that holds one icon and shows the kind of an item.

```tsx
import { IconBox } from 'ferry-ui'
import { FolderKanban, KeyRound, Receipt } from 'lucide-react'

const RESULTS = [
  { icon: <FolderKanban />, name: 'Billing portal', kind: 'Project' },
  { icon: <Receipt />, name: 'INV-2041', kind: 'Invoice' },
  { icon: <KeyRound />, name: 'Analytics export', kind: 'API key' },
]

export default function IconBoxHero() {
  return (
    <ul className="w-full max-w-sm divide-y rounded-lg border bg-surface-100">
      {RESULTS.map((result) => (
        <li key={result.name} className="flex items-center gap-3 px-4 py-3">
          <IconBox size="sm">{result.icon}</IconBox>
          <span className="flex min-w-0 flex-col">
            <span className="truncate text-sm text-foreground">{result.name}</span>
            <span className="text-[13px] text-foreground-lighter">{result.kind}</span>
          </span>
        </li>
      ))}
    </ul>
  )
}
```

## Usage guidelines

- **The kind of an item.** Put an `IconBox` before a name in a list row, a card, a page title or a tile.
- **A person is not an icon.** For a person or an organization, use [Avatar](/docs/components/avatar).
- **A state alone is a status.** To show only a state, use [Status](/docs/components/status).
- **Not a button.** For an action, use a [Button](/docs/components/button) with an `icon` size.
- **Keep the neutral tone.** Use another tone only if the color of the box has a meaning.

## Anatomy

Import the component. It has one part. The child is the icon.

```tsx title="Anatomy"

<IconBox>
  <Icon />
</IconBox>
```

## Examples

### Sizes

The `size` prop sets the size of the box and of the icon. Select the size from the place of the box.

```tsx
import { IconBox, type IconBoxSize } from 'ferry-ui'
import { Receipt } from 'lucide-react'

const SIZES: IconBoxSize[] = ['xs', 'sm', 'md', 'lg', 'xl']

export default function IconBoxSizes() {
  return (
    <div className="flex flex-wrap items-end gap-6">
      {SIZES.map((size) => (
        <div key={size} className="flex flex-col items-center gap-2">
          <IconBox size={size}>
            <Receipt />
          </IconBox>
          <span className="text-[13px] text-foreground-lighter">{size}</span>
        </div>
      ))}
    </div>
  )
}
```

| Size | Box | Icon | Place |
| --- | --- | --- | --- |
| `xs` | 28px | 14px | Dense rows and compact choice cards. |
| `sm` | 32px | 16px | List rows and strips. |
| `md` (default) | 36px | 18px | Choice cards and card headers. |
| `lg` | 44px | 20px | Next to a page title or a dialog title. |
| `xl` | 56px, then 72px from the `md` breakpoint | 20px | The tiles of [Info Tile](/docs/components/info-tile). |

### Tones

The `tone` prop sets the color. The `primary` tone marks the current item or the selected item.

```tsx
import { IconBox, type IconBoxTone } from 'ferry-ui'
import { Webhook } from 'lucide-react'

const TONES: IconBoxTone[] = ['neutral', 'primary', 'success', 'warning', 'destructive', 'info']

export default function IconBoxTones() {
  return (
    <div className="flex flex-wrap items-end gap-6">
      {TONES.map((tone) => (
        <div key={tone} className="flex flex-col items-center gap-2">
          <IconBox tone={tone}>
            <Webhook />
          </IconBox>
          <span className="text-[13px] text-foreground-lighter">{tone}</span>
        </div>
      ))}
    </div>
  )
}
```

### State tones

The `success`, `warning`, `destructive` and `info` tones show a state. In this list, the color of each box shows the kind of event.

```tsx
import type { ReactNode } from 'react'
import { IconBox, type IconBoxTone } from 'ferry-ui'
import { AlertTriangle, CreditCard, Receipt, UserPlus } from 'lucide-react'

const EVENTS: { tone: IconBoxTone; icon: ReactNode; title: string; meta: string }[] = [
  { tone: 'success', icon: <Receipt />, title: 'Invoice INV-2041 paid', meta: '2 hours ago' },
  { tone: 'info', icon: <UserPlus />, title: 'Maya Chen is now a member', meta: '5 hours ago' },
  { tone: 'warning', icon: <CreditCard />, title: 'The card expires this month', meta: 'Yesterday' },
  { tone: 'destructive', icon: <AlertTriangle />, title: 'Webhook delivery failed', meta: 'Yesterday' },
]

export default function IconBoxStatus() {
  return (
    <ul className="flex w-full max-w-sm flex-col gap-4">
      {EVENTS.map((event) => (
        <li key={event.title} className="flex items-center gap-3">
          <IconBox size="sm" tone={event.tone}>
            {event.icon}
          </IconBox>
          <span className="flex min-w-0 flex-col">
            <span className="truncate text-[13px] text-foreground">{event.title}</span>
            <span className="text-[13px] text-foreground-lighter">{event.meta}</span>
          </span>
        </li>
      ))}
    </ul>
  )
}
```

### Shadow

The `elevated` prop adds the shadow of a card. It is on by default for the `lg` and `xl` sizes. With the `neutral` tone, it also makes the color of the icon one step stronger.

```tsx
import { IconBox } from 'ferry-ui'
import { FolderKanban } from 'lucide-react'

export default function IconBoxElevated() {
  return (
    <div className="flex flex-col gap-5">
      <div className="flex items-center gap-3">
        <IconBox size="lg">
          <FolderKanban />
        </IconBox>
        <span className="text-2xl font-medium text-foreground">Billing portal</span>
      </div>
      <div className="flex items-center gap-3 text-[13px] text-foreground-light">
        <IconBox>
          <FolderKanban />
        </IconBox>
        <IconBox elevated>
          <FolderKanban />
        </IconBox>
        The md size, flat and with the shadow
      </div>
    </div>
  )
}
```

### Accessible name

With no `label`, the box is decorative. This is correct when the name is next to the box. If the icon is the only sign, set `label`.

```tsx
import { IconBox } from 'ferry-ui'
import { Database, KeyRound } from 'lucide-react'

export default function IconBoxLabel() {
  return (
    <div className="flex flex-col gap-3 text-sm text-foreground">
      {/* The name is next to the box: the box is decorative. */}
      <div className="flex items-center gap-3">
        <IconBox size="sm">
          <Database />
        </IconBox>
        Database
      </div>
      {/* No type name is next to the box: the label gives it to screen readers. */}
      <div className="flex items-center gap-3">
        <IconBox size="sm" label="API key">
          <KeyRound />
        </IconBox>
        <span className="font-mono text-[13px]">sk_test_07be</span>
      </div>
    </div>
  )
}
```

### Icon size

The box sets the size of the icon. To use another icon size, add a class such as `[&_svg]:size-4` to the box. A size class on the icon has no effect.

```tsx
import { IconBox } from 'ferry-ui'
import { Receipt } from 'lucide-react'

export default function IconBoxIconSize() {
  return (
    <>
      <IconBox size="xs">
        <Receipt />
      </IconBox>
      <IconBox size="xs" className="[&_svg]:size-4">
        <Receipt />
      </IconBox>
    </>
  )
}
```

### Box look on another element

`iconBoxVariants` returns the classes of the box. Use it for an element that cannot be an `IconBox`. If the element is decorative, add `aria-hidden`.

```tsx
import { iconBoxVariants } from 'ferry-ui'
import { BellRing } from 'lucide-react'

export default function IconBoxVariants() {
  return (
    <div className="flex items-center gap-3">
      {/* A <div> with the look of the box. It is decorative: hide it from screen readers. */}
      <div aria-hidden="true" className={iconBoxVariants({ size: 'lg', tone: 'primary' })}>
        <BellRing />
      </div>
      <span className="flex flex-col">
        <span className="text-sm font-medium text-foreground">Alerts are on</span>
        <span className="text-[13px] text-foreground-lighter">You get an email for each failed payment.</span>
      </span>
    </div>
  )
}
```

## API reference

`IconBox` also accepts each attribute of the `<span>` element.

### IconBox

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `size` | `"sm" \| "md" \| "lg" \| "xl" \| "xs"` | `md` | Box and icon size, see `IconBoxSize`. Defaults to `md` (36px box, 18px icon). |
| `tone` | `"primary" \| "destructive" \| "warning" \| "success" \| "info" \| "neutral"` | `neutral` | Colour, see `IconBoxTone`. Defaults to `neutral`. |
| `elevated` | `boolean` | `false` | Adds the subtle card shadow (and, for `neutral`, a one-step stronger icon colour). Defaults to `true` for `lg` and `xl` (headline icons), `false` for the inline sizes. |
| `label` | `string` |  | Accessible name ("Invoice", "API key"). Without it the box is decorative (`aria-hidden`), which is right when a visible name sits next to it. Set it when the icon is the only cue of what it stands for. |
| `children` | `ReactNode` |  | The icon: a lucide icon or any `<svg>`, sized automatically for the box. To force another icon size, pass `className="[&_svg]:size-4"` on the box (a class on the `<svg>` itself is overridden). |

### iconBoxVariants

| Name | Type | Role |
| --- | --- | --- |
| `iconBoxVariants` | `({ size, tone, elevated }) => string` | Returns the classes of an `IconBox`. Each option has the same default as the prop. |
