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.

Aug 3 – Aug 9, 2026
Mon3Tue4Wed5Thu6Fri7Sat8Sun9
07:0008:0009:0010:0011:0012:0013:0014:0015:0016:0017:0018:0019:0020:00

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.

August 2026
MonTueWedThuFriSatSun

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 onSlotCreate with 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 labels and locale to translate controls and date formatting.

API and code

Blade: Deferred by decision — CalendarView has no Blade counterpart yet.

NameTypeRequiredDescription
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.
datestring | Date—Controlled local period anchor as a Date or `YYYY-MM-DD` string.
defaultDatestring | 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.
eventsCalendarViewEvent[]—Events rendered in the visible local day, week, or month.
availabilityRecord<number, CalendarViewAvailability[]>—Weekly local availability windows keyed by `0` (Sunday) through `6` (Saturday).
startHournumber—First visible hour in day and week grids. Default: `7`.
endHournumber—Hour at which the day and week grids end. Default: `21`.
weekStartsOn0 | 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.
slotStepnumber—Local-minute increment for empty-grid clicks. Default: `30`.
toolbarActionsReactNode—Extra content rendered at the end of the toolbar.
localestring—BCP 47 locale used for all weekday and month formatting. Default: `"en-US"`.
labelsCalendarViewLabels—Translatable visible and accessible labels, merged over English defaults.

x-data="lyraCalendarView({ … })"

OptionTypeRequiredDescription
defaultViewView—Initial view, default `week`. Can be synchronized with `x-modelable="view"`.
defaultDatestring | Date—Initial local anchor, default today. Can be synchronized with `x-modelable="date"`.
eventsLyraCalendarViewEvent[]—
availabilityRecord<number, LyraCalendarViewAvailability[]>—
startHournumber—
endHournumber—
weekStartsOn0 | 1 | 2 | 3 | 4 | 5 | 6—
slotStepnumber—
localestring—
labelsLyraCalendarViewLabels—

Canonical HTML without a behavior runtime (supply event placement and interactions yourself):

html
<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.

html
<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>