Skip to content
kerfjsGitHub

MeetingPlanner

Find a time that works for everyone, then send the invite.

SourcePackage @kerf-js/veneer

Preview

tsx
import {  MeetingPlanner,  Toaster,  toast} from '@kerf-js/veneer'import type { Attendee } from '@kerf-js/veneer-core'export function Review() {  const sent = () => {    toast.show({      title: 'Invite sent',      description: 'Everyone has it on their calendar.',    })    return pause(400)  }  const search = (q: string) =>    Promise.resolve(      TEAM_MORE.filter((p) =>        p.name.toLowerCase().includes(q.toLowerCase()),      ),    )  return (    <>      <MeetingPlanner        date={TODAY}        attendees={TEAM_ROOM}        loadAttendees={search}        title="Capacity review"        link="meet.kerf.dev/capacity"        tags={['Internal', 'Incident', 'Planning']}        timeZone={TEAM_ZONE}        today={TODAY}        locale="en-GB"        hourCycle="h23"        onSubmit={sent}        onUndo={() =>          toast.show({ title: 'Invite withdrawn' })        }      />      <Toaster />    </>  )}const TODAY = '2026-11-03'const TEAM_ZONE = 'Europe/Stockholm'const TEAM_ROOM: Attendee[] = [  {    id: 'mara',    name: 'Mara Lindqvist',    organizer: true,    zone: TEAM_ZONE,    busy: [      [540, 555],      [600, 660],      [780, 825],    ],  },  {    id: 'jun',    name: 'Jun Tanaka',    zone: 'Asia/Tokyo',    hours: [540, 1080],    busy: [      [540, 555],      [780, 825],    ],  },  {    id: 'priya',    name: 'Priya Raman',    zone: 'Asia/Kolkata',    hours: [540, 1080],    busy: [      [600, 660],      [870, 930],    ],  },  {    id: 'sam',    name: 'Sam Okafor',    zone: 'Africa/Lagos',    hours: [540, 1080],    busy: [      [690, 750],      [780, 825],    ],  },]const TEAM_MORE: Attendee[] = [  {    id: 'lena',    name: 'Lena Fischer',    zone: 'Europe/Berlin',    busy: [[900, 960]],  },  {    id: 'tomas',    name: 'Tomás Oliveira',    zone: 'Europe/Lisbon',    busy: [[600, 660]],  },]const pause = (ms: number) =>  new Promise((done) => {    setTimeout(done, ms)  })

Installation

npm install @kerf-js/veneer

Usage

tsx
import { MeetingPlanner } from '@kerf-js/veneer'

Accessibility

  • The suggested times are a radio group, and the availability is a table, so it reads without the colours.

API Reference

PropTypeDefaultDescription
datestring—The day to plan.
attendeesAttendee[]—Who is invited, with their busy ranges in minutes.
loadAttendees((q: string, signal: AbortSignal) => Promise<Attendee[]>)—Search for people to add.
loadSlots((input: { attendees: Attendee[]; date: string; duration: number; }) => …—Your own suggestions, in place of working them out from busy.
durationnumber—The meeting's starting length, in minutes. Default 30.
durationsnumber[]—The lengths on offer.
titlestring—The heading.
linkstring—The meeting link the form starts with, such as a video call URL.
tagsstring[]—Labels to pick from.
fromnumber—The strip's hours, minutes since midnight.
tonumber—Where the strip's hours end, in minutes since midnight.
timeZonestring—The IANA zone times are shown in, such as Europe/Berlin.
todaystring—Pins today's date, for server rendering and tests. The browser's clock otherwise.
localestring—The language and region used to format values.
hourCyclestring—h12 for AM and PM, h23 for a 24-hour clock. The locale's otherwise.
EventTypeDefaultDescription
onSubmit(m: MeetingDetails) => void | Promise<unknown>—Send was pressed.
onUndo(() => void)—Adds Undo after sending.

Parts

Every element carries a stable name, so you can style it from your own CSS: [data-scope="meeting-planner"] [data-part="eyebrow"]

eyebrow

RelatedBooking calendarAvailability editor