# Item

The generic dense row — a media gutter, a content column, and the actions against the trailing edge.

```slint
import { Item, ItemGroup, ItemMedia, ItemMediaVariant, ItemContent, ItemTitle, ItemDescription, ItemActions } from "@glint/components/item.slint";
import { Button, ButtonVariant, ButtonSize } from "@glint/components/button.slint";
import { Tokens } from "@glint/theme/tokens.slint";
import { IconSet } from "@lucide";

export component Demo inherits Window {
    width: 640px;
    height: 300px;
    background: Tokens.color-background;

    VerticalLayout {
        padding: 24px;
        alignment: start;

        ItemGroup {
            label: "Integrations";
            item-count: 3;

            Item {
                index: 0;
                ItemMedia {
                    variant: ItemMediaVariant.icon;
                    icon: IconSet.GitBranch;
                }
                ItemContent {
                    ItemTitle { text: "Source control"; }
                    ItemDescription { text: "Pull requests and issues."; }
                }
                ItemActions {
                    Button {
                        variant: ButtonVariant.outline;
                        size: ButtonSize.sm;
                        text: "Configure";
                    }
                }
            }

            Item {
                index: 1;
                separator-before: true;
                ItemMedia {
                    variant: ItemMediaVariant.icon;
                    icon: IconSet.MessageSquare;
                }
                ItemContent {
                    ItemTitle { text: "Chat"; }
                    ItemDescription { text: "Deploy notices in the releases channel."; }
                }
                ItemActions {
                    Button {
                        variant: ButtonVariant.outline;
                        size: ButtonSize.sm;
                        text: "Configure";
                    }
                }
            }

            Item {
                index: 2;
                separator-before: true;
                ItemMedia {
                    variant: ItemMediaVariant.icon;
                    icon: IconSet.Palette;
                }
                ItemContent {
                    ItemTitle { text: "Design"; }
                    ItemDescription { text: "Design files linked to tickets."; }
                }
                ItemActions {
                    Button {
                        variant: ButtonVariant.outline;
                        size: ButtonSize.sm;
                        text: "Connect";
                    }
                }
            }
        }
    }
}
```

## Usage

```slint
import { Item, ItemMedia, ItemMediaVariant, ItemContent, ItemTitle, ItemDescription, ItemActions } from "@glint/components/item.slint";
import { Button, ButtonVariant, ButtonSize } from "@glint/components/button.slint";
import { Tokens } from "@glint/theme/tokens.slint";
import { IconSet } from "@lucide";

export component AppWindow inherits Window {
    width: 520px;
    height: 160px;
    background: Tokens.color-background;

    VerticalLayout {
        padding: 24px;
        alignment: start;

        Item {
            ItemMedia {
                variant: ItemMediaVariant.icon;
                icon: IconSet.FileText;
            }
            ItemContent {
                ItemTitle { text: "Q3 report.pdf"; }
                ItemDescription { text: "1.4 MB · updated yesterday"; }
            }
            ItemActions {
                Button {
                    variant: ButtonVariant.ghost;
                    size: ButtonSize.sm;
                    text: "Download";
                }
            }
        }
    }
}
```

**The row’s one slot is its band**: media, content, actions, laid out left to right. A component gets exactly one `@children`, written once and never inside an `if` (ADR-0027), and the band is what `Item` spends it on — so a plain row costs no wrapper at all.

That is also why `header` and `footer` are strings the row draws above and below the band rather than slots of their own. `ItemHeader` and `ItemFooter` stay exported as the type styles those lines use, for a stack built by hand.

| Part                            | What it is                                                                                      |
| ------------------------------- | ----------------------------------------------------------------------------------------------- |
| `ItemMedia`                     | The leading square: a slot, a lucide icon on a tile, or an image                                |
| `ItemContent`                   | The middle cell. It takes the slack, so a long description never pushes the actions off the row |
| `ItemTitle` / `ItemDescription` | The row’s name and the line under it. The title elides rather than widening its container       |
| `ItemActions`                   | The trailing cluster, centred against the band                                                  |
| `ItemGroup`                     | The stack a set of rows sits in, and the list assistive technology walks                        |

Item is [Card](/docs/components/card) one density down, and it is named to read that way: `ItemTitle` and `ItemDescription` stand to `ItemContent` exactly as `CardTitle` and `CardDescription` stand to `CardHeader`. What it adds is the row’s own axis and the opt-in whole-row trigger a list needs.

