# Layout Components

> Generated reference (regen-owned; see [index.md](index.md)). Binds only under `primitives: nurix`.

Components: Accordion, AspectRatio, Block, Card, Carousel, Collapsible, Item, Resizable, Row, ScrollArea, Separator, SidePanel, Sortable, Spacer

Each imports from its own subpath as one namespace, with its parts as dotted members, e.g. `import { Card } from "@nurix/components/block/card"` then `Card.Header`, `Card.Content`, `Card.Footer`. Parts swap their element through `render` — there is no `asChild` (Resizable's parts are the exception: they take no `render`).

## Block · Row · Spacer

The naked space primitives: `Block` (`block/block`) is a page region that stacks its children on the density gap and owns the region's insets; `Row` (`element/row`) owns only the gap between its children; `Spacer` (`element/spacer`) is placed empty space that pushes siblings apart. None of them draws anything — the dress is `className`.

**Block props:** `render` (root element, default `section` — `<header />`, `<footer />`, `<aside />`, `<main />`), `height` [hug|hero] (default `hug`; `hero` reserves a 300px masthead band), `shouldSettle` (a content-driven height change travels instead of snapping; outermost settling region only)
**Row props:** `gap` [none|sm|md|lg] (default `md`, on the `--density-gap-*` tokens), `align` [start|center|end|baseline] (default `center`), `hasFloor` (reserves one control height, so a toolbar row measures the same with or without its buttons), `render`
**Spacer props:** `render` only — no size, no content; `aria-hidden`, and it grows only inside a flex parent

- Insets live only on the Block: never pad a Row or Spacer inside it, never put margins on its children.
- Distribution is positional — a Spacer between the sides replaces `justify-between` and `mt-auto`. Never use it for a fixed distance; that is the parent's gap.

```tsx
<Block render={<header />} className="border-b bg-card">
  <Row hasFloor>
    <Breadcrumb />
    <Spacer />
    <Button>New</Button>
  </Row>
</Block>
```

## Card

The surfaced container (`block/card`): ring edge, elevation and density insets, with a header grid that grows an end column only when an action is present.

**Sub-components:** `Card.Header` (`hasDivider`), `Card.Header.Action` (pinned to the header's end column), `Card.Title`, `Card.Description`, `Card.Content`, `Card.Footer` (`hasDivider`)

- No `padding`/`radius`/`shadow` props: insets read the `--density-pad-*` tokens. Depth rides `className` as `shadow-(--elevation-N)` or `shadow-none`, which keeps the ring edge.
- `hasDivider` draws the hairline rule and the density inset on that side; a bare `border-b` / `border-t` class adds no inset.
- A Title mints an id; the root points `aria-labelledby` at it only while the root carries a role (`role`, or `render={<section />}` / `article` / `aside` / `form` / `nav` / `fieldset`).
- Full-bleed media is a direct `<img>` child of `Card`. A grid of clickable cards is `block/list` with `view="card"`; there is no action-area part.

```tsx
<Card render={<section />}>
  <Card.Header hasDivider>
    <Card.Title render={<h2 />}>Billing</Card.Title>
    <Card.Description>Plan and payment method</Card.Description>
    <Card.Header.Action><Button variant="ghost" size="sm">Edit</Button></Card.Header.Action>
  </Card.Header>
  <Card.Content>{/* … */}</Card.Content>
  <Card.Footer hasDivider><Button>Upgrade</Button></Card.Footer>
</Card>
```

## Item

A list or menu row primitive (`element/item`): leading media, a title and description, trailing actions.

**Sub-components:** `Item.Media` (`variant` [default|icon|image]), `Item.Content`, `Item.Title`, `Item.Description`, `Item.Actions` (a `role="group"` labelled by the row's Title), `Item.Header`, `Item.Footer`, `Item.Group`, `Item.Separator`
**Item props:** `variant` [default|outline|muted], `inset` [default|sm], `side` [start|end] (`end` reverses the row), `render` (`render={<a href="…" />}` makes the whole row a link; an untyped `<button />` becomes `type="button"`)

- `Item.Media variant="image"` is what crops and sizes an `<img>`; `icon` is a bordered tile.
- `Item.Group` inserts no dividers and carries no list role — place `Item.Separator` yourself, and write `<Item.Group render={<ul />}>` with rows `render={<li />}` for list semantics.
- Data-driven rows with search, grouping or pagination are `block/list` with `Item` rows.

```tsx
<Item variant="outline">
  <Item.Media variant="icon"><Bell /></Item.Media>
  <Item.Content>
    <Item.Title>Deploy finished</Item.Title>
    <Item.Description>web-app shipped to production.</Item.Description>
  </Item.Content>
  <Item.Actions><Button variant="ghost" size="sm">View</Button></Item.Actions>
</Item>
```

## Sortable

Drag-to-reorder list over dnd-kit (`compound/sortable`), keyboard sortable, with a floating drag overlay. Optional peer family `sortable` — install `@dnd-kit/core`, `@dnd-kit/modifiers`, `@dnd-kit/sortable` and `@dnd-kit/utilities` yourself.

**Sub-components:** `Sortable.Content`, `Sortable.Item` (`value`, `isHandle` drags the whole row), `Sortable.ItemHandle` (the grip), `Sortable.Overlay` (the drag preview, portalled to `document.body` or a `container`)
**Props:** `value` / `defaultValue` / `onValueChange(value, details)` (an uncontrolled caller refuses a move with `details.cancel()`), `onValueCommitted`, `getItemValue` (stable unique id; required when `value` holds objects), `orientation` [vertical|horizontal|mixed] (default `vertical`), `strategy`, `activationConstraint` (`distance` px or `delay` ms), `hasGrabCursor` (default on), `disabled`, `labels`

- Also exported: `useSortableItem`, `arrayMove`, `arraySwap`, the list/rect strategies, the `restrictTo*` and snap modifiers, and the collision detectors.
- Skip `Sortable.Overlay` and a dragged item only shifts in place, with nothing following the pointer.

```tsx
<Sortable value={items} onValueChange={setItems} getItemValue={(item) => item.id}>
  <Sortable.Content>
    {items.map((item) => (
      <Sortable.Item key={item.id} value={item.id}><Sortable.ItemHandle />{item.name}</Sortable.Item>
    ))}
  </Sortable.Content>
  <Sortable.Overlay />
</Sortable>
```

## Other layout components

- `compound/accordion` — `Accordion` with `Accordion.Item`, `.Header` (an `<h3>` by default; `render={<h4 />}` moves the level), `.Trigger`, `.Panel` (`hiddenUntilFound`, `keepMounted`); `value`/`defaultValue` are always arrays, `multiple` lets several stay open, `onValueChange` drives it from outside.
- `element/collapsible` — `Collapsible` with `Collapsible.Trigger` (`render` swaps the button), `.Panel` (unmounts when closed unless `keepMounted`; never give it a height class), `.Indicator` (turns on the trigger's `data-panel-open`); `open`/`defaultOpen`/`onOpenChange`.
- `block/side-panel` — `SidePanel` with `SidePanel.Trigger`, `SidePanel.Panel`: an in-flow region in a `flex` row that collapses to a rail and reflows its sibling; `side` [start|end] (default `end`), `open`/`onOpenChange`, `expandedWidth`/`collapsedWidth` (or the `--side-panel-expanded-width`/`--side-panel-collapsed-width` variables), `keepMounted`, `labels`.
- `block/resizable` — `Resizable` with `Resizable.Panel`, `Resizable.Handle` (`hasGrip`): draggable split panes inside a parent with a height; a numeric size is pixels and a string is a percentage (`defaultSize="30%"`), `orientation` [horizontal|vertical], `onLayoutCommitted` + `defaultLayout` persist the layout, `panelRef`/`groupRef` reach the imperative handles. Optional peer family `resizable` (`react-resizable-panels`).
- `compound/carousel` — `Carousel` with `Carousel.Content`, `.Item` (`basis-full` by default), `.Previous`, `.Next`; `value`/`defaultValue`/`onValueChange` (0-based snap index), `orientation`, `opts`, `setApi`, `labels`. `LoadingCarousel` is the ready-made tips strip (`data` tips with a stable `id`, `timeout`, `indicators` [progress|steps|none], `aspectRatio`, `textPlacement`). Optional peer family `carousel` (`embla-carousel-react`, `embla-carousel-autoplay`).
- `element/scroll-area` — `ScrollArea`: styled bars on both axes, each drawn only while its axis overflows; give it an explicit `h-*`/`max-h-*`, `hasFadeEdges` fades every edge with more content past it, and `aria-label` names the focusable viewport. Never pass `ScrollArea.Bar` as a child.
- `element/separator` — `Separator`: a hairline, `orientation` [horizontal|vertical] (vertical needs a bounded height); children render the labeled form, placed by `align` [start|center|end] (default `center`, ignored without children).
- `element/aspect-ratio` — `AspectRatio`: `ratio` is width / height (default `1`; a non-finite or non-positive ratio falls back to `1`), the child fills the box itself (`h-full w-full object-cover`), `render` swaps the element.

## Fallback-only concepts

No primitive ships for these; build them from the catalog's fallback spec, composed over the components above.

- `bento-grid` — an asymmetric CSS grid of `Card`s with spanning cells.
- `editable-card` — a `Card` whose `Card.Content` edits in place and whose `Card.Footer` commits.
