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

Headless / Collections

Kanban board

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

14 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.Board>        <Collection.Lane>          <Collection.LaneLabel />          <Collection.LaneCount />          <Collection.Row render={<div />}>            <Collection.Cell render={<span />}>              <Collection.Value />            </Collection.Cell>          </Collection.Row>        </Collection.Lane>      </Collection.Board>    </Collection.Root>  )}

Choose a grouping field in the collection configuration so the board has lanes.

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

A board that reads lane by lane, tabs and one panel per lane on a phone, a lane per option of the group field with its label, backend count, loading and error, and the gate that says there is nothing to group by. LaneCount hides an unknown count rather than counting loaded cards.

Capability: countMatching (LaneCount)

Required scope

Board requires Collection.Root.

Props

2 documented props

Collection.Board props
PropTypeDefault
pageSize
number20
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/board.tsx

Collection.Lanecontent · <section>

A board that reads lane by lane, tabs and one panel per lane on a phone, a lane per option of the group field with its label, backend count, loading and error, and the gate that says there is nothing to group by. LaneCount hides an unknown count rather than counting loaded cards.

Capability: countMatching (LaneCount)

Required scope

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

Props

3 documented props · Native <section> attributes supported

Collection.Lane props
PropTypeDefault
limit
number | Readonly<Record<string, number>>—
children

Content rendered inside this part.