## Examples

### Variants

`variant` is the row’s surface treatment. `ItemVariant.default` draws nothing at all — a row is a band of a list, and a list of bordered rows is a list of cards. `outline` gives it a hairline edge, `muted` a fill.

```slint
import { Item, ItemVariant, ItemMedia, ItemMediaVariant, ItemContent, ItemTitle, ItemDescription } from "@glint/components/item.slint";
import { Tokens } from "@glint/theme/tokens.slint";
import { IconSet } from "@lucide";

export component Demo inherits Window {
    width: 620px;
    height: 320px;
    background: Tokens.color-background;

    VerticalLayout {
        padding: 24px;
        spacing: 12px;
        alignment: start;

        Item {
            variant: ItemVariant.default;
            ItemMedia { variant: ItemMediaVariant.icon; icon: IconSet.Circle; }
            ItemContent {
                ItemTitle { text: "default"; }
                ItemDescription { text: "No surface — the row of a plain list."; }
            }
        }

        Item {
            variant: ItemVariant.outline;
            ItemMedia { variant: ItemMediaVariant.icon; icon: IconSet.Square; }
            ItemContent {
                ItemTitle { text: "outline"; }
                ItemDescription { text: "A hairline edge and a wider radius."; }
            }
        }

        Item {
            variant: ItemVariant.muted;
            ItemMedia { variant: ItemMediaVariant.icon; icon: IconSet.Triangle; }
            ItemContent {
                ItemTitle { text: "muted"; }
                ItemDescription { text: "A filled band, for a row that is set apart."; }
            }
        }
    }
}
```

### Density

`size` packs the row: the padding, the gap between the band’s cells and the media square all follow it. The square’s side is *given* rather than read — a slotted component cannot see its host — so a row that changed its density hands the number on with `size: row.media-size`. `Sidebar`’s regions are told about their collapse the same way.

```slint
import { Item, ItemSize, ItemVariant, ItemMedia, ItemMediaVariant, ItemContent, ItemTitle, ItemDescription } from "@glint/components/item.slint";
import { Tokens } from "@glint/theme/tokens.slint";
import { IconSet } from "@lucide";

export component Demo inherits Window {
    width: 620px;
    height: 320px;
    background: Tokens.color-background;

    VerticalLayout {
        padding: 24px;
        spacing: 12px;
        alignment: start;

        big := Item {
            size: ItemSize.default;
            variant: ItemVariant.outline;
            ItemMedia {
                variant: ItemMediaVariant.icon;
                icon: IconSet.Folder;
                size: big.media-size;
            }
            ItemContent {
                ItemTitle { text: "default"; }
                ItemDescription { text: "A 40px square and roomy padding."; }
            }
        }

        small := Item {
            size: ItemSize.sm;
            variant: ItemVariant.outline;
            ItemMedia {
                variant: ItemMediaVariant.icon;
                icon: IconSet.Folder;
                size: small.media-size;
            }
            ItemContent {
                ItemTitle { text: "sm"; }
                ItemDescription { text: "32px, for a list that has to fit."; }
            }
        }

        tiny := Item {
            size: ItemSize.xs;
            variant: ItemVariant.outline;
            ItemMedia {
                variant: ItemMediaVariant.icon;
                icon: IconSet.Folder;
                size: tiny.media-size;
            }
            ItemContent {
                ItemTitle { text: "xs"; }
                ItemDescription { text: "24px — the densest row."; }
            }
        }
    }
}
```

### The media gutter

`ItemMedia.variant` is what the square holds. `default` is the slot: an [Avatar](/docs/components/avatar) goes straight in, since Glint already has one and a media variant for it would be a second spelling of the same thing. `icon` draws a lucide glyph on a backing tile, and `image` draws a picture from `source`, clipped to the square’s radius.

