Skip to content
WingoUI
ComponentsTemplatesDocsPricingAI agentsBlogChangelogContact
Components
Buttons & Actions
ButtonButton GroupCopy ButtonDelete ButtonTheme ToggleToggleToggle Group
Inputs
Avatar UploadCalendarCheckboxColor PickerComboboxCountry SelectCron PickerCurrency InputDate PickerDate Range PickerDate Time PickerDuration PickerEmail InputEmoji PickerFile UploadFilter BuilderInputInput GroupListboxMasked InputMention InputMonth PickerMulti SelectNumber InputOTP InputPassword InputPhone InputRadio GroupRatingSearch InputSegmented ControlSelectSignature PadSliderSwitchTag InputTextareaTime PickerTimezone SelectTree Select
Forms
Address InputBooking WidgetCSV ImportFieldForm WizardInline EditInvoice line itemsRich Text EditorSchema Builder
Navigation
Back To TopBounce SidebarBreadcrumbCommand MenuGlobal SearchGooey NavHook SidebarNav MenuNav TabsPaginationProximity SidebarScroll ProgressSidebarStepsTable Of ContentsTabsTopbarUser MenuWorkspace Switcher
Overlays
Alert DialogContext MenuDialogDrawerDropdown MenuFamily DrawerHover CardPopoverShare DialogSheetShortcuts DialogTooltipTour
Feedback
AlertBannerCookie ConsentEmoji ReactionEmpty StateNotification BellNotification CenterProgressRoute ProgressSetup ChecklistSkeletonSpinnerToast
Data Display
Approval FlowAvatarBadgeCalendar ViewCode BlockComment ThreadContact cardCountdownDescription ListDiff ViewerFile ListFile managerFolderGanttJSON ViewerKbdLog ViewerMarkdownPresenceSchedulerTagTimelineTree View
Tables & Lists
Audit LogData TableDeal PipelineEditable GridKanbanListMembers tablePermissions MatrixTableTask ListVirtual List
Charts & Stats
Animated CounterArea chartBar chartDonut chartFunnel chartGaugeGitHub ActivityHeatmap calendarLeaderboardLine chartRadar chartRadial barSparklineStat CardTrackerTrend badge
Layout
AccordionApp ShellCardCollapsibleDetail LayoutEntity HeaderPage HeaderScroll AreaSeparatorSettings LayoutSplit ViewToolbar
Media
Audio PlayerCarouselCSV ViewerDOCX EditorDOCX ViewerExcel ViewerFile SystemImage compareImage CropperImage GalleryImage LightboxLocation MapPDF ViewerPowerPoint ViewerQR CodeStep PlayerVideo Player
AI Kit
Agent Status CardChat MessageChat ThreadChat WidgetConversation ListFluid OrbGrid RevealMatrix OrbModel PickerPrompt InputReasoning IndicatorRule BuilderSuggestion ChipsTool Call TimelineUsage MeterVoice InputWorkflow Canvas
Text & Effects
Background PatternGravity LettersMarqueeTerminalText AnimateText Rotate
Mobile
Action SheetBottom Tab BarFABHaptic ButtonHero HeaderInstall PromptLarge Title HeaderNotification BannerOnboarding CarouselPicker WheelPull To RefreshSlide To ConfirmSticky CTA BarSwipe DeckSwipeable Tabs
Commerce
Cart drawerCategory pickerCheckout stepsCoupon inputFavorite buttonFilter panelListing cardListing gridOrder trackerPricePrice rangePricing tableProduct galleryQuantity stepperReviewsSeller card
Marketing Sections
App Download SectionBlog Cards SectionCTA SectionFAQ SectionFeature GridFeature ShowcaseHero SectionIntegrations SectionLogo CloudMarketing FooterMarketing HeaderMarketing SectionNewsletter SectionPricing SectionStats SectionSteps SectionTeam SectionTestimonials SectionTimeline SectionVideo Section
Blocks
Admin Dashboard PageAdmin ShellAI Chat PageAPI Keys PageAutomation Builder PageCheckout PageCRM Contact PageCRM Pipeline PageError PagesInbox PageInvoice PageKPI DashboardListing Detail PageLogin PageMarketplace Home PageMarketplace Search PageMarketplace ShellMobile App HomeOnboarding FlowOrder History PagePost Listing WizardPricing PageProfile PageRegister PageSettings PageUsers Admin Page
Templates
About PageAcademy PageAccessibility PageAffiliate PageAgency PageAI Product PageAppointment Booking PageBlog PageBlog Post PageBug Bounty PageCareers PageChangelog PageCommunity PageCompare PageContact PageCustomer Story PageCustomers PageDemo Booking PageDevelopers PageDocs Home PageDownload PageEnterprise PageEvents PageExperts PageFAQ PageFeature PageGlossary PageHelp Article PageHelp Center PageHome Services PageImpact PageIntegration Detail PageIntegrations PageJob Detail PageLanding PageLaunch PageLegal PageManifesto PagePortfolio PagePress PageProduct Tour PagePromo PageReal Estate PageReferral PageResearch Report PageRestaurant PageRoadmap PageROI Calculator PageSecurity PageSolutions PageStartups PageStatus PageStorefront PageSwitch PageTemplate Gallery PageTravel PageWaitlist PageYear in Review Page
Hooks & Utilities
AI ChatChart kitCommerceDate utilsFile exportFile typesFiltersForm validationHapticsInvoicingMotion presetsNavigationNumber formatRelative timeRomanian locationsSpreadsheetText matchTonesUIProvideruseClipboarduseConsentuseControllableStateuseCooldownuseDebounceuseKeyboardInsetuseLocaleuseLongPressuseMediaControlsuseMediaQueryuseOverflowItemsusePersistedStateuseScrollEdgesuseScrollStateuseSelectionuseShortcutuseStickToBottom
GalleryTheme

