---
title: Bento Grid
description: An asymmetric feature showcase — cells claim different amounts of space.
category: Layout
related:
  - /components/feature-grid
  - /components/grid
  - /components/image
  - /components/aspect-ratio
visibility: guest
draft: false
---

# Bento Grid

A bento grid is a feature showcase where **cells claim different amounts of
space**, so the eye gets a hierarchy instead of a uniform matrix. Use it when your
features are genuinely unequal — one headline capability, a few supporting ones.

When every feature carries the same weight, [feature grid](/components/feature-grid)
is still the right answer. A bento that says everything is important says nothing.

:::preview{title="Bento grid with mixed spans"}
<x-wirekit::bento-grid label="Platform features">
    <x-wirekit::bento-cell span="2x2" emphasis>
        <x-wirekit::heading level="3" size="lg">Ship accessible UI by default</x-wirekit::heading>
        <x-wirekit::text variant="muted">
            Every component arrives with its keyboard model, focus handling and
            ARIA wiring already correct — not as an option you remember to enable.
        </x-wirekit::text>
    </x-wirekit::bento-cell>
    <x-wirekit::bento-cell>
        <x-wirekit::heading level="3" size="md">Theme with tokens</x-wirekit::heading>
        <x-wirekit::text variant="muted">One file changes the whole surface.</x-wirekit::text>
    </x-wirekit::bento-cell>
    <x-wirekit::bento-cell>
        <x-wirekit::heading level="3" size="md">No build step</x-wirekit::heading>
        <x-wirekit::text variant="muted">Require it and start composing.</x-wirekit::text>
    </x-wirekit::bento-cell>
    <x-wirekit::bento-cell span="2x1">
        <x-wirekit::heading level="3" size="md">Dark mode is not a fork</x-wirekit::heading>
        <x-wirekit::text variant="muted">
            Every token switches together, so there is no second set of components to maintain.
        </x-wirekit::text>
    </x-wirekit::bento-cell>
    <x-wirekit::bento-cell>
        <x-wirekit::heading level="3" size="md">MIT licensed</x-wirekit::heading>
        <x-wirekit::text variant="muted">Commercial use, no fee.</x-wirekit::text>
    </x-wirekit::bento-cell>
</x-wirekit::bento-grid>
:::

## Responsive to its own width

The grid reflows on **its own** width, not the window's — it is a container query.
Drag the divider below: as the bento panel narrows, the cells collapse to a single
stacked column, and they claim their spans again once the panel is wide enough. A
bento in a sidebar, a card, or a split view adapts to the room it actually has.

:::preview{title="Drag the divider — the bento reflows to fit its panel" wide desktopOnly}
<x-wirekit::resizable style="height: 24rem;">
    <x-wirekit::resizable.panel :defaultSize="70" :minSize="28" :maxSize="88">
        <x-wirekit::bento-grid label="Platform features" gap="sm" style="width: 100%; height: 100%; grid-auto-rows: minmax(0, 1fr);">
            <x-wirekit::bento-cell span="2x1" emphasis>
                <x-wirekit::heading level="3" size="md">Reflows to its own width</x-wirekit::heading>
                <x-wirekit::text variant="muted" size="sm">One column when narrow — whatever the window does.</x-wirekit::text>
            </x-wirekit::bento-cell>
            <x-wirekit::bento-cell>
                <x-wirekit::heading level="3" size="md">Container query</x-wirekit::heading>
                <x-wirekit::text variant="muted" size="sm">Not the viewport.</x-wirekit::text>
            </x-wirekit::bento-cell>
            <x-wirekit::bento-cell>
                <x-wirekit::heading level="3" size="md">Pure CSS</x-wirekit::heading>
                <x-wirekit::text variant="muted" size="sm">No JavaScript reflow.</x-wirekit::text>
            </x-wirekit::bento-cell>
            <x-wirekit::bento-cell span="2x1">
                <x-wirekit::heading level="3" size="md">Any container</x-wirekit::heading>
                <x-wirekit::text variant="muted" size="sm">A sidebar, a card, a split view — it adapts to the room it has.</x-wirekit::text>
            </x-wirekit::bento-cell>
        </x-wirekit::bento-grid>
    </x-wirekit::resizable.panel>
    <x-wirekit::resizable.handle />
    <x-wirekit::resizable.panel>
        <x-wirekit::stack gap="sm" style="height: 100%; justify-content: center; padding: var(--padding-wk-x-md);">
            <x-wirekit::text weight="medium">Drag the divider ←</x-wirekit::text>
            <x-wirekit::text variant="muted" size="sm">Widen or narrow the bento and watch it reflow.</x-wirekit::text>
        </x-wirekit::stack>
    </x-wirekit::resizable.panel>