ReactNode | ((state: { option: string count: number | null over: boolean dropIndex: 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/lane.tsx

CollectionPointer.Lanecontent · <section>

Lane makes a lane a drop target, empty lanes included. Row registers a card as sortable within its lane and keeps the row's role, tabIndex, aria and data attributes, so the engine never replaces selection semantics.

Capability: order (positions within a lane need a numeric orderBy)

Required scope

Pointer.Lane requires Collection.Lane.

Props

2 documented props · Native <section> attributes supported

CollectionPointer.Lane 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/collection-pointer/lane.tsx

Collection.LaneLabelcontent

A board that reads lane by lane, tabs and one panel per lane on a phone, a lane per option of the group field with its label, backend count, loading and error, and the gate that says there is nothing to group by. LaneCount hides an unknown count rather than counting loaded cards.

Capability: countMatching (LaneCount)

Required scope

Collection.LaneLabel must be inside <Collection.Lane>.

Props

2 documented props

Collection.LaneLabel 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/lane-label.tsx

Collection.LaneCountcontent · <span>

A board that reads lane by lane, tabs and one panel per lane on a phone, a lane per option of the group field with its label, backend count, loading and error, and the gate that says there is nothing to group by. LaneCount hides an unknown count rather than counting loaded cards.

Capability: countMatching (LaneCount)

Required scope

Collection.LaneCount must be inside <Collection.Lane>.

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

Props

2 documented props · Native <span> attributes supported

Collection.LaneCount props
PropTypeDefault
children

Content rendered inside this part.

ReactNode | ((state: { count: number; limit: number | null; exceeded: 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/lane-count.tsx

Collection.BoardTabscontent · <div>

A board that reads lane by lane, tabs and one panel per lane on a phone, a lane per option of the group field with its label, backend count, loading and error, and the gate that says there is nothing to group by. LaneCount hides an unknown count rather than counting loaded cards.

Capability: countMatching (LaneCount)

Props

2 documented props · Native <div> attributes supported

Collection.BoardTabs 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/board-tabs.tsx

Collection.LaneTabaction · <button>

A board that reads lane by lane, tabs and one panel per lane on a phone, a lane per option of the group field with its label, backend count, loading and error, and the gate that says there is nothing to group by. LaneCount hides an unknown count rather than counting loaded cards.

Capability: countMatching (LaneCount)

Required scope

LaneTab requires Collection.Root.

LaneTab requires Collection.Board.

Props

2 documented props · Native <button> attributes supported

Collection.LaneTab props
PropTypeDefault
children

Content rendered inside this part.

ReactNode | ((state: { label: string; count: number | null; active: 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/lane-tab.tsx

Collection.BoardPanelcontent · <div>

Inactive panels keep their ARIA targets, but only one grid is mounted on mobile.

Capability: countMatching (LaneCount)

Required scope

BoardPanel requires Collection.Root.

Props

2 documented props · Native <div> attributes supported

Collection.BoardPanel 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/board-panel.tsx

Collection.MoveToaction · <button>

Explicit moves a finger and a screen reader can make: to another lane, or to the top or bottom of this one, and the live announcement of a drag. MoveWithin renders nothing without a numeric orderBy.

Required scope

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

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

Props

2 documented props · Native <button> attributes supported

Collection.MoveTo props
PropTypeDefault
children

Content rendered inside this part.

ReactNode | ((state: { lane: string; label: string }) => 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/move-to.tsx

Collection.MoveWithinaction · <button>

Sends a card to the top or the bottom of the lane it is already in. A pointer drag reorders a card and so does the keyboard carry; a finger does neither, because HTML5 drag events never fire from touch. So the two ends of a lane are offered as ordinary buttons — which is the same answer Atlassian's own accessibility guidance gives for pragmatic-drag-and-drop: an explicit move a reader can name, not a gesture. Away when the schema declares no numeric `orderBy`, because then a lane holds no order to move within, and away when the card is already at that end.

Required scope

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

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

Props

3 documented props · Native <button> attributes supported

Collection.MoveWithin props
PropTypeDefault
toRequired
"top" | "bottom"—
children

Content rendered inside this part.

ReactNode | ((state: { to: Edge }) => 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/move-within.tsx

Collection.DragStatuscontent · <span>

Explicit moves a finger and a screen reader can make: to another lane, or to the top or bottom of this one, and the live announcement of a drag. MoveWithin renders nothing without a numeric orderBy.

Required scope

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

Props

2 documented props · Native <span> attributes supported

Collection.DragStatus props
PropTypeDefault
children

Content rendered inside this part.

ReactNode | ((state: DragStatusState) => 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/drag-status.tsx

Collection.LaneLoadinggate

A board that reads lane by lane, tabs and one panel per lane on a phone, a lane per option of the group field with its label, backend count, loading and error, and the gate that says there is nothing to group by. LaneCount hides an unknown count rather than counting loaded cards.

Capability: countMatching (LaneCount)

Required scope

LaneLoading requires Collection.Lane.

LaneLoading requires Collection.Root.

Props

1 documented props

Collection.LaneLoading 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/lane-loading.tsx

Collection.LaneErrorgate

A board that reads lane by lane, tabs and one panel per lane on a phone, a lane per option of the group field with its label, backend count, loading and error, and the gate that says there is nothing to group by. LaneCount hides an unknown count rather than counting loaded cards.

Capability: countMatching (LaneCount)

Required scope

LaneError requires Collection.Lane.

LaneError requires Collection.Root.

Props

1 documented props

Collection.LaneError props
PropTypeDefault
children

Content rendered inside this part.

ReactNode | ((state: LaneErrorState) => ReactNode)—

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

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

Collection.Ungroupedgate

A board that reads lane by lane, tabs and one panel per lane on a phone, a lane per option of the group field with its label, backend count, loading and error, and the gate that says there is nothing to group by. LaneCount hides an unknown count rather than counting loaded cards.

Capability: countMatching (LaneCount)

Required scope

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

Props

1 documented props

Collection.Ungrouped 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/ungrouped.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

CollectionPointer.Rootscope

The isolated pointer engine: a 5 px or 200 ms activation with a mouse, a 250 ms press on touch, and a drop that becomes one move write (group, and position when orderBy is numeric). A refused write rolls the card back and returns focus to it.

Required scope

Pointer.Root requires Collection.Root.

Props

1 documented props

CollectionPointer.Root 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/collection-pointer/root.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 →