Data Display

Scheduler

Data Display

Scheduler React component for Tailwind CSS v4

PresenceTag
Live props
Layout
defaultView
mobileView
layout
dayStartHour8
dayEndHour20
slotWidth48px
dayWidth160px
rowHeight56px
resourceWidth220px
height640px
weekStartsOn-1
hideWeekends
groupResources
Behavior
slotMinutes
editable
allowOverlap
locale
Appearance
showNowIndicator
showUtilization
toolbar
tone
variant
radius
State
loading
Code
import { Scheduler } from "@/components/ui/scheduler"

<Scheduler />
  1. Home
  2. Components
  3. Data Display
  4. Scheduler
Pro

Scheduler React component for Tailwind CSS v4

v1.0.0·Added in 1.0.0

A React resource scheduler to book rooms, cars and people on a timeline: drag to book, move and resize, conflicts refused, agenda on phones.

Resources run down the side in collapsible groups (a click on a group heading folds it) and time runs across: a day in 15, 30 or 60 minute slots, or a week of days, with the hours outside someone's working day shaded, unavailable time hatched with its reason, a red now line and a thin utilization bar under every name. The toolbar steps a day or a week back or forward, Today jumps home and the switch picks Day, Week or Agenda; a click on a day in the week header opens that day. With a mouse, drag across free slots to book: the range snaps to the slots, stops at the next booking and opens a quick-book form under it (title focused, Enter saves, Escape cancels). Drag a booking to another time or resource: it follows the pointer, a ghost shows where it will land (red when that time is taken) and it settles into the slot on release, or shakes and springs back with the reason announced when the time is taken or unavailable. Drag either edge to stretch it; it stops at its neighbours. Near the edges the grid scrolls by itself, and Escape cancels any drag. Click a booking for its details with Edit and Cancel booking. Keyboard: the grid is one Tab stop; Left and Right move through the free slots and bookings of a row (a day at a time in the week view), Up and Down move between resources and group headings (Left and Right fold a heading), Home and End jump to the ends of the row (with Ctrl or Cmd to the first or last resource), PageUp and PageDown go a day or a week back or forward, Shift with Left or Right selects more slots and Enter or Space books them, Enter on a booking opens it, Alt with the arrows moves the focused booking a slot or a resource at a time and Alt with Shift with Left or Right changes where it ends. Moves, stretches, new bookings and conflicts are announced. Touch: the grid scrolls freely; a tap on a free slot opens the booking sheet and a long press on a booking picks it up to move it. When the scheduler is narrower than 576px it becomes an agenda: a strip of the week's days (dots mark busy days), the day's bookings grouped by resource with their times, the resources free all day as chips that book them, and a full-width Book button; bookings open as a bottom sheet with Edit and Cancel booking, and the booking form is a sheet too. Reduced motion turns the settles and slides into instant changes and drops the shake.