```slint
import { Item, ItemVariant, ItemMedia, ItemMediaVariant, ItemContent, ItemTitle, ItemDescription } from "@glint/components/item.slint";
import { Avatar } from "@glint/components/avatar.slint";
import { Tokens } from "@glint/theme/tokens.slint";
import { IconSet } from "@lucide";

export component Demo inherits Window {
    width: 620px;
    height: 260px;
    background: Tokens.color-background;

    VerticalLayout {
        padding: 24px;
        spacing: 12px;
        alignment: start;

        Item {
            variant: ItemVariant.outline;
            ItemMedia {
                variant: ItemMediaVariant.default;
                Avatar { fallback: "AL"; alt: "Ada Lovelace"; size: 40px; }
            }
            ItemContent {
                ItemTitle { text: "Ada Lovelace"; }
                ItemDescription { text: "Slotted — the gutter holds an Avatar."; }
            }
        }

        Item {
            variant: ItemVariant.outline;
            ItemMedia {
                variant: ItemMediaVariant.icon;
                icon: IconSet.Paperclip;
            }
            ItemContent {
                ItemTitle { text: "contract.pdf"; }
                ItemDescription { text: "A lucide icon on a backing tile."; }
            }
        }
    }
}
```

### A row that is a trigger

A row is a surface, not an affordance, until it is asked to be one — Card’s rule. `interactive` is the ask: it gives the row a pointer cursor, a press dip, a tab stop and a `clicked` callback. `disabled` is the refusal — the row dims, keeps its place and swallows every activation path; `selected` is the current row of a list, which wears the highlight the pointer raises.

The controls in `ItemActions` sit above the row’s own trigger, so they keep their presses and their tab stops. A keyboard user reaches the row first, then its actions.

```slint
import { Item, ItemVariant, ItemMedia, ItemMediaVariant, ItemContent, ItemTitle, ItemDescription, ItemActions } from "@glint/components/item.slint";
import { Button, ButtonVariant, ButtonSize } from "@glint/components/button.slint";
import { Tokens } from "@glint/theme/tokens.slint";
import { IconSet } from "@lucide";

export component Demo inherits Window {
    width: 640px;
    height: 320px;
    background: Tokens.color-background;

    property <string> last: "Nothing pressed yet.";

    VerticalLayout {
        padding: 24px;
        spacing: 10px;
        alignment: start;

        Item {
            variant: ItemVariant.outline;
            interactive: true;
            label: "Open the September invoice";
            description: "Paid on 3 October";
            ItemMedia { variant: ItemMediaVariant.icon; icon: IconSet.Receipt; }
            ItemContent {
                ItemTitle { text: "September invoice"; }
                ItemDescription { text: "Paid on 3 October"; }
            }
            ItemActions {
                Button {
                    variant: ButtonVariant.ghost;
                    size: ButtonSize.sm;
                    text: "Download";
                    clicked => { root.last = "The action was pressed."; }
                }
            }
            clicked => { root.last = "The row was pressed."; }
        }

        Item {
            variant: ItemVariant.outline;
            interactive: true;
            selected: true;
            label: "Open the October invoice";
            ItemMedia { variant: ItemMediaVariant.icon; icon: IconSet.Receipt; }
            ItemContent {
                ItemTitle { text: "October invoice"; }
                ItemDescription { text: "The selected row."; }
            }
            clicked => { root.last = "The selected row was pressed."; }
        }

        Item {
            variant: ItemVariant.outline;
            interactive: true;
            disabled: true;
            label: "November invoice, not yet issued";
            ItemMedia { variant: ItemMediaVariant.icon; icon: IconSet.Receipt; }
            ItemContent {
                ItemTitle { text: "November invoice"; }
                ItemDescription { text: "Refuses every activation path."; }
            }
            clicked => { root.last = "This never fires."; }
        }

        Text {
            text: root.last;
            color: Tokens.color-muted-foreground;
            font-size: Tokens.typography-body-sm-size;
        }
    }
}
```

**An interactive row is named at the call site.** Slint gives a host no way to read the text it was slotted, so the row cannot borrow the `ItemTitle` inside it: `label` and `description` are what assistive technology announces, and the slotted title goes on speaking for itself.

### A list of rows

`ItemGroup` is the list a set of rows sits in. It counts what it holds from `item-count` rather than from its own children — Slint lets a host neither address nor count what was slotted into it, which is the same reason each row is told its own `index`.

A row with an `index` is a list item carrying that position and its selected state; an interactive row without one, standing alone, is announced as a button instead. Membership of a list is not an interaction, so a plain row inside a group still has a position — and a row that is neither interactive nor indexed has no node of its own at all.

