# Record detail

Build record details on your collection's schema, query and permissions.

Import: @slices.sh/collection/headless

Public parts: 12

## Installation

### 1. Install the service registry

services/index.ts, which `import "@/services"` loads. A provider setup writes the same file; skip this if your app has one.

```bash
npx shadcn@latest add "https://slices.sh/r/services.json"
```

### 2. Install the headless library

```bash
npm install @slices.sh/collection@^0.1.6
```

### 3. Install the service

Install the provider configuration via shadcn or the slices CLI. Skip this step if your app already has the service configured. Provider commands and provisioning: https://slices.sh/docs/services/collection.md

## Anatomy

### Composition

```text
Collection.Root
  Collection.View name="table"
    Collection.Grid
      Collection.Row
        Collection.Open
  Collection.Hierarchy parent="parent"
    Collection.Peek
      Collection.PeekNext
      Collection.PeekPrevious
      Collection.PeekPosition
    Collection.DetailSheet
      Collection.Detail
        Collection.RecordReady
        Collection.RecordLoading
        Collection.RecordError
      Collection.Back
  Collection.NoDetail
```

## Minimal composition

```tsx
"use client"

import "@/services"
import { Collection } from "@slices.sh/collection/headless"
import type { CollectionSchema } from "@slices.sh/collection/services/types"

export function Example({ schema }: { schema: CollectionSchema }) {
  return (
    <Collection.Root collection={schema.collection} schema={schema}>
      <Collection.Row render={<div />}>
        <Collection.Open>Open record</Collection.Open>
      </Collection.Row>
      <Collection.Detail>
        <Collection.Back>Back</Collection.Back>
        <Collection.RecordReady>
          <Collection.Cell render={<span />}>
            <Collection.Value />
          </Collection.Cell>
        </Collection.RecordReady>
        <Collection.RecordLoading>Loading…</Collection.RecordLoading>
      </Collection.Detail>
    </Collection.Root>
  )
}

```

Supply a registered collection schema. Parts use its fields, permissions and provider capabilities.

## Complete API reference

### Collection.DetailSheet

content · Element: div

Open for exactly as long as a record is open: what a sheet takes its state from.

On a phone there is no room for two panes, and a detail underneath a list that scrolls is a
detail nobody sees — opening a record has to cover the list rather than push it below the
fold. Closing the sheet closes the record, whichever way it was closed: the backdrop,
Escape, the sheet's own button. So "what is being read" stays one answer that both screens
read, rather than a second piece of state to keep in step.

Import: @slices.sh/collection/headless

Collection.DetailSheet must be inside <Collection.Root>.

```tsx
PartProps<"div">
```

Source: @slices.sh/collection/src/headless/detail-sheet.tsx

### Collection.Peek

content · Element: div

A nonmodal preview of the open record that the row arrows keep driving, with previous, next and a position whose total stays null when unknown.

Import: @slices.sh/collection/headless

Collection.Peek requires Collection.Root.

```tsx
PartProps<"div"> & {
  label: string
  followDelay?: number
  /** The dialog the record opens in, which the pattern styles; it never has a backdrop, since the list stays usable. */
  popup?: ReactElement<ComponentProps<typeof MotionDialog>>
}
```

Source: @slices.sh/collection/src/headless/peek.tsx

### Collection.Detail

gate

The record a detail is showing, as a scope its fields are read from.

Cells work inside it exactly as they do inside a row, which is why a detail needs no parts
of its own to draw one: `<Collection.Cell fields="all">` is the same part either side.

Import: @slices.sh/collection/headless

Collection.Detail requires Collection.Root.

Collection.Detail must be inside <Collection.Root>.

```tsx
GateProps<{ id: string }> & { follow?: boolean; followDelay?: number }
```

Source: @slices.sh/collection/src/headless/detail.tsx

### Collection.RecordReady

gate

