Skip to content
SLICES / TECHNICAL FOLIO Nº 001FULL-STACK ASSEMBLIES — OPEN SOURCE

Headless / Collections

Record detail

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

12 public parts · 1examples

@slices.sh/collection/headlessView as Markdown ↗

Your design. Our behavior.

Explore the tree to see what each part controls. Gates reveal the relevant state; roots outline their scope.

Behavior / inputGateGenericRenderer · not headlessScope
COMPONENT ANATOMYHover · focus · tap
ONE POSSIBLE DESIGNStatic preview

Collection.Root · Scope

Collection.RootThe 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.

Installation

Choose what your component needs. Run these commands in an initialized Next.js app.

0

Before you start

The steps below run in a Next.js app with shadcn set up (it has a components.json). In an existing Next.js app, such as one from create-next-app, set shadcn up first:

npx shadcn@latest init -t next -b base -p nova

Or start fresh: the slices CLI creates the app and sets it up.

npx slices.sh install
1

Install the service registry

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

npx add services
2

Install the headless library

npm

Add the React parts to your app, then compose them with your own elements.

npm install @slices.sh/collection@^0.1.6

Import from @slices.sh/collection/headless.

3

Install the service

Connect the parts to a provider. Skip this step if this service is already configured in your app.

Provider
npx add collection.memory

Adds service configuration and API routes to your app, with the provider dependencies. For a headless composition, add import "@/services" after "use client".

The slices CLI installs the service and provisions its required resources in one command.

