Skip to content

Slot Grid

Choose a bookable time from a weekly grid that shows remaining places.

import { SlotGrid, SlotGridSlot } from '@youj/react'

Installation & usage

npm i @youj/react

For an app already set up with npx @youj/cli init, which installs the peers (@youj/core, @youj/motion, motion and Base UI) and wires the provider. New app? Start at Installation.

Use a slot grid to pick one time from a week of sessions that each have a number of places: a studio's classes, a salon's chairs, a clinic's appointments, a tour's departures. There's a column per day and a row per start time. In each cell a run of pips, one per place up to eight, is lit for the places still free, so availability reads as a pattern before you read a number. A slot with only a few places left warms to the warning tone, a full one is hatched and dark, a time the day doesn't offer is a dash, and the viewer's own booking carries a check. For a single day of free times, use a list of buttons; for picking a date, a Calendar.

import { SlotGrid, type SlotGridSlot } from '@youj/react'

const slots: SlotGridSlot[] = [
  { id: 'mon-7', start: new Date('2026-09-28T07:00'), left: 3, capacity: 8 },
  { id: 'mon-9', start: new Date('2026-09-28T09:00'), left: 0, capacity: 8 },
  { id: 'tue-7', start: new Date('2026-09-29T07:00'), left: 8, capacity: 8, mine: true },
]

const [slot, setSlot] = useState<string | null>(null)

<SlotGrid slots={slots} start={new Date('2026-09-28')} value={slot} onValueChange={setSlot} />

start is the first day shown (by default, the day of the earliest slot) and days how many follow (7). The rows are every start time among the slots, or the ones you pass as times, in minutes after midnight, so a time one day doesn't offer shows as a dash. A slot counts as few when it has fewAt places left or fewer (2). Only a slot with room can be picked: onValueChange is called with its id and the slot, and value makes the pick controlled.

layout="auto" (the default) measures the grid's own width and shows one day at a time when it's under 36rem, whatever the viewport: a track of the week's days, each with a bar of how much of it is still free, over that day's times as full-width rows. layout="week" and layout="day" fix one or the other.

Reference

Propsslots, start, days… (13)

Props

SlotGrid props
PropType
slotsRequiredreadonly SlotGridSlot[]–
startDate | number

The first day shown. Default: the day of the earliest slot.

–
daysnumber

Days shown.

default 7

7
timesreadonly number[]

The rows, as start times in minutes after midnight. Default: every start time among the slots.

–
valuestring | null

The picked slot's id (controlled); null for none.

–
defaultValuestring | null

The slot picked at first.

default null

null
onValueChange(id: string, slot: SlotGridSlot) => void–
fewAtnumber

A slot with this many places left or fewer counts as few.

default 2

2
layout'auto' | 'week' | 'day'

week is the grid, day one day under a day picker; auto picks by the room there is.

default 'auto'

'auto'
legendboolean

Show the legend under the grid.

default true

true
labelstring

The grid's accessible name. Default: the labels' "Available times".

–
localestring

Default: the YoujProvider locale.

–
soundboolean

With sound on, picking a slot plays select.

default true

true
Other props pass through: extends ComponentProps<'div'>.
StylingParts and data attributes

Styling

Parts: slot-grid (with data-layout), slot-grid-grid, slot-grid-row, slot-grid-time, slot-grid-day, slot-grid-cell (with data-state: open, few, full, closed or mine, and data-picked), slot-grid-pips, slot-grid-pip (with data-state), slot-grid-value, slot-grid-legend, and in the day view slot-grid-days, slot-grid-day-key, slot-grid-date and slot-grid-day-indicator. In Bezel the day names are mono capital eyebrows and the times, dates and counts are mono.

MotionFirst view, pick, another day · reduced motion

Motion

Motion tokens
MomentTokens
First viewthe cells come in as a wave from the first slot
duration.deliberateease.standard

560ms · cubic-bezier(0.2, 0, 0, 1)

560mscubic-bezier(0.2, 0, 0, 1)
Pickthe cell lights as the key; a press gives a little
duration.fastease.standard

140ms · cubic-bezier(0.2, 0, 0, 1)

140mscubic-bezier(0.2, 0, 0, 1)
Another dayin the day view the new day slides in from the side it lies on; the day key glides
spring.snappy

0.2s, bounce 0

0.2s, bounce 0

Reduced motion

The grid is drawn in place, days swap without the slide and the key jumps.

Reduced motion

The cells are drawn in place, a new day replaces the last without sliding, and the day key moves without gliding.

AccessibilityKeyboard

Accessibility

  • The grid is an ARIA grid named by label (default: the labels' "Available times") and described by how to move through it, with the days as column headers and the times as row headers.
  • Each cell is named by its day, time and state ("Monday, September 28, 07:00, 3 left"). A slot with room is selectable and says whether it's picked; a full slot, a time not offered and your own booking are disabled.
  • The grid is one tab stop, starting on the picked slot or the first with room. Arrow keys move between days and times (left and right mirrored in RTL), Home and End to a row's ends, Ctrl+Home and Ctrl+End to the grid's, and Enter or Space picks. In the day view, left and right step the day.
  • With sound on, picking a slot plays select (sound={false} keeps it quiet).
  • Its words come from the labels (slotGrid, slotGridHint, slotGridCell, slotGridLeft, slotGridFull, slotGridClosed, slotGridFew, slotGridOpen, slotGridMine, slotGridDays, slotGridOpenCount), translated by the language packs.