component — date & time
Week View
Seven days on one time axis — or five for the work week — with overlapping meetings packed into columns per day, an all-day band with "+N more" overflow, and the now-marker on today. The week starts where the locale says it does. With isEventEditable and onEventChange an event can be dragged — or arrow-keyed — to another time or another day, snapped to the slot grid; the view reports the drop and your state decides.
- calendar
- week
- agenda
- events
- schedule
- outlook
08 examples
Default
Seven days on one axis. The week starts where the locale says it does, and today's column carries the now-marker.
Work week
visibleDays={5} cuts the week to its working days. The chevrons still step a whole week, so the view cannot drift onto a Saturday.
All-day band and overflow
Multi-day events stack into lanes above the grid. With more lanes than maxAllDayLanes allows, the last row becomes a "+N more" — and a band that would not fit on every day it covers is folded away whole rather than cut short.
Click handler
onEventClick fires on every activation and receives the event object you passed in, so you can open your own detail panel from it.
Click an event — onEventClick hands you the whole event.
Jumping to another week
The heading is a picker, and it has been the default since the alternative was Today or one chevron press at a time. It opens the unit the heading is spelled in — a week grid, where the row is the target — because every day of a row leads to the same view. pickerGranularity="day" asks for a day instead, and labelVariant="static" turns the heading back into text.
The heading names a week, so the grid it opens offers weeks: the row is the target and the ISO number is in the gutter.
Where the legend goes
Placement is layout, so the legend takes no prop for it: it is a child, and a class puts it before the view, after it, or in a column beside it. Over the grid is the one case that needs the library — variant="overlay" draws the same row on an elevated surface, because a bare row of small muted text with events behind it is no longer a legend.
Overlay in the corner the week leaves free
The same overlay in the other corner, with the hour axis and a column of events behind it. The surface is translucent and blurred, so the key stays readable without hiding what it covers — and which corner it takes is yours, one absolute class.
Drag to move
isEventEditable says which events may be picked up and onEventChange reports where one was dropped, snapped to the slot grid and never outside the hours the axis draws. The arrow keys do the same thing from the keyboard — a slot up or down, a day left or right — and the ghost shows where the event is about to land. Your state decides whether it actually lands there.
Drag an event to another time or another day — or tab to one and use the arrow keys.
source
week-view.tsxCopy this into your project. Resolve its dependencies from the registryDependencies in the component's API entry.
"use client"
import type { CalendarDate } from "@internationalized/date"
import {
type CalendarEvent,
type CalendarGridViewProps,
CalendarShell,
DEFAULT_CALENDAR_TIME_ZONE,
} from "@/components/calendar-shell"
import {
calendarRangeLabel,
type CalendarToolbarLabelVariant,
CalendarToolbar,
type CalendarViewName,
type CalendarViewOption,
useCalendarLocale,
useCalendarNavigation,
} from "@/components/calendar-toolbar"
import { weekRange } from "@/lib/calendar"
import { cn } from "@/lib/utils"
/**
* WeekView — quebi design system
*
* Seven days on one time axis, with overlapping meetings packed into side-by-side
* columns per day, an all-day band that folds into "+N more" for calendars that
* have all-day events, and the now-marker on today's column. `visibleDays={5}`
* is the work week; the week still starts where the locale says it does, so the
* five days are Monday to Friday under `de-DE` and Sunday to Thursday under
* `en-US` unless `firstDayOfWeek` says otherwise.
*
* The toolbar's heading is a week picker: the label names a week, so the grid
* it opens offers whole weeks rather than asking which of the seven days you
* meant when all seven lead to the same view.
*
* Display, selection, and — given `isEventEditable` and `onEventChange` —
* dragging an event to another time or another day, by pointer or by arrow key.
* The machinery for that is `CalendarShell`'s; see the note there, including
* what it will and will not let you drop. This file decides which seven days,
* and nothing else.
*/
export interface WeekViewProps<E extends CalendarEvent = CalendarEvent>
extends CalendarGridViewProps<E> {
/** Any day in the week on show. Controlled. */
date?: CalendarDate
/** Any day in the initial week. Defaults to today; pin it on a prerendered route. */
defaultDate?: CalendarDate
onDateChange?: (date: CalendarDate) => void
/** How many days from the start of the week. 5 is the work week. Default 7. */
visibleDays?: number
/** Override the locale's first day — 0 is Sunday. */
firstDayOfWeek?: "sun" | "mon" | "tue" | "wed" | "thu" | "fri" | "sat"
showToolbar?: boolean
view?: CalendarViewName
views?: readonly CalendarViewOption[]
onViewChange?: (view: CalendarViewName) => void
label?: React.ReactNode
/** The toolbar's date label as a picker, or as plain text. Default "picker". */
labelVariant?: CalendarToolbarLabelVariant
/**
* Which grid that picker opens. Default "week".
*
* The heading names a week, so a week is what it offers — the row is the
* target and the ISO number is in the gutter. `day` is there for a view whose
* reader is really choosing a day and looking at its week around it.
*/
pickerGranularity?: "day" | "week"
}
export function WeekView<E extends CalendarEvent = CalendarEvent>({
date,
defaultDate,
onDateChange,
visibleDays = 7,
firstDayOfWeek,
showToolbar = true,
view,
views,
onViewChange,
label,
labelVariant = "picker",
pickerGranularity = "week",
timeZone = DEFAULT_CALENDAR_TIME_ZONE,
locale: localeProp,
className,
...shell
}: WeekViewProps<E>) {
const locale = useCalendarLocale(localeProp)
const navigation = useCalendarNavigation({
date,
defaultDate,
onDateChange,
// A week steps by a week even when only five days are shown: a work-week
// view that advanced five days at a time would drift onto a Saturday.
step: { weeks: 1 },
timeZone,
})
const days = weekRange(navigation.date, locale, visibleDays, firstDayOfWeek)
return (
<div data-slot="week-view" className={cn("flex w-full flex-col gap-3", className)}>
{showToolbar ? (
<CalendarToolbar
label={label ?? calendarRangeLabel(days, { locale, timeZone })}
labelVariant={labelVariant}
pickerGranularity={pickerGranularity}
locale={locale}
firstDayOfWeek={firstDayOfWeek}
date={navigation.date}
onDateChange={navigation.goTo}
view={view}
views={views}
onViewChange={onViewChange}
onPrevious={navigation.goToPrevious}
onNext={navigation.goToNext}
onToday={navigation.goToToday}
/>
) : null}
<CalendarShell {...shell} days={days} locale={locale} timeZone={timeZone} />
</div>
)
}