Xzero UI
Pro library

Smart schedulerPro

A scheduling card where one sentence like "Find 30 min with Maya and Leo next week, mornings" is read into chips. The three best slots are then ranked across everyone's Google Calendar and Outlook calendars, with an availability strip per person and conflicts called out. Picking a slot opens a draft invite with a Google Meet link, ready to send.

$ pnpm dlx shadcn@4.23.0 add @xzero-ui-pro/smart-scheduler
Live · keyboard ready

Drop it into a CRM, recruiting tool or team app as the "find a time" step. Pass parse for your LLM call (structured output: duration, people, range, window) and keep findSlots deterministic on your server (Google freeBusy or Microsoft Graph getSchedule). Each slot returns every attendee's busy blocks for that day so the strips can draw them. onSend creates the event (reject to show a retry), and onPick plus meetLink let you create a real Meet link before sending. With no props it plays a simulated run on a loop until someone clicks or types in the card. The sample parser and slot finder (parseScheduleRequest, findScheduleSlots) are exported as a reference.

When to use

When not to use

Installation

Register both Xzero UI namespaces in your project's components.json:

{
  "registries": {
    "@xzero-ui": "https://mcp.xzero.co.uk/r/{name}.json",
    "@xzero-ui-pro": "https://mcp.xzero.co.uk/r/pro/{name}.json"
  }
}
npx shadcn@4.23.0 add @xzero-ui-pro/smart-scheduler

Install xzero-foundation once, and import @/components/xzero/foundation.css in your app root. No Xzero token is used by this snapshot. For sample portraits, photos, fonts, or logos, download https://mcp.xzero.co.uk/install-assets.mjs and run node xzero-install-assets.mjs <receiving-project>.

Usage

import { SmartScheduler } from "@/components/xzero/blocks/smart-scheduler/smart-scheduler";

export function ScheduleCard({ me }) {
  return (
    <SmartScheduler
      organizer={me}
      defaultQuery=""
      timeZone="ET"
      parse={(query, signal) =>
        fetch("/api/schedule/parse", { method: "POST", body: JSON.stringify({ query }), signal }).then(async (res) => {
          if (!res.ok) throw new Error((await res.json()).message ?? "Couldn't read that request.");
          return res.json();
        })}
      findSlots={(request, signal) =>
        fetch("/api/schedule/slots", { method: "POST", body: JSON.stringify(request), signal }).then((res) => res.json())}
      onSend={(invite, signal) =>
        fetch("/api/schedule/invite", { method: "POST", body: JSON.stringify(invite), signal }).then((res) => {
          if (!res.ok) throw new Error("Not sent");
        })}
    />
  );
}

API reference

SmartScheduler

Natural language scheduling in one card. A request such as "Find 30 min with Maya and Leo next week, mornings" is read into chips (duration, people with their avatars, range, time of day), then the three best slots are ranked across everyone's calendars. Above the list, one availability strip per person (with a Google Calendar or Outlook mark) draws that day's busy blocks, and an outlined column marks the slot under the pointer, sliding as you move between suggestions. Conflicts are called out with a warning icon and a short reason ("Leo's Arch office hours is tentative") and tint the block they overlap. Picking a slot opens a draft invite in the same space: an editable title, the time, a Google Meet link with a copy button, the guests, and Send invite, which turns into Sent. With no props it plays a simulated run on a loop until someone interacts.

PropTypeDefaultDescription
defaultQuerystring"Find 30 min with Maya and Leo next week, mornings"The request in the field on first render.
placeholderstring"Find 30 min with Maya next week"Placeholder for the empty field.
organizerSchedulerPersonSofia Ramirez (sample)The person sending the invite: { id, name, avatar?, email?, calendar?: "google" | "outlook" }. Shown as "You" in the strips.
directorySchedulerPerson[]sample directoryPeople the built in parser can match by first name. Ignored when you pass parse.
parse(query: string, signal: AbortSignal) => Promise<ScheduleRequest>–Turns the request into { duration (min), people, range: { label, detail, days: { id, label }[] }, window: { id, label, start, end } } (minutes from midnight). Wire your LLM call with structured output here. Throw an Error with a short message to show it in the card. A new request aborts the signal.
findSlots(request: ScheduleRequest, signal: AbortSignal) => Promise<SlotSuggestion[]>–Returns ranked slots, best first; the first three are shown. Each is { id, day, start, end, reason, conflicts: { personId, title, tentative? }[], busy: Record<personId, BusyBlock[]> }. busy feeds the strips. An empty array shows the no time works state.
onSend(invite: InviteDraft, signal: AbortSignal) => Promise<void>–Sends { title, slot, organizer, people, meetLink, timeZone }. Reject to show Couldn't send with the button ready to retry. Without it the send is simulated and the card says nothing was sent.
onPick(slot: SlotSuggestion) => void–Called when a slot is picked and the draft opens.
meetLinkstring"meet.google.com/kfq-rvwp-zte"Video link on the draft. Create a real one in onPick (Google Calendar conferenceData) and pass it back.
timeZonestring"ET"Short time zone label shown beside the window and the invite time.
autoPlaybooleantrue without parse, findSlots and onSendPlays read, preview, pick and send on a loop. Stops for good on the first click or key press in the card, pauses off screen, and is off under reduced motion.
classNamestring–Class on the root element.

BusyBlock

One event on an attendee's day, as drawn in the strips.

PropTypeDefaultDescription
start (required)number–Minutes from midnight.
end (required)number–Minutes from midnight.
title (required)string–Event title, used in the block's tooltip, the strip's label and conflict reasons.
tentativeboolean–Drawn with a dashed outline and treated as a soft conflict.

Keyboard interactions

KeysAction
EnterIn the field, reads the request and finds times.
TabMoves from the field to Find times, then to the suggested slots, or through the draft's title, copy link, Back and Send invite.
Arrow up / Arrow downMoves between the suggested slots; focusing a slot previews it in the strips.
Enter / SpaceOn a slot, opens the draft invite.
EscapeIn the draft, goes back to the suggestions.

Accessibility

Motion

Responsive behavior

Performance

Notes for AI

Guidance for AI tools

Blocks are complete, self-contained screens with sample data. Replace the sample data and connect the callbacks described above. Follow the declared prop types and do not invent props. Keep keyboard access, reduced motion support, and both light and dark themes intact when adapting it.

Full library index: https://ui.xzero.co.uk/llms.txt

Browse all components