Opening a record without touching the selection, the detail scope its cells read from (follow opens the cursor's record after a pause), a sheet that is open while a record is, the hint while none is, closing it, and the per-record read states of a hierarchy.

Import: @slices.sh/collection/headless

```tsx
GateProps
```

Source: @slices.sh/collection/src/headless/record-ready.tsx

### Collection.PeekPosition

content · Element: span

A nonmodal preview of the open record that the row arrows keep driving, with previous, next and a position whose total stays null when unknown.

Import: @slices.sh/collection/headless

Collection.PeekPosition requires Collection.Root.

```tsx
PartProps<
    "span",
    { position: number; exact: boolean; loadedPosition: number; loaded: number; total: number | null }
  >
```

Source: @slices.sh/collection/src/headless/peek-position.tsx

### Collection.PeekPrevious

action · Element: button

A nonmodal preview of the open record that the row arrows keep driving, with previous, next and a position whose total stays null when unknown.

Import: @slices.sh/collection/headless

Collection.PeekPrevious requires Collection.Root.

```tsx
PartProps<"button">
```

Source: @slices.sh/collection/src/headless/peek-previous.tsx

### Collection.PeekNext

action · Element: button

A nonmodal preview of the open record that the row arrows keep driving, with previous, next and a position whose total stays null when unknown.

Import: @slices.sh/collection/headless

Collection.PeekNext requires Collection.Root.

```tsx
PartProps<"button">
```

Source: @slices.sh/collection/src/headless/peek-next.tsx

### Collection.Back

action · Element: button

Closes the open record. Away while none is open, since there is nothing to go back from.

Import: @slices.sh/collection/headless

Collection.Back requires Collection.Root.

Collection.Back must be inside <Collection.Root>.

```tsx
PartProps<"button"> & { restoreFocus?: boolean }
```

Source: @slices.sh/collection/src/headless/back.tsx

### Collection.NoDetail

gate

While no record is being shown, which is where a detail says what to do rather than staying
blank.

"Being shown", not "asked for": a record deleted while it was open — from the detail's own
button, or by somebody else — leaves an id pointing at a row that is not there any more, and
`Collection.Detail` closes on exactly that. The two of them have to agree, or the pane goes
blank and says nothing about why.

Import: @slices.sh/collection/headless

Collection.NoDetail must be inside <Collection.Root>.

```tsx
GateProps<{ count: number }>
```

Source: @slices.sh/collection/src/headless/no-detail.tsx

### Collection.Open

action · Element: button

Opens the record it is inside, for a detail to show.

A record can be open and ticked at once and they mean different things, so this never
touches the selection: one is what is being read, the other is what an action reaches.

Import: @slices.sh/collection/headless

Collection.Open must be inside <Collection.Row>.

Collection.Open must be inside <Collection.Root>.

```tsx
PartProps<"button", { open: boolean }>
```

Source: @slices.sh/collection/src/headless/open.tsx

### Collection.RecordLoading

gate

Opening a record without touching the selection, the detail scope its cells read from (follow opens the cursor's record after a pause), a sheet that is open while a record is, the hint while none is, closing it, and the per-record read states of a hierarchy.

Import: @slices.sh/collection/headless

```tsx
GateProps
```

Source: @slices.sh/collection/src/headless/record-loading.tsx

### Collection.RecordError

gate

Opening a record without touching the selection, the detail scope its cells read from (follow opens the cursor's record after a pause), a sheet that is open while a record is, the hint while none is, closing it, and the per-record read states of a hierarchy.

Import: @slices.sh/collection/headless

```tsx
GateProps<{ message: string; denied: boolean; retry(): void }>
```

Source: @slices.sh/collection/src/headless/record-error.tsx

### Collection.Root

scope

The root, and the parts that choose what is drawn under it: one of several views, the data a view needs, and one of two screens by width.

Import: @slices.sh/collection/headless

```tsx
CollectionRootProps

type CollectionRootProps = RootProps &
  CollectionViewOptions &
  CardSizeOptions &
  SavedViewOptions &
  GroupingOptions & {
    /**
     * Left out or `true`: required, and a missing service throws. `"optional"`: renders its
     * children with no rows, and every part inside renders nothing. `false`: off. A capability
     * name (`"live"`) requires the service and that capability.
     */
    enabled?: Enabled
    /** The collection config, `"default"` when left out. `collection` names one inside it. */
    config?: string
    groupSumFields?: readonly string[]
    cardSizing?: boolean
    lanes?: boolean
    collection: string
    schema: CollectionSchema
    density?: DensityValue
    onDensityChange?: (density: DensityValue) => void
    palette?: boolean
    pageSize?: number
    infinite?: boolean
    live?: boolean
    /** Whether the search, the sort and the filter travel in the address bar. */
    urlState?: boolean
    /** `false` when nothing on screen searches: no search applies, from the address or a saved view. */
    search?: boolean
    /** `false` when nothing on screen filters: no filter applies, from the address or a saved view. */
    filters?: boolean
  }
```

Source: @slices.sh/collection/src/headless/root.tsx

### Collection.View

gate

The root, and the parts that choose what is drawn under it: one of several views, the data a view needs, and one of two screens by width.

Import: @slices.sh/collection/headless

Collection.View requires Collection.Root.

```tsx
GateProps & { name: CollectionViewValue | readonly CollectionViewValue[] }
```

Source: @slices.sh/collection/src/headless/view.tsx

### Collection.Grid

content · Element: table

Row focus is the default. Cell mode preserves the existing APG grid keyboard.

Import: @slices.sh/collection/headless

```tsx
PartProps<"table"> & { focus?: "row" | "cell"; selectable?: boolean }
```

Source: @slices.sh/collection/src/headless/grid.tsx

### Collection.Row

content · Element: tr

The row surface of a list, grid or board, the row repeated per record, the cell repeated per field (all, primary, media or details, or one field by name) and the value formatted by field type. Value renders nothing when the field is empty.

Import: @slices.sh/collection/headless

Collection.Row must be inside <Collection.Root>.

Collection.Row requires Collection.Root.

```tsx
RowProps

type RowProps = PartProps<"tr", { selected: boolean; dragging: boolean }> & { openOnClick?: boolean }
```

Source: @slices.sh/collection/src/headless/row.tsx

### Collection.Hierarchy

scope

A treegrid over a parent field: the read that nests records, the tree itself, a node per record on screen, the expander, the indent, moving a node in or out, a progress bar over its children, and the gate that says there is nothing to nest.

Import: @slices.sh/collection/headless

Hierarchy requires Collection.Root.

```tsx
RootProps & { parent: string; progressField?: string }
```

Source: @slices.sh/collection/src/headless/hierarchy.tsx