Scheduler is a Pro React component in the React data display components category, written in TypeScript for React 19 and Tailwind CSS v4. It installs as source with npx wingo-ui@latest add scheduler, together with Avatar, Badge, Button, Calendar View, Empty State and 16 more and the npm packages lucide-react and motion.

Add to a project

Requirements: React 19 and Tailwind CSS v4, in Next.js (App Router or Pages), Vite or React Router. Installation guide · Next.js setup · Vite setup

npx wingo-ui@latest add scheduler

Scheduler is part of Pro, with 298 more Pro items: $8 a month or $150 once. 14-day refund before you download any Pro item. See plans

Usage

Import Scheduler from @/components/ui/scheduler.

import { useState } from "react"
import { Car, Presentation } from "lucide-react"
import { Scheduler, type SchedulerEvent, type SchedulerResource } from "@/components/ui/scheduler"

const resources: SchedulerResource[] = [
  { id: "hudson", name: "Hudson room", description: "8 seats · screen", group: "Rooms", icon: <Presentation />, tone: "info" },
  { id: "mihai", name: "Michael Reed", avatar: "/demo/mihai-ionescu.jpg", group: "Team", workingHours: { start: 9, end: 18 } },
  { id: "corolla", name: "Toyota Corolla", description: "NY 12 ACM", group: "Cars", icon: <Car /> },
]

const bookings: SchedulerEvent[] = [
  { id: "r1", resourceId: "hudson", title: "Tech Solutions demo", start: new Date(2026, 8, 24, 10, 0), end: new Date(2026, 8, 24, 11, 30) },
  { id: "r2", resourceId: "mihai", title: "BuildRight Group negotiation", start: new Date(2026, 8, 24, 11, 0), end: new Date(2026, 8, 24, 12, 0), status: "tentative" },
  { id: "r3", resourceId: "corolla", title: "Client visit, Denver", start: new Date(2026, 8, 24, 15, 0), end: new Date(2026, 8, 24, 17, 30) },
]

export function Bookings() {
  const [events, setEvents] = useState(bookings)
  return (
    <Scheduler
      resources={resources}
      events={events}
      defaultDate={new Date(2026, 8, 24)}
      onEventChange={(event, change) =>
        setEvents((list) => list.map((e) => (e.id === event.id ? { ...e, ...change } : e)))
      }
      onCreateEvent={(input) => setEvents((list) => [...list, { id: crypto.randomUUID(), ...input }])}
      onEventCancel={(event) =>
        setEvents((list) => list.map((e) => (e.id === event.id ? { ...e, status: "cancelled" } : e)))
      }
      labels={{
        book: "Reserve",
        newBooking: "New reservation",
        conflict: (resource) => `${resource} is already reserved then`,
      }}
    />
  )
}

