CalendarView
CalendarView renders local-time event schedules as a day, week or month view. Choose it when people need to inspect event placement and availability; choose Calendar when the task is selecting dates rather than reading a schedule.
Examples
Week and hour grid
Event chips are measured against the real scroll container so an optional event popover is positioned beside the chip. The five event kinds use a distinct color and shape treatment: solid session, solid program session, dashed pending, hatched block and outlined external.
Month overview
Month cells show up to three events and then a remaining-count indicator. Selecting a day switches to its day view, keeping the date anchored in local time.
When to use
Use CalendarView to browse concrete local events, availability windows and open hour-grid space.
Limitations: overlapping events on one day are stacked at full width; CalendarView does not pack them into columns. An event that crosses midnight renders only on its local start day. Event dragging and roving grid keyboard navigation are application-owned.
Reach for something else when:
- The person is selecting one date or date range — use Calendar or DatePicker.
- People need to reserve one available instant — use SlotPicker.
- The task is defining repeated availability — use WeeklyScheduleEditor.
Accessibility
- Previous, Today and next are labelled native buttons. Day, Week and Month are buttons in a named group and
expose the active view with
aria-pressed. - Event chips are native buttons with localized date, time and title in their accessible name. An optional popover is a named dialog; it closes on Escape, outside pointer interaction or resize to avoid stale measured positioning.
- Clicking empty hour-grid space is deliberately pointer-only and calls
onSlotCreatewith a snapped local Date. Event chips and all toolbar controls remain keyboard-accessible. - CalendarView emits no page landmark or heading, so it can sit inside the consuming page's document outline.
Use
labelsandlocaleto translate controls and date formatting.
API and code
Blade: Deferred by decision — CalendarView has no Blade counterpart yet.
| Name | Type | Required | Description |
|---|---|---|---|
view | 'day' | 'week' | 'month' | — | Controlled displayed view. Default: `"week"`. |
defaultView | 'day' | 'week' | 'month' | — | Initial displayed view when uncontrolled. Default: `"week"`. |
onViewChange | (view: 'day' | 'week' | 'month') => void | — | Called after the displayed view changes. |
date | string | Date | — | Controlled local period anchor as a Date or `YYYY-MM-DD` string. |
defaultDate | string | Date | — | Initial local period anchor as a Date or `YYYY-MM-DD` string. |
onDateChange | (isoDate: string) => void | — | Called with the local `YYYY-MM-DD` anchor after it changes. |
events | CalendarViewEvent[] | — | Events rendered in the visible local day, week, or month. |
availability | Record<number, CalendarViewAvailability[]> | — | Weekly local availability windows keyed by `0` (Sunday) through `6` (Saturday). |
startHour | number | — | First visible hour in day and week grids. Default: `7`. |
endHour | number | — | Hour at which the day and week grids end. Default: `21`. |
weekStartsOn | 0 | 1 | 2 | 3 | 4 | 5 | 6 | — | First weekday, where `0` is Sunday and `6` is Saturday. Default: `1`. |
onEventOpen | (event: CalendarViewEvent) => void | — | Called when an event chip is opened. |
renderEventPopover | (event: CalendarViewEvent, close: () => void) => ReactNode | — | Renders the optional event-summary popover. Receives a close callback. |
onSlotCreate | (start: Date) => void | — | Called with a snapped local Date after an empty hour-grid area is clicked. |
slotStep | number | — | Local-minute increment for empty-grid clicks. Default: `30`. |
toolbarActions | ReactNode | — | Extra content rendered at the end of the toolbar. |
locale | string | — | BCP 47 locale used for all weekday and month formatting. Default: `"en-US"`. |
labels | CalendarViewLabels | — | Translatable visible and accessible labels, merged over English defaults. |
x-data="lyraCalendarView({ … })"
| Option | Type | Required | Description |
|---|---|---|---|
defaultView | View | — | Initial view, default `week`. Can be synchronized with `x-modelable="view"`. |
defaultDate | string | Date | — | Initial local anchor, default today. Can be synchronized with `x-modelable="date"`. |
events | LyraCalendarViewEvent[] | — | |
availability | Record<number, LyraCalendarViewAvailability[]> | — | |
startHour | number | — | |
endHour | number | — | |
weekStartsOn | 0 | 1 | 2 | 3 | 4 | 5 | 6 | — | |
slotStep | number | — | |
locale | string | — | |
labels | LyraCalendarViewLabels | — |
Canonical HTML without a behavior runtime (supply event placement and interactions yourself):
<div class="lyra-calview">
<div class="lyra-calview__toolbar">
<button class="lyra-calview__nav" type="button" aria-label="Previous period">‹</button>
<span class="lyra-calview__title">August 2026</span>
<span class="lyra-calview__seg" role="group" aria-label="Calendar view"
><button type="button" aria-pressed="true">Week</button></span
>
</div>
<div class="lyra-calview__grid">
<div class="lyra-calview__col">
<button class="lyra-calview__evt lyra-calview__evt--pending" type="button">
10:00–11:00 Awaiting approval
</button>
</div>
</div>
</div>lyraCalendarView enhances served markup. Pass events, weekday-keyed availability, startHour,
endHour, weekStartsOn, slotStep, locale, and labels as options. view (day, week, or
month) and local ISO date are writable state. Set defaultView and defaultDate initially;
use x-modelable="date" on the root to connect an external date model. A nested Alpine scope can
bridge view with x-modelable="view" when both values need external control. External model
updates do not emit interaction events.
The four root events carry the same values as React callbacks: lyra:view-change has a view
string, lyra:change a local YYYY-MM-DD string, lyra:event-open the event object, and
lyra:slot-create a snapped local Date. Toolbar actions and event-popover content remain
application markup. The popover is anchored to its event chip and closes on outside pointerdown,
Escape, or resize. Include its .lyra-calview__pop region when a popover is wanted.
<div
class="lyra-calview"
x-data="lyraCalendarView({
defaultDate: '2026-08-12', events: calendarEvents,
availability: { 3: [{ start: '08:00', end: '17:00' }] }
})"
x-modelable="date"
x-model="selectedDate"
@lyra:event-open="showEvent($event.detail)"
@lyra:slot-create="createAt($event.detail)"
>
<div class="lyra-calview__toolbar">
<button
class="lyra-calview__nav"
type="button"
:aria-label="label('previous')"
@click="navigate(-1)"
>
‹
</button>
<button
class="lyra-calview__nav"
type="button"
:aria-label="label('today')"
@click="setDate(new Date())"
>
●
</button>
<button
class="lyra-calview__nav"
type="button"
:aria-label="label('next')"
@click="navigate(1)"
>
›
</button>
<span class="lyra-calview__title" x-text="title()"></span>
<span class="lyra-calview__seg" role="group" :aria-label="label('view')">
<button
type="button"
:aria-pressed="view === 'day'"
@click="setView('day')"
x-text="label('day')"
></button>
<button
type="button"
:aria-pressed="view === 'week'"
@click="setView('week')"
x-text="label('week')"
></button>
<button
type="button"
:aria-pressed="view === 'month'"
@click="setView('month')"
x-text="label('month')"
></button>
</span>
<!-- Place application toolbar actions here. -->
</div>
<div x-show="view !== 'month'">
<div class="lyra-calview__head" :style="gridStyle()">
<span></span>
<template x-for="day in days()" :key="day.key"
><span class="lyra-calview__head-cell" :class="dayClass(day)"
><span x-text="weekdayLabel(day)"></span
><strong x-text="day.date.getDate()"></strong></span
></template>
</div>
<div class="lyra-calview__scroll" tabindex="0" aria-label="Calendar hours">
<div class="lyra-calview__grid" :style="gridStyle()">
<div class="lyra-calview__ruler" :style="{ height: gridHeight() + 'px' }">
<template x-for="hour in hours()" :key="hour"
><span
class="lyra-calview__hour"
:style="hourStyle(hour)"
x-text="String(hour).padStart(2, '0') + ':00'"
></span
></template>
</div>
<template x-for="day in days()" :key="day.key"
><div
class="lyra-calview__col"
:style="{ height: gridHeight() + 'px', backgroundSize: '100% 48px' }"
@click="createSlot($event, day)"
>
<template x-for="(window, index) in day.windows" :key="index"
><span class="lyra-calview__avail" :style="availabilityStyle(window)"></span
></template>
<span
class="lyra-calview__now"
x-show="nowStyle(day)"
:style="nowStyle(day) || {}"
></span>
<template x-for="item in day.events" :key="item.id"
><button
class="lyra-calview__evt"
:class="{ ['lyra-calview__evt--' + item.kind]: true }"
:style="eventStyle(item)"
type="button"
:aria-label="eventLabel(item)"
@click="openEvent($event, item)"
>
<span
class="lyra-calview__evt-time"
x-text="time(item.startDate) + '–' + time(item.endDate)"
></span>
<span
class="lyra-calview__evt-title"
x-show="parseFloat(eventStyle(item).height) >= 34"
x-text="item.title"
></span></button
></template></div
></template>
</div>
<div
class="lyra-calview__pop"
role="dialog"
:aria-label="popover?.event.title"
x-show="popover"
:style="popover ? { top: popover.top + 'px', left: popover.left + 'px' } : {}"
>
<!-- Render popover?.event with application markup. -->
<button type="button" @click="closePopover()">Close</button>
</div>
</div>
</div>
<div class="lyra-calview__mgrid" x-show="view === 'month'">
<template x-for="(name, index) in weekdays()" :key="index"
><span class="lyra-calview__head-cell" x-text="name"></span
></template>
<template x-for="day in days()" :key="day.key"
><button
class="lyra-calview__mcell"
type="button"
:class="monthClass(day)"
@click="selectMonthDay(day)"
>
<span class="lyra-calview__mday" x-text="day.date.getDate()"></span>
<template x-for="item in monthEvents(day)" :key="item.id"
><span
class="lyra-calview__mevt"
:class="{ ['lyra-calview__evt--' + item.kind]: true }"
x-text="time(item.startDate) + ' ' + item.title"
></span
></template>
<span
class="lyra-calview__more"
x-show="monthMore(day)"
x-text="'+' + monthMore(day)"
></span></button
></template>
</div>
</div>