Provider setup notes
  • Rows are lost when the server restarts, and live updates reach only watchers in the process that wrote. Move on with npx shadcn@latest add @slices/collection.mongodb (or -neon, -supabase): services/collection/*.config.ts and rules.ts are yours and stay.

Start small

Parts bring the behavior. Use their default elements, or supply your own with render.

example.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.

API reference

Every public part used by this experience is listed below. Gates render children without adding an element. Element parts accept native element props and render; function children receive the state declared in their props. Scopes provide context rather than a visual element.

Collection.DetailSheetcontent · <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.

Required scope

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

Props

2 documented props · Native <div> attributes supported

Collection.DetailSheet props
PropTypeDefault
children

Content rendered inside this part.

ReactNode—
render

Replace the default element with your own while retaining the part’s behavior and accessibility.

ReactElement—

Required props are marked. A dash means no explicit default is declared in this component.

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

Collection.Peekcontent · <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.

Required scope

Collection.Peek requires Collection.Root.

Props

5 documented props · Native <div> attributes supported

Collection.Peek props
PropTypeDefault
labelRequired
string—
followDelay
number120
popup

The dialog the record opens in, which the pattern styles; it never has a backdrop, since the list stays usable.

ReactElement<ComponentProps<typeof MotionDialog>>—
children

Content rendered inside this part.

ReactNode—
render

Replace the default element with your own while retaining the part’s behavior and accessibility.

ReactElement—

Required props are marked. A dash means no explicit default is declared in this component.

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

Collection.Detailgate

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.

Required scope

Collection.Detail requires Collection.Root.

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

Props

3 documented props

Collection.Detail props
PropTypeDefault
follow
booleanfalse
followDelay
number120
children

Content rendered inside this part.

ReactNode | ((state: { id: string }) => ReactNode)—

Required props are marked. A dash means no explicit default is declared in this component.

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

Collection.RecordReadygate

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.

Props

1 documented props

Collection.RecordReady props
PropTypeDefault
children

Content rendered inside this part.

ReactNode—

Required props are marked. A dash means no explicit default is declared in this component.

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

Collection.PeekPositioncontent · <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.

Required scope

Collection.PeekPosition requires Collection.Root.

Props

2 documented props · Native <span> attributes supported

Collection.PeekPosition props
PropTypeDefault
children

Content rendered inside this part.

ReactNode | ((state: { position: number; exact: boolean; loadedPosition: number; loaded: number; total: number | null }) => ReactNode)—
render

Replace the default element with your own while retaining the part’s behavior and accessibility.

ReactElement—

Required props are marked. A dash means no explicit default is declared in this component.

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

Collection.PeekPreviousaction · <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.

Required scope

Collection.PeekPrevious requires Collection.Root.

Props

2 documented props · Native <button> attributes supported

Collection.PeekPrevious props
PropTypeDefault
children

Content rendered inside this part.

ReactNode—
render

Replace the default element with your own while retaining the part’s behavior and accessibility.

ReactElement—

Required props are marked. A dash means no explicit default is declared in this component.

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

Collection.PeekNextaction · <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.

Required scope

Collection.PeekNext requires Collection.Root.

Props

2 documented props · Native <button> attributes supported

Collection.PeekNext props
PropTypeDefault
children

Content rendered inside this part.

ReactNode—
render

Replace the default element with your own while retaining the part’s behavior and accessibility.

ReactElement—

Required props are marked. A dash means no explicit default is declared in this component.

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

Collection.Backaction · <button>

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

Required scope

Collection.Back requires Collection.Root.

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

Props

3 documented props · Native <button> attributes supported

Collection.Back props
PropTypeDefault
restoreFocus
booleanfalse
children

Content rendered inside this part.

ReactNode—
render

Replace the default element with your own while retaining the part’s behavior and accessibility.

ReactElement—

Required props are marked. A dash means no explicit default is declared in this component.

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

Collection.NoDetailgate

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.

Required scope

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

Props

1 documented props

Collection.NoDetail props
PropTypeDefault
children

Content rendered inside this part.

ReactNode | ((state: { count: number }) => ReactNode)—

Required props are marked. A dash means no explicit default is declared in this component.

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

Collection.Openaction · <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.

Required scope

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

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

Props

2 documented props · Native <button> attributes supported

Collection.Open props
PropTypeDefault
children

Content rendered inside this part.

ReactNode | ((state: { open: boolean }) => ReactNode)—
render

Replace the default element with your own while retaining the part’s behavior and accessibility.

ReactElement—

Required props are marked. A dash means no explicit default is declared in this component.

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

Collection.RecordLoadinggate

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.

Props

1 documented props

Collection.RecordLoading props
PropTypeDefault
children

Content rendered inside this part.

ReactNode—

Required props are marked. A dash means no explicit default is declared in this component.

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

Collection.RecordErrorgate

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.

Props

1 documented props

Collection.RecordError props
PropTypeDefault
children

Content rendered inside this part.

ReactNode | ((state: { message: string; denied: boolean; retry(): void }) => ReactNode)—

Required props are marked. A dash means no explicit default is declared in this component.

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

Collection.Rootscope

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.

Props

25 documented props

Collection.Root props
PropTypeDefault
treeParent
string—
view
"table" | "list" | "grid" | "board" | "tree" | "detail"—
views
readonly CollectionViewValue[]—
cardSize
"small" | "medium" | "large"—
savedViews
readonly SavedView[]—
onSaveView
(view: SavedView) => void | Promise<void>—
groupBy
string | null—
onGroupByChange
(field: string | null) => void—
enabled

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—
config

The collection config, "default" when left out. collection names one inside it.

string—
groupSumFields
readonly string[]—
cardSizing
booleanfalse
lanes
booleanfalse
collectionRequired
string—
schemaRequired
CollectionSchema—
density
"compact" | "comfortable" | "spacious"—
onDensityChange
(density: DensityValue) => void—
palette
booleanfalse
pageSize
number10
infinite
booleanfalse
live
booleanfalse
urlState

Whether the search, the sort and the filter travel in the address bar.

booleantrue
search

false when nothing on screen searches: no search applies, from the address or a saved view.

booleantrue
filters

false when nothing on screen filters: no filter applies, from the address or a saved view.

booleantrue
children

Content rendered inside this part.

ReactNode—

Required props are marked. A dash means no explicit default is declared in this component.

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

Collection.Viewgate

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.

Required scope

Collection.View requires Collection.Root.

Props

2 documented props

Collection.View props
PropTypeDefault
nameRequired
CollectionViewValue | readonly CollectionViewValue[]—
children

Content rendered inside this part.

ReactNode—

Required props are marked. A dash means no explicit default is declared in this component.

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

Collection.Gridcontent · <table>

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

Props

4 documented props · Native <table> attributes supported

Collection.Grid props
PropTypeDefault
focus
"row" | "cell""row"
selectable
booleanfalse
children

Content rendered inside this part.

ReactNode—
render

Replace the default element with your own while retaining the part’s behavior and accessibility.

ReactElement—

Required props are marked. A dash means no explicit default is declared in this component.

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

Collection.Rowcontent · <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.

Required scope

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

Collection.Row requires Collection.Root.

Props

3 documented props · Native <tr> attributes supported

Collection.Row props
PropTypeDefault
openOnClick
booleanfalse
children

Content rendered inside this part.

ReactNode | ((state: { selected: boolean; dragging: boolean }) => ReactNode)—
render

Replace the default element with your own while retaining the part’s behavior and accessibility.

ReactElement—

Required props are marked. A dash means no explicit default is declared in this component.

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

Collection.Hierarchyscope

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.

Required scope

Hierarchy requires Collection.Root.

Props

3 documented props

Collection.Hierarchy props
PropTypeDefault
parentRequired
string—
progressField
string—
children

Content rendered inside this part.

ReactNode—

Required props are marked. A dash means no explicit default is declared in this component.

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

When something is missing

No service configuration
Install the collection setup below and import "@/services". Required service roots report missing setup; gates do not replace that configuration.
Missing capability or parent scope
Follow each part’s context requirements and the example composition. Capability-aware controls hide when the provider cannot perform their action.
Read the setup guide →