Props

  • resources*
    SchedulerResource[]

    The rows: { id, name, avatar?, icon?, description?, group?, capacity?, tone?, color?, unavailable?: { start, end, label? }[], workingHours?: { start, end } }. People get their photo or initials, things their icon in a tile. Outside workingHours (whole hours) the lane is shaded, weekends too; unavailable stretches are hatched with their label and refuse every booking.

  • eventsdefault "[]"
    SchedulerEvent[]

    The bookings: calendar-view's CalendarEvent (id, title, start, end, allDay?, tone?, color?, location?, description?, attendees?, editable?, meta?) plus resourceId and status?: "confirmed" | "tentative" | "cancelled". Tentative ones are dashed, cancelled ones untinted, muted and struck through, and they never block. A resourceId that is not in resources lands in a "No resource" row. The file also exports findConflicts(events, { start, end, resourceId, id? }): the bookings of that resource that overlap the candidate (cancelled ones never do), to check a booking on the server before it is saved.

  • defaultViewdefault "day"
    "day" | "week" | "agenda"

    The view it opens in: the day in slots, the week in days (bookings keep their place in each day's hours), or the agenda of one day.

  • view
    "day" | "week" | "agenda"

    The view, controlled; pair it with onViewChange.

  • onViewChange
    (view: SchedulerView) => void

    When the switch, a week header day or the phone switch changes the view.

  • viewsdefault "["day","week","agenda"]"
    SchedulerView[]

    The views the switch offers, in order. One view hides the switch; a day in the week header only opens the day view when it is listed.

  • date
    Date

    The day on screen (any day of the week in the week view), controlled; pair it with onDateChange.

  • defaultDate
    Date

    The day it opens on. Left out, the server renders the first booking's day and the scheduler moves to today (from now) once the clock is known; with neither, it shows skeleton lanes until then.

  • onDateChange
    (date: Date) => void

    When the arrows, Today, PageUp / PageDown, the day strip or a week header day change the day.

  • mobileViewdefault "agenda"
    "agenda" | "day"

    What it shows when it is narrower than 576px (a container query, so a narrow side panel gets it too and the server HTML is already right on a phone): the agenda with the day strip and the Book button, or the day grid scrolling sideways with the names pinned, for tablets in portrait.

  • layoutdefault "responsive"
    "responsive" | "desktop" | "mobile"

    responsive switches to the narrow layout under 576px of its own width; desktop and mobile force one, e.g. a phone frame on a wide screen.

  • dayStartHourdefault 8
    number

    The first visible hour. An end at or before the start falls back to 8 to 20.

  • dayEndHourdefault 20
    number

    The hour the visible day ends at. Bookings that run past either edge are cut with an arrow and read as continuing.

  • slotMinutesdefault "30"
    15 | 30 | 60

    The slot: what dragging, stretching and booking snap to, and one column of the day view. Hour lines are stronger than the ones between.

  • slotWidthdefault 48
    number

    px per slot in the day view; the slots stretch when the scheduler is wider than they need.

  • dayWidthdefault 160
    number

    px per day in the week view.

  • rowHeightdefault 56
    number

    px per resource lane (at least 56px on touch screens, so a booking is a 44px target). A row of overlapping bookings grows by this much per stack.

  • resourceWidthdefault 220
    number

    px of the pinned resource column; it never takes more than 42% of a narrow scheduler.

  • heightdefault 640
    number

    The most the grid (and the agenda list) grows to before it scrolls, in px. The time header and the names stay pinned while it scrolls both ways. When narrow it also stays within 75% of the screen (the agenda list 60%), so the Book button is always in sight.

  • weekStartsOndefault -1
    0 | 1 | 2 | 3 | 4 | 5 | 6 | -1

    The first day of the week view and the day strip: 0 is Sunday, 1 Monday ... 6 Saturday; -1 follows the locale (Sunday in en-US, Monday in en-GB).

  • hideWeekendsdefault false
    boolean

    Leaves the locale's weekend out of the week view and the day strip.

  • groupResourcesdefault true
    boolean

    Groups the rows under their resource.group, in the order the groups first appear, each heading folding its rows away.

  • defaultCollapsedGroupsdefault "[]"
    string[]

    Group names that start folded.

  • showNowIndicatordefault true
    boolean

    The red line at the current time, with a dot in the time header. It moves once a minute.

  • showUtilizationdefault true
    boolean

    A thin bar and a percentage under every name: the booked share of its working hours in view, unavailable time left out.

  • toolbardefault true
    boolean

    The title, the arrows, Today and the view switch. Off, a page can drive view and date itself.

  • tonedefault "neutral"
    "neutral" | "brand" | "success" | "warning" | "danger" | "info"

    The color of bookings that have no tone or color of their own or from their resource, and of the drag-to-book selection, the utilization bars and today's dot.

  • variantdefault "surface"
    "surface" | "outline" | "plain"

    surface: a raised panel. outline: a hairline border on the page. plain: no container, for a page that is only the scheduler.

  • radiusdefault "md"
    "none" | "sm" | "md" | "lg" | "full"

    The panel's corners: 0 / 12 / 16 / 24px (full reads as lg).

  • editabledefault true
    boolean

    Drag and stretch bookings, drag across free time to book (with onCreateEvent or onSlotSelect), and Edit / Cancel booking in the details. Off, it is an availability view: bookings still open their details. A booking with editable: false stays put either way.

  • allowOverlapdefault false
    boolean

    Off, a drop, a stretch, a selection or a form save that overlaps another booking of the same resource is refused (the selection stops at the neighbour). On, people can be double-booked and overlapping bookings stack in lanes that grow the row. Unavailable time refuses bookings either way.

  • loadingdefault false
    boolean

    Skeleton lanes in the real row shape (skeleton rows in the agenda); the toolbar keeps working.

  • onEventChange
    (event: SchedulerEvent, change: SchedulerChange) => (void | false | Promise<unknown>)

    After a move, a stretch, an Alt+arrow or the edit form: change is { start, end, resourceId, title? }. The booking moves at once; false or a rejected promise puts it back (announced). Without it moves stay on screen until events change.

  • onCreateEvent
    (input: SchedulerCreateInput) => (void | Promise<unknown>)

    The quick-book form's Save, with { start, end, resourceId, title } (an empty title saves as labels.titlePlaceholder). A promise keeps Save loading and a rejection shows the error in the form. Needed, with editable, for booking from the grid and the Book button.

  • onSlotSelect
    (slot: SchedulerSlot) => void

    Replaces the built-in form: a drag across free time, a click, a tap or Enter on a slot (and the Book button, with a suggested free slot) calls it with { start, end, resourceId }.

  • onEventClick
    (event: SchedulerEvent) => void

    Replaces the details popover (and sheet) when a booking is clicked or tapped.

  • onEventCancel
    (event: SchedulerEvent) => (void | Promise<unknown>)

    Adds Cancel booking to the details of bookings that are not cancelled yet; a promise keeps the button loading.

  • renderEvent
    (event: SchedulerEvent, info: { view: SchedulerView; resource: SchedulerResource | null }) => ReactNode

    Replaces the title and time inside a booking bar (the bar, its color, drag and focus stay).

  • renderEventDetails
    (event: SchedulerEvent) => ReactNode

    Replaces the rows of the details (time, resource, location, attendees); the title, the status and the actions stay.

  • renderResource
    (resource: SchedulerResource, info: { utilization: number | null }) => ReactNode

    Replaces what the pinned resource cell shows; utilization is 0 to 1, or null without working time in view.

  • toolbarActions
    ReactNode

    Extra actions at the end of the toolbar, e.g. an "Export" button or a filter.

  • now
    Date | number

    The current time for today, the now line and the Book button's suggestion. Defaults to a live clock that ticks every minute; demos pin 24.09.2026 16:40.

  • localedefault ""
    string

    Hours (AM/PM in en-US, 24h in en-GB), day names and the week start. Empty uses the UIProvider locale, then the browser's.

  • labels
    Partial<SchedulerLabels>

    Every string, English by default: today, previous, next, day, week, agenda, views, resources, caption(period), keyboardHint, book, newBooking, editBooking, title, titlePlaceholder, resource, date, start, end, save, cancel, edit, cancelBooking, close, details, location, attendees, allDay, confirmed, tentative, cancelled, unavailable, noResource, noResources, noBookings, freeAllDay, continues, loading, previousWeek, nextWeek, capacity(count), bookings(count, formatted), utilization(percent), slot(resource, time), timeRange(start, end), event({ title, resource, time, status }), conflict(resource), unavailableConflict(resource), invalidRange, moved(title, resource, time), resized(title, time), created(title, resource, time), cancelledBooking(title), failed(title). DEFAULT_SCHEDULER_LABELS has them all, to spread under a translation.

  • classNames
    Partial<Record<"toolbar" | "title" | "nav" | "switch" | "actions" | "body" | "grid" | "header" | "corner" | "group" | "row" | "resource" | "utilization" | "lane" | "slot" | "event" | "unavailable" | "selection" | "now" | "agenda" | "dayStrip" | "agendaEvent" | "booking" | "details", string>>

    Extra classes for the parts. booking goes on the quick-book panel, details on the details panel.

  • className
    string

    Extra classes for the root, merged last. The rest of the props land on the root too.

Parts

  • findConflicts

    function

    findConflicts(events, { start, end, resourceId, id? }): the bookings of that resource that overlap the candidate, cancelled ones excluded. Use it to check a booking on the server before saving it.

  • DEFAULT_SCHEDULER_LABELS

    constant

    The English strings for the toolbar, the booking and details panels, statuses, empty states and every screen reader announcement. Override keys through labels.

  • SchedulerResource

    type

    A row of the timeline (a room, a car or a person): { id, name, avatar?, icon?, description?, group?, capacity?, tone?, color?, unavailable?, workingHours? }.

  • SchedulerEvent

    type

    A booking: a calendar event plus resourceId and an optional status ("confirmed", "tentative" or "cancelled").

  • SchedulerChange

    type

    What a create, move or resize hands you: { start, end, resourceId, title? }.

  • SchedulerProps

    type

    The props of Scheduler, for wrappers that load resources and bookings from your API.

Examples

Week without weekends

defaultView="week" with hideWeekends: rooms and cars across five days.

Clinic in 15-minute slots

slotMinutes={15} with narrow slots, two dentists and the X-ray room.

Double booking

allowOverlap stacks overlapping bookings in lanes instead of refusing them.

Read-only availability

editable={false} on an outline panel: look, do not touch.

Agenda on a phone

layout="mobile": the day strip, bookings grouped by resource and the Book button.

Loading

Skeleton rows keep the layout still; Reload plays it again.

Dependencies

lucide-reactmotionavatarbadgebuttoncalendar-viewempty-stateinputpopoversegmented-controlselectskeletontime-pickertooltipuse-controllable-stateuse-localeuse-long-pressdate-utilsmotionnumber-formatrelative-timetonesui-config

Files

  • components/ui/scheduler.tsx

FAQ

Is the React Scheduler component free?

The preview, props and docs are free; copying the source needs a Pro plan. See plans.

How do I install Scheduler?

Run npx wingo-ui@latest add scheduler. It also adds the Wingo UI items it builds on (Avatar, Badge, Button, Calendar View, Empty State and 16 more) and the npm packages lucide-react and motion. With shadcn, add the @wingo-ui registry with your API key, then run npx shadcn@latest add @wingo-ui/scheduler.

How does Scheduler behave on phones?

When the scheduler is narrower than 576px it becomes an agenda: a strip of the week's days (dots mark busy days), the day's bookings grouped by resource with their times, the resources free all day as chips that book them, and a full-width Book button; bookings open as a bottom sheet with Edit and Cancel booking, and the booking form is a sheet too.

See also

Calendar ViewTime PickerDate Picker

More React data display components

TagTimelineTree ViewKbdBadgeFile manager
All 23 React data display components

Articles using this component

  • React Resource Scheduler: Drag to Book Rooms and Cars

    Component guides · Oct 16, 2026 · 10 min read

PreviousPresenceNext Tag

Thursday, September 24, 2026

Thursday, September 24

Arrow keys move between time slots and bookings, Enter books a free slot or opens a booking. Shift with the arrows selects more slots, Alt with the arrows moves a booking and Alt with Shift resizes it.
Resources
8:00 AM
9:00 AM
10:00 AM
11:00 AM
12:00 PM
1:00 PM
2:00 PM
3:00 PM
4:00 PM
5:00 PM
6:00 PM
7:00 PM
Hudson room13%
8 seats · screen13% booked
Charles room17%
4 seats17% booked
Potomac room10%
12 seats · projector10% booked
Maintenance
Michael Reed22%
Sales · manager22% booked
Ryan Mitchell22%
Sales22% booked
Olivia Stone19%
Support · 9 to 519% booked
Daniel Parker
Logistics
Time off
Toyota Corolla21%
NY 12 ACM21% booked
Nissan Leaf17%
NY 34 ACM · electric17% booked
  • Hudson room

    8 seats · screen1 booking
  • Charles room

    4 seats2 bookings
  • Potomac room

    12 seats · projector1 booking
    • 2:00 PM4:00 PMMaintenanceUnavailable
  • Michael Reed

    Sales · manager2 bookings
  • Ryan Mitchell

    Sales2 bookings
  • Olivia Stone

    Support · 9 to 51 booking
  • Daniel Parker

    Logistics0 bookings
    • All dayTime offUnavailable
  • Toyota Corolla

    NY 12 ACM1 booking
  • Nissan Leaf

    NY 34 ACM · electric1 booking

Drag across free time to book, drag a booking to another time or resource, stretch its edges. Sala Olt is in maintenance from 14:00 to 16:00 and Daniel Parker is on leave, so drops there are refused.

last change: none · mobileView agenda · weekStartsOn -1 · hideWeekends false · dayWidth 160px · allowOverlap false · editable true