Skip to content
WANCRAFT

F.206Forms & Inputs

Availability Grid

A weekly availability picker: drag to paint or erase time slots, merged ranges, per-day summaries and spreadsheet keys.

NewReactTailwindadvanced

Overview

Availability Grid sets recurring weekly hours. Days run across and time slots (8:00–20:00 in 30-minute steps by default) run down; press and drag to paint a rectangle of availability, or start on a painted slot to erase — the first cell decides. Adjacent slots merge into rounded ranges labelled with their start time, and each day is summarised in words ("Mon 9:00–12:00, 14:00–17:00") along with a weekly total. The keyboard works like a spreadsheet: arrows move, Space toggles, and Shift + arrows extend from the last toggled slot. Copy Monday to every weekday in one click, show the time zone the hours are in, and read the value back as merged "HH:mm" ranges per day. On narrow screens the grid scrolls sideways.

01Installation

Method 01 · Registry

CLI verified

Uses the shadcn CLI in a project initialised with shadcn init. Installs the file to components/ui/availability-grid.tsx and adds its dependencies.

npx shadcn@latest add https://wancraft.my/r/availability-grid.json

Method 02 · Copy and paste

  1. 1. No extra dependencies — React and Tailwind CSS only.
  2. 2. Copy the source from the Code tab into components/ui/availability-grid.tsx.
  3. 3. Import it and adjust props.
Dependencies
None
Peer
react ≥ 18, tailwindcss ≥ 4
Added
13 Oct 2026
Updated
—

02Usage

The default configuration. Change properties above to generate a tailored example in the Usage tab.

example.tsx
import { AvailabilityGrid, type AvailabilityValue } from "@/components/ui/availability-grid";
import { useState } from "react";

export function AvailabilityGridExample() {
  const [hours, setHours] = useState<AvailabilityValue>({
    mon: [{ start: "09:00", end: "12:00" }, { start: "14:00", end: "17:00" }],
    tue: [{ start: "09:00", end: "17:00" }],
  });

  return (
    <div className="w-full max-w-3xl">
      <AvailabilityGrid
        value={hours}
        onChange={setHours}
        timeZoneLabel="Kuala Lumpur · GMT+8"
      />
    </div>
  );
}

03Props

PropTypeDefaultDescription
value / defaultValueAvailabilityValue—Ranges per weekday: { mon: [{ start: "09:00", end: "12:00" }], … } — controlled, or initial when uncontrolled. End times are exclusive.
onChange(value: AvailabilityValue) => void—Called with merged, sorted ranges after every paint, toggle or action.
daysWeekday[]["mon", … "sun"]Columns, in order ("mon" … "sun").
startTime / endTimestring"08:00" / "20:00"The visible window, "HH:mm".
slotMinutesnumber30Slot length: 5, 10, 15, 20, 30 or 60 minutes.
timeZoneLabelstring—Shown next to the weekly total; display only (no conversion).
localestring"en"Weekday names via Intl.DateTimeFormat.
hourCycle"12" | "24""24"Time labels and summaries as 9:00 or 9:00 AM.
labelstring"Weekly availability"Title and accessible name of the grid.
showSummarybooleantruePer-day summary list under the grid.
showActionsbooleantrueCopy-to-weekdays and Clear buttons.
rowHeightnumber18Slot row height in px.
accentstring"#FF7448"Painted ranges and focus ring.

04Features

  • 01Click-and-drag paints or erases a day × time rectangle; the first cell decides the mode
  • 02Adjacent slots merge visually into rounded ranges labelled with their start time
  • 03Per-day summaries ("Mon 9:00–12:00, 14:00–17:00") and a weekly hour total
  • 04Copy Monday to all weekdays, and Clear, in one click
  • 05Value as merged "HH:mm" ranges per weekday; configurable window and 5–60 minute slots
  • 06Scrolls sideways on narrow screens; time zone label prop
input · draginput · clickinput · keyboardinput · pointer

05Accessibility

  • The grid uses role="grid" with rows, column headers (full weekday names) and row headers; each slot is a gridcell named like "Monday 9:00 to 9:30" with aria-selected for availability.
  • One tab stop (roving tabindex): arrows move, Page Up / Page Down jump two hours, Home / End go to the first / last day, Ctrl + Home / End to the corners.
  • Space or Enter toggles a slot; Shift + arrows extend a rectangle from the last toggled slot, using that slot's new state.
  • Every toggle, extension and finished drag is announced in a polite live region ("Monday, 9:00–12:00 available.").
  • Painted fills and range labels are aria-hidden; the per-day summary repeats the result as text.

06Limitations

  • Recurring weekly hours only — no dates, exceptions or time-zone conversion (the zone label is display text).
  • Ranges are snapped to the slot grid inside the visible window; anything outside it is dropped on the next edit.
  • On touch, vertical drags paint and horizontal swipes scroll the grid, so painting across days needs a mouse, pen or the keyboard.