Calendar Time Slot
- Needs review
- WCAG 2.2 evidence
- RTL
One bookable time in a scheduling grid: Available, Selected, Busy and Focused. A real grid
cell (role="gridcell") around a React Aria ToggleButton: Tab reaches it, Enter or Space toggles it
(aria-pressed), a busy slot is disabled, and keyboard focus draws the shared ring (WCAG 2.1.1, 2.4.7, 4.1.2). Selected is a filled block against outlined slots, and Busy is struck through and announced as "Busy", so neither state is
carried by colour alone (1.4.1). The button is 42 px tall and fills its column, well over the 24 px target (2.5.8). Render it inside a role="row" of a role="grid" (or set cellRole="presentation"). The arrow-key movement between the
cells of a grid belongs to the container that lays the grid out; the slots themselves are in the tab order. Consumer duties: column and row labels of the grid, one selection at a time when the grid is a picker, the time zone of
start, and what happens on selection.
import { CalendarTimeSlot } from "@nexera-ui/react";- 7
- examples
- 21
- props
- 5
- live controls
- 1
- platform
- 7
- WCAG criteria
- 1
- block uses it
Try every prop. Copy the code.
Change the props and the code updates. Check light and dark, LTR and RTL, and phone width.
import { CalendarTimeSlot } from "@nexera-ui/react";
<CalendarTimeSlot />Examples 6
The same examples as Storybook, rendered live. Open Code to copy one.
Selected
Figma State=Selected: the brand fill. Controlled with isSelected and onChange, or defaultSelected for a start value.
import { CalendarTimeSlot } from "@nexera-ui/react";
<CalendarTimeSlot time="10:30" />;
Busy
Figma State=Busy: sunken, struck through and announced as "Busy"; disabled, so it cannot be chosen.
import { CalendarTimeSlot } from "@nexera-ui/react";
<CalendarTimeSlot time="10:30" />;
From start
The label written from a start instant for the provider locale and the given time zone (here Karachi, en-US: "10:30 AM").
import { CalendarTimeSlot } from "@nexera-ui/react";
const slot = (hour: number, minute: number) => new Date(Date.UTC(2026, 9, 14, hour - 5, minute));
<CalendarTimeSlot time="10:30" start={slot(10, 30)} timeZone="Asia/Karachi" />;
Slot picker
A time picker for one day: choose one slot (controlled isSelected), busy slots are skipped by Tab.
import { useState } from "react";
import { CalendarTimeSlot } from "@nexera-ui/react";
const zone = "Asia/Karachi";
const slot = (hour: number, minute: number) => new Date(Date.UTC(2026, 9, 14, hour - 5, minute));
const times = [
slot(9, 0),
slot(9, 30),
slot(10, 0),
slot(10, 30),
slot(11, 0),
slot(11, 30),
slot(12, 0),
slot(12, 30),
];
const busyAt = new Set([slot(10, 0).getTime(), slot(11, 30).getTime(), slot(12, 0).getTime()]);
export function SlotPicker() {
const [chosen, setChosen] = useState<number | null>(slot(10, 30).getTime());
return (
<div className="flex max-w-xl flex-col gap-3">
<div
role="grid"
aria-label="Available times on Wednesday 14 October"
className="grid grid-cols-2 gap-2 @nx-sm:grid-cols-4"
>
<div role="row" className="contents">
{times.map((time) => (
<CalendarTimeSlot
key={time.getTime()}
start={time}
timeZone={zone}
status={busyAt.has(time.getTime()) ? "busy" : "available"}
isSelected={chosen === time.getTime()}
onChange={(isSelected) => {
setChosen(isSelected ? time.getTime() : null);
}}
/>
))}
</div>
</div>
<p className="m-0 text-body-small text-secondary" aria-live="polite">
{chosen === null
? "No time chosen"
: `Chosen: ${new Intl.DateTimeFormat("en-US", { hour: "numeric", minute: "2-digit", timeZone: zone }).format(chosen)}`}
</p>
</div>
);
}
Right to left
Right-to-left (Arabic): the label is centred and the time written for the locale by the consumer or from start.
import { CalendarTimeSlot } from "@nexera-ui/react";
<CalendarTimeSlot time="١٠:٣٠ ص" status="busy" busyLabel="مشغول" />;
Narrow container
In a narrow column the slot keeps its height and the label wraps instead of overflowing.
import { CalendarTimeSlot } from "@nexera-ui/react";
<CalendarTimeSlot time="10:30 AM to 11:00 AM" />;
Props 21
Press "Try it" on a card to load that prop into the playground.
21 props shown
statusNexera"available" | "busy"Availability. A busy slot is struck through, announced as busy, disabled and cannot
be selected. Selected is not a status: use isSelected.
isSelectedNexerabooleanWhether the slot is selected, controlled. Use with onChange; React Aria exposes it as
aria-pressed. Selecting one slot of a group at a time is the consumer's job (set the others to false).
defaultSelectedNexerabooleanWhether the slot is selected on first render (uncontrolled).
cellRoleNexera"gridcell" | "presentation"Role of the cell around the slot. gridcell (the default) belongs in a role="row" of a role="grid"; use
presentation for a plain list or group of slots outside a grid.
timeZoneNexerastringIANA time zone start is read in (for example Asia/Karachi). Without it the viewer's time zone is used, which differs
between server and browser: pass it for server-rendered pages.
busyLabelNexerastringText announced after the time of a busy slot. Translate it for your locale.
classNameNexerastringExtra classes for the slot button, merged last so they win over the defaults.
styleNexeraCSSPropertiesInline style for the slot button.
timeNexerastringVisible time. It is the accessible name. Format it for the locale, or give start
and let the slot do it.
Overrides the text derived from start.
startNexeraDateOptional when time is given.
The instant the slot starts. The visible time ("10:30", "10:30 AM") is written from it with Intl.DateTimeFormat in the
provider's locale.
aria-labelReact AriastringDefines a string value that labels the current element.
aria-labelledbyReact AriastringIdentifies the element (or elements) that labels the current element.
aria-describedbyReact AriastringIdentifies the element (or elements) that describes the object.
onChangeReact Aria(isSelected: boolean) => voidHandler that is called when the element's selection state changes.
isDisabledReact AriabooleanWhether the button is disabled.
onPressReact Aria(e: PressEvent) => voidHandler that is called when the press is released over the target.
onPressStartReact Aria(e: PressEvent) => voidHandler that is called when a press interaction starts.
onPressEndReact Aria(e: PressEvent) => voidHandler that is called when a press interaction ends, either over the target or when the pointer leaves the target.
onPressChangeReact Aria(isPressed: boolean) => voidHandler that is called when the press state changes.
onPressUpReact Aria(e: PressEvent) => voidHandler that is called when a press is released over the target, regardless of whether it started on the target or not.
autoFocusReact AriabooleanWhether the element should receive focus on render.
* Required. React Aria props shown are the ones most apps use; the component accepts the rest of its React Aria props too.
Accessible by default.
Built on React Aria, and covered by the WCAG 2.2 evidence generated on every build.
WCAG 2.2 evidence
5 direct · 2 supporting- 1.3.1Info and RelationshipsLevel A · tested directly
- 1.4.1Use of ColorLevel A · tested directly
- 1.4.10ReflowLevel AA · supporting test
- 2.1.1KeyboardLevel A · tested directly
- 2.4.7Focus VisibleLevel AA · tested directly
- 2.5.8Target Size (Minimum)Level AA · supporting test
- 4.1.2Name, Role, ValueLevel A · tested directly
Fits any width.
Nexera components respond to the space they are given. Drag the corner of the frame, or pick a width.
Styling hooks
Pass className to add Tailwind classes (merged last). State is exposed as data attributes, so you can style it with variants like data-pressed:.
data-disableddata-hovereddata-presseddata-selected
<CalendarTimeSlot className="data-disabled:opacity-90 shadow-sm" />Used in blocks
Related components
- AccordionItemOne collapsible section of an FAQ or settings page.
- AreaChartA stacked area chart: layers piled up over time with the title, range switch, actions and legend of the card.
- AvailabilityRowOne person's availability across a window of the day: an avatar with initials and the name, and a timeline with the busy periods drawn as bars.
- AvatarA person or organisation shown as a photo, initials or an icon.
- AvatarGroupA row of overlapping avatars with a "+N" chip for the rest.
- AvatarLabelAn avatar with a name and an optional subtitle beside it, for a person in a list, a header or a mention.
- BadgeShort status or category label: `Tone` x `Variant` x `Size`, with an optional dot, leading icon and remove button.
- BadgeGroupA pill that pairs a small badge with a short message, for an announcement or a status line.