</x-wirekit::resizable>
:::

## Spans

A cell claims space with `span`. The claim engages **once the grid is wide
enough** — in a narrow grid every cell is full width, whatever its span says.

That collapse is not a convenience. A two-column cell inside a cramped grid
squeezes its neighbors into unreadable slivers, and because the reflow follows the
grid's own width, it happens wherever the grid is narrow — a sidebar, a split
view, a phone — not only on a small window.

| `span` | Claims | Use it for |
|--------|--------|------------|
| `1x1` | One cell (default) | A supporting feature |
| `2x1` | Two columns wide | A feature that needs a sentence, not a phrase |
| `1x2` | Two rows tall | A tall visual — a screenshot, a phone mockup |
| `2x2` | Two by two | The one headline capability |

```blade
<x-wirekit::bento-grid label="Platform features">
    <x-wirekit::bento-cell span="2x2" emphasis>The headline</x-wirekit::bento-cell>
    <x-wirekit::bento-cell>A supporting point</x-wirekit::bento-cell>
    <x-wirekit::bento-cell span="2x1">Something that needs room</x-wirekit::bento-cell>
</x-wirekit::bento-grid>
```

::: info Reading order is DOM order
There is deliberately no prop to place a cell at an arbitrary track. Such a prop
lets the visual order and the order a screen reader announces drift apart — and
the drift is invisible to whoever authored it. Write the cells in the order they
should be read, and the layout follows.
:::

## Emphasis

`emphasis` lifts a cell with a **border and a surface** — two independent signals,
so the emphasis survives a reader who cannot separate the accent hue from the
surrounding surface.

```blade
<x-wirekit::bento-cell span="2x2" emphasis>The one that matters</x-wirekit::bento-cell>
```

## Bleeding imagery

`bleed` drops the cell's padding so content can run to the edge — for a
screenshot or an illustration that should meet the border rather than sit in a box.
The cell clips it to its own rounded corners.

```blade
<x-wirekit::bento-cell span="1x2" bleed>
    <x-wirekit::image src="/img/editor.png" alt="The editor in dark mode" ratio="3/4" fit="cover" />
</x-wirekit::bento-cell>
```

## Props

### `<x-wirekit::bento-grid>`

| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `label` | `string\|null` | `null` | Accessible name for the showcase. Omit and no group boundary is announced. |
| `gap` | `string` | `'md'` | `none`, `xs`, `sm`, `md`, `lg`, `xl`, `2xl` — the same ladder as [grid](/components/grid). |
| `scope` | `string\|null` | `null` | Class-scope override. |

### `<x-wirekit::bento-cell>`

| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `span` | `string` | `'1x1'` | `1x1`, `2x1`, `1x2`, `2x2`. Engages from `md` up. |
| `emphasis` | `bool` | `false` | Lift the cell with a border and a surface. |
| `bleed` | `bool` | `false` | Drop the cell's padding so content meets the edge. |
| `scope` | `string\|null` | `null` | Class-scope override. |

**The rung names are shared with a second ladder, and the values are not.** `gap` reads `--space-wk-*`; the tighter `--gap-wk-*` ladder uses the same `xs`…`2xl` labels with different values from `md` up, and WireKit's own components read it for spacing inside a component. Swapping one for the other by name silently re-spaces the container — see [Two spacing ladders, same rung names](/theming/design-tokens#two-spacing-ladders-same-rung-names).

## Accessibility

- Pass `label` and the showcase becomes a named `role="group"`, so a screen reader
  gets a boundary and a name rather than a run of loose headings. Without a label
  no group is announced — a boundary that cannot say what it is for is noise.
- Reading order is DOM order. Nothing here can reorder cells visually.
- `emphasis` is never color alone: the border and the surface both change.
- Rows size to their content, so a tall cell cannot crop the text inside it.

## Keyboard Interaction

None of its own. A bento grid is a layout — whatever you put in a cell keeps its
own keyboard behavior, in the order the cells are written.