`separator-before` opens a row with the group hairline. It belongs to the row below it rather than being an entry of its own, so every child of the group stays a real, indexable row (ADR-0012).

```slint
import { Item, ItemGroup, ItemMedia, ItemMediaVariant, ItemContent, ItemTitle, ItemDescription } from "@glint/components/item.slint";
import { Tokens } from "@glint/theme/tokens.slint";
import { IconSet } from "@lucide";

export component Demo inherits Window {
    width: 620px;
    height: 300px;
    background: Tokens.color-background;

    in-out property <int> current: 1;

    VerticalLayout {
        padding: 24px;
        alignment: start;

        ItemGroup {
            label: "Recent files";
            item-count: 3;

            Item {
                index: 0;
                interactive: true;
                selected: root.current == 0;
                label: "Roadmap.md";
                ItemMedia { variant: ItemMediaVariant.icon; icon: IconSet.FileText; }
                ItemContent {
                    ItemTitle { text: "Roadmap.md"; }
                    ItemDescription { text: "Edited 2 hours ago"; }
                }
                clicked => { root.current = 0; }
            }

            Item {
                index: 1;
                separator-before: true;
                interactive: true;
                selected: root.current == 1;
                label: "Budget.xlsx";
                ItemMedia { variant: ItemMediaVariant.icon; icon: IconSet.Sheet; }
                ItemContent {
                    ItemTitle { text: "Budget.xlsx"; }
                    ItemDescription { text: "Edited yesterday"; }
                }
                clicked => { root.current = 1; }
            }

            Item {
                index: 2;
                separator-before: true;
                interactive: true;
                selected: root.current == 2;
                label: "Launch deck";
                ItemMedia { variant: ItemMediaVariant.icon; icon: IconSet.Presentation; }
                ItemContent {
                    ItemTitle { text: "Launch deck"; }
                    ItemDescription { text: "Edited last week"; }
                }
                clicked => { root.current = 2; }
            }
        }
    }
}
```

### A line above and below the band

`header` and `footer` are the row’s first line and its last — a group name over the band, a timestamp under it. Empty draws nothing and costs nothing.

```slint
import { Item, ItemVariant, ItemMedia, ItemMediaVariant, ItemContent, ItemTitle, ItemDescription, ItemActions } from "@glint/components/item.slint";
import { Badge, BadgeVariant } from "@glint/components/badge.slint";
import { Tokens } from "@glint/theme/tokens.slint";
import { IconSet } from "@lucide";

export component Demo inherits Window {
    width: 620px;
    height: 240px;
    background: Tokens.color-background;

    VerticalLayout {
        padding: 24px;
        alignment: start;

        Item {
            variant: ItemVariant.outline;
            header: "Deployment · production";
            footer: "Started 4 minutes ago by ada";
            ItemMedia { variant: ItemMediaVariant.icon; icon: IconSet.Rocket; }
            ItemContent {
                ItemTitle { text: "web-frontend @ 78c1f9f"; }
                ItemDescription { text: "Build 214 · eu-central-1"; }
            }
            ItemActions {
                Badge { variant: BadgeVariant.secondary; text: "Running"; }
            }
        }
    }
}
```

### A stack built by hand

The four type styles and the hairline are exported so a column that never becomes an `ItemGroup` can still be built out of the family’s own parts. `ItemSeparator` *is* `Separator` under the item family’s name, so it takes the same `orientation` — a vertical rule between two rows standing side by side — and the same optional caption.

```slint
import { Item, ItemVariant, ItemHeader, ItemFooter, ItemTitle, ItemDescription, ItemSeparator, ItemContent, ItemMedia, ItemMediaVariant } from "@glint/components/item.slint";
import { SeparatorOrientation } from "@glint/components/separator.slint";
import { Tokens } from "@glint/theme/tokens.slint";
import { IconSet } from "@lucide";

export component Demo inherits Window {
    width: 660px;
    height: 300px;
    background: Tokens.color-background;

    VerticalLayout {
        padding: 24px;
        spacing: 12px;
        alignment: start;

        // The type styles, stacked by hand rather than by a row.
        VerticalLayout {
            spacing: 2px;
            ItemHeader { text: "PINNED"; }
            ItemTitle { text: "Incident 41 — elevated 5xx on checkout"; }
            ItemDescription { text: "Mitigated. Post-mortem due Friday."; }
            ItemFooter { text: "Opened 2 days ago"; }
        }

        ItemSeparator { text: "Everything else"; }

        // Two rows side by side, parted by an upright hairline.
        HorizontalLayout {
            spacing: 12px;

            Item {
                variant: ItemVariant.outline;
                horizontal-stretch: 1;
                ItemMedia { variant: ItemMediaVariant.icon; icon: IconSet.CircleCheck; }
                ItemContent {
                    ItemTitle { text: "Checkout"; }
                    ItemDescription { text: "Healthy"; }
                }
            }

            ItemSeparator { orientation: SeparatorOrientation.vertical; }

            Item {
                variant: ItemVariant.outline;
                horizontal-stretch: 1;
                ItemMedia { variant: ItemMediaVariant.icon; icon: IconSet.TriangleAlert; }
                ItemContent {
                    ItemTitle { text: "Search"; }
                    ItemDescription { text: "Degraded"; }
                }
            }
        }
    }
}
```

