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/reactFor 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.
Description
A week of class slots with the places left in each, one of them already yours.
Code
slot-grid-demo.tsx, the Demo story. The component itself comes from the package.
Reference
Propsslots, start, days… (13)
Props
| Prop | Type | |
|---|---|---|
slotsRequired | readonly SlotGridSlot[] | – |
start | Date | numberThe first day shown. Default: the day of the earliest slot. | – |
days | numberDays shown. default 7 | 7 |
times | readonly number[]The rows, as start times in minutes after midnight. Default: every start time among the slots. | – |
value | string | nullThe picked slot's id (controlled); | – |
defaultValue | string | nullThe slot picked at first. default null | null |
onValueChange | (id: string, slot: SlotGridSlot) => void | – |
fewAt | numberA slot with this many places left or fewer counts as few. default 2 | 2 |
layout | 'auto' | 'week' | 'day'
default 'auto' | 'auto' |
legend | booleanShow the legend under the grid. default true | true |
label | stringThe grid's accessible name. Default: the labels' "Available times". | – |
locale | stringDefault: the YoujProvider locale. | – |
sound | booleanWith sound on, picking a slot plays 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
| Moment | Tokens | ||
|---|---|---|---|
| First viewthe cells come in as a wave from the first slot | duration.deliberateease.standard560ms · 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.standard140ms · 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.snappy0.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.