## API Reference

### Properties

| Property           | Type             | Default               | Description                                                                                                                                                                                                                                           |
| ------------------ | ---------------- | --------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `variant`          | `in ItemVariant` | `ItemVariant.default` | Surface treatment — none, a hairline edge, or a muted fill.                                                                                                                                                                                           |
| `size`             | `in ItemSize`    | `ItemSize.default`    | Density: the media square, the padding and the band's gap together.                                                                                                                                                                                   |
| `header`           | `in string`      | no default            | A line above the band; empty draws nothing.                                                                                                                                                                                                           |
| `footer`           | `in string`      | no default            | A line below it; empty draws nothing.                                                                                                                                                                                                                 |
| `interactive`      | `in bool`        | `false`               | Makes the whole row activatable. Off by default: a surface is not an affordance until it is asked to be one.                                                                                                                                          |
| `disabled`         | `in bool`        | `false`               | A row that refuses: it dims, swallows every activation, keeps its place and reports itself unavailable (CONTEXT.md "Refusal"). Spelled the way every other Glint control that is not a text entry spells it.                                          |
| `selected`         | `in bool`        | `false`               | The current row of a list — it wears the same highlight the pointer raises, and says so when it is announced.                                                                                                                                         |
| `separator-before` | `in bool`        | `false`               | Opens the row with the group hairline. It belongs to the row below it rather than being an entry of its own, so every child of a group stays a real, indexable row (ADR-0012).                                                                        |
| `index`            | `in int`         | `-1`                  | Where the row stands in its list. `-1` — the default — is a row standing alone, which is announced as a button; anything else is a list item carrying this index. A slotted child cannot read its host, so the position is given rather than counted. |
| `label`            | `in string`      | no default            | What assistive technology announces the row as, and the line under it. An interactive row needs the name: the title it was slotted is out of reach.                                                                                                   |
| `description`      | `in string`      | no default            |                                                                                                                                                                                                                                                       |
| `media-size`       | `out length`     | no default            | The media square's side at this density, published because the call site is what hands it to the `ItemMedia` it slotted.                                                                                                                              |
| `surface-color`    | `out color`      | no default            | The surface behind the row, and its edge — published so a slotted action can match the row it sits on.                                                                                                                                                |
| `edge-color`       | `out color`      | no default            |                                                                                                                                                                                                                                                       |

### Callbacks

| Callback    | Description                                                                                                              |
| ----------- | ------------------------------------------------------------------------------------------------------------------------ |
| `clicked()` | Fired when a live, interactive row is activated by pointer, by Enter or Space, or through the accessible default action. |

### Enums

| Enum          | Values                        |
| ------------- | ----------------------------- |
| `ItemVariant` | `default`, `outline`, `muted` |
| `ItemSize`    | `default`, `sm`, `xs`         |

### ItemGroup

The list a set of rows sits in.

### Properties

| Property     | Type        | Default    | Description                                                                                                            |
| ------------ | ----------- | ---------- | ---------------------------------------------------------------------------------------------------------------------- |
| `label`      | `in string` | no default | What the list is called. A page with two lists needs them told apart.                                                  |
| `item-count` | `in int`    | no default | How many rows it holds, for assistive technology's "3 of 12".                                                          |
| `spacing`    | `in length` | `0px`      | Gap between two rows. Rows touch by default, which is what makes `separator-before` read as one hairline between them. |

### ItemMedia

The leading square.

### Properties

| Property  | Type                  | Default                    | Description                                                                        |
| --------- | --------------------- | -------------------------- | ---------------------------------------------------------------------------------- |
| `variant` | `in ItemMediaVariant` | `ItemMediaVariant.default` | What the gutter holds.                                                             |
| `icon`    | `in LucideIcon`       | no default                 | The lucide icon drawn by the `icon` variant.                                       |
| `source`  | `in image`            | no default                 | The picture drawn by the `image` variant.                                          |
| `size`    | `in length`           | `40px`                     | The square's side. `Item.media-size` is the number that follows the row's density. |

### Enums

| Enum               | Values                     |
| ------------------ | -------------------------- |
| `ItemMediaVariant` | `default`, `icon`, `image` |

### ItemContent

The band’s middle cell: the title, the description, and anything else the row stacks under them.

`ItemContent` publishes no properties, callbacks or functions of its own. What a call site can set on it is the Slint `VerticalLayout` it inherits.

### ItemTitle

The row’s name. It elides rather than growing its container, and takes a zero minimum width so a narrow list does not overflow.

`ItemTitle` publishes no properties, callbacks or functions of its own. What a call site can set on it is the Slint `Text` it inherits.

### ItemDescription

The line under the title, at the muted tone and wrapping by word.

`ItemDescription` publishes no properties, callbacks or functions of its own. What a call site can set on it is the Slint `Text` it inherits.

### ItemActions

The trailing cluster. Centred against the band rather than stretched down it, so a two-line row does not stand its buttons at the top.

`ItemActions` publishes no properties, callbacks or functions of its own. What a call site can set on it is the Slint `VerticalLayout` it inherits.

### ItemHeader

The row’s first line, as a type style — for a stack built by hand.

`ItemHeader` publishes no properties, callbacks or functions of its own. What a call site can set on it is the Slint `Text` it inherits.

### ItemFooter

The row’s last line, as a type style.

`ItemFooter` publishes no properties, callbacks or functions of its own. What a call site can set on it is the Slint `Text` it inherits.

### ItemSeparator

The hairline between two rows, under the item family’s own name. It is [Separator](/docs/components/separator), so it carries that component’s `orientation` and caption.

### Properties

| Property      | Type                      | Default                           | Description                                                                                                  |
| ------------- | ------------------------- | --------------------------------- | ------------------------------------------------------------------------------------------------------------ |
| `orientation` | `in SeparatorOrientation` | `SeparatorOrientation.horizontal` | `horizontal` (default) or `vertical`. Ignored while there is a caption, which is horizontal by construction. |
| `text`        | `in string`               | no default                        | Caption centred in the rule; empty (the default) is the bare hairline.                                       |

### Enums

| Enum                   | Values                   |
| ---------------------- | ------------------------ |
| `SeparatorOrientation` | `horizontal`, `vertical` |

## Accessibility

- **A plain row has no node.** An `Item` that is neither interactive nor indexed declares no role: what a screen reader reads is what you put inside it. That is Card’s rule at one lower density.
- **Two nodes, one of which exists.** `accessible-role` must be a constant in Slint, so a row standing alone (`index: -1`, the default) is announced as a `button` and a row inside a list as a `list-item` carrying its index and its selected state. Only the button half is gated on `interactive` — membership of a list is not an interaction.
- **The name is the call site’s.** A host cannot read the text it was slotted, so `label` and `description` are what the row announces. The slotted `ItemTitle` keeps speaking for itself; there is nothing here that could silence it.
- **One activation path.** The pointer, Enter and Space, and the accessible default action all land in one place, and that is where `disabled` refuses. The refusal is never the `TouchArea`’s `enabled` — a disabled TouchArea is transparent to the pointer, and the press would land on whatever sits under the row.
- **A row nobody can activate takes no tab stop.** An affordance that does nothing is not one. An interactive row takes the stop *before* the controls it holds, so a keyboard user reaches the row and then its actions.
- **`ItemGroup` is the list.** It carries `accessible-role: list` with its `label` and `item-count`, which is what gives assistive technology its “3 of 12”. The count is a property because a host can neither address nor count what was slotted into it.
- **The media gutter is decoration.** An `ItemMedia` holding an icon adds no node of its own; a slotted `Avatar` announces itself through its own `alt`.
