Skip to content

Calendar

A full multi-view event calendar with month, week, day, and year views. Events are supplied through a reactive config object.

The component renders its own toolbar (previous/next navigation, Today button, and view switcher). The host element must have a defined height (e.g. style="height: 600px") because the week and day views contain a scrollable time grid.

For a compact calendar that selects a single date or a date range, see Inline Calendar.

Usage

Use x-h-calendar when users need to view and navigate a schedule - appointments, team calendars, project timelines, and so on.

Keyboard Handling

In the month view (and within each year-view mini-month) the day cells form an ARIA grid with roving focus:

  • Up / Down - Move focus a week earlier/later.
  • Left / Right - Move focus to the previous/next day (crossing month boundaries).
  • Home / End - Move focus to the first/last day of the month.
  • PageUp / PageDown - Move focus to the previous/next month.
  • Enter / Space - Fire date-click for the focused day (year view - open that day in day view).

Events are buttons in the tab order. Activate them to fire event-click. In the month view, the "+N more" overflow opens a dialog that moves focus to its event list and returns focus to the trigger on Escape.

Accessibility

The calendar is a labeled group (default name "Calendar", overridable with an aria-label attribute). The toolbar period heading is an aria-live region. The month grid uses role="grid"/row/gridcell with aria-current="date" on today and full keyboard navigation. Events are buttons whose accessible label includes the title, time (or "all day"), and status (e.g. "unconfirmed"). The week/day time grid's empty-slot "click to pick a time" is a pointer-only convenience.

API Reference

Component attribute(s)

x-h-calendar

Attributes

AttributeValuesRequiredDescription
data-aria-prevstringfalseSets the aria-label for the previous-period navigation button.
data-aria-nextstringfalseSets the aria-label for the next-period navigation button.
data-aria-viewsstringfalseSets the aria-label for the view switcher menu (Defaults to "Change view").
data-today-labelstringfalseSets the text label for the Today button (Defaults to "Today").
data-more-labelstringfalseTemplate for the month-view overflow button. {count} is substituted. Defaults to +{count} more.
data-day-labelstringfalseSets the label for the Day view option (Defaults to "Day").
data-week-labelstringfalseSets the label for the Week view option (Defaults to "Week").
data-month-labelstringfalseSets the label for the Month view option (Defaults to "Month").
data-year-labelstringfalseSets the label for the Year view option (Defaults to "Year").

Events

EventDescription
event-clickFired when the user clicks an event. The original event object is passed in $event.detail.event.
date-clickFired when the user clicks an empty date cell or time slot. The clicked Date is in $event.detail.date. For time-grid views the slot time string ("HH:MM") is also in $event.detail.time.

Configuration

Pass a configuration object to the directive as an expression.

html
<div x-h-calendar="calConfig" style="height: 600px"></div>
KeyDescription
eventsArray of event objects. See Event object below.
viewInitial view. The options are "month" (default), "week", "day", or "year".
dateInitial focus date in YYYY-MM-DD format. Defaults to today.
localeBCP 47 language tag for formatting. When not provided, it is taken from the page's <html lang> attribute, then the browser locale.
firstDayFirst day of the week. 0 = Sunday (default), 1 = Monday.
showNowIndicatorShow the current-time indicator in week and day views. Defaults to true. Set to false to hide it.
viewsShow the view-switcher button group in the toolbar. Defaults to true. Set to false to lock the calendar to the view set in view and hide the switcher.
scrollToWhere week and day views scroll to on load - "now" anchors on the current time, "first-event" anchors on the earliest event in view. Falls back to "now" when the view has no timed events.

Event object

Each item in the events array supports the following fields:

FieldTypeRequiredDescription
idstringfalseUnique identifier for the event. Auto-generated if omitted.
titlestringtrueDisplay title of the event.
startstringtrueStart datetime as an ISO string ("YYYY-MM-DDTHH:MM:SS") or date ("YYYY-MM-DD" for all-day).
endstringfalseEnd datetime. Defaults to start. For all-day events, defaults to end of the start day.
allDaybooleanfalseWhen true, the event appears in the all-day strip of week/day views. Defaults to false.
colorblue
red
green
yellow
purple
pink
indigo
orange
gray
teal
falseColor key.
statusstringfalsePill style. confirmed (default) renders a filled pill, unconfirmed renders an outlined pill, and rejected renders an outlined pill with a dashed border.
descriptionstringfalseShown as a tooltip on event pills.

Examples

Month view

html
<div
  x-data="{
  cal: {},
  init() {
    const today = new Date().toISOString().slice(0, 10);
    const tomorrow = new Date(new Date().setDate(new Date().getDate() + 1)).toISOString().slice(0, 10);
    this.cal = {
      view: 'month',
      events: [
        { id: '1', title: 'Team Sync', start: today + 'T10:00:00', end: today + 'T11:00:00', color: 'blue' },
        { id: '2', title: 'Company Meeting', start: today + 'T10:00:00', end: today + 'T11:00:00', status: 'unconfirmed', color: 'blue' },
        { id: '3', title: 'All Hands', start: today, allDay: true, color: 'green' },
        { id: '4', title: 'Off-site', start: today + 'T08:00:00', end: tomorrow + 'T18:00:00', color: 'purple' },
        { id: '5', title: 'Vendor Call', start: today + 'T13:00:00', end: today + 'T14:00:00', status: 'rejected', color: 'red' },
      ],
    };
  }
}"
  x-h-calendar="cal"
  style="height: 560px"
  @event-click="console.log('event clicked:', $event.detail.event)"
  @date-click="console.log('date clicked:', $event.detail.date)"
></div>

Week view

html
<div
  x-data="{
  cal: {},
  init() {
    const today = new Date().toISOString().slice(0, 10);
    const tomorrow = new Date(new Date().setDate(new Date().getDate() + 1)).toISOString().slice(0, 10);
    this.cal = {
      view: 'week',
      scrollTo: 'first-event',
      events: [
        { id: '1', title: 'Team Sync', start: today + 'T09:00:00', end: today + 'T10:00:00', color: 'blue' },
        { id: '2', title: 'Design Review', start: today + 'T09:30:00', end: today + 'T10:30:00', color: 'purple' },
        { id: '3', title: 'Lunch with Client', start: today + 'T12:00:00', end: today + 'T13:30:00', color: 'green' },
        { id: '4', title: 'Off-site', start: today, end: tomorrow, allDay: true, color: 'orange' },
        { id: '5', title: 'Budget Review', start: today + 'T15:00:00', end: today + 'T16:00:00', color: 'red', status: 'unconfirmed' },
      ],
    };
  }
}"
  x-h-calendar="cal"
  style="height: 560px"
  @event-click="console.log('event clicked:', $event.detail.event)"
  @date-click="console.log('date clicked:', $event.detail.date, $event.detail.time)"
></div>

Day view

html
<div
  x-data="{
  cal: {},
  init() {
    const today = new Date().toISOString().slice(0, 10);
    this.cal = {
      view: 'day',
      events: [
        { id: '1', title: 'Stand-up', start: today + 'T09:00:00', end: today + 'T09:15:00', color: 'blue' },
        { id: '2', title: 'Sprint Planning', start: today + 'T10:00:00', end: today + 'T12:00:00', color: 'indigo' },
        { id: '3', title: 'Lunch', start: today + 'T12:00:00', end: today + 'T13:00:00', color: 'green' },
        { id: '4', title: '1:1 with Manager', start: today + 'T14:00:00', end: today + 'T14:30:00', color: 'teal' },
        { id: '5', title: 'Code Review', start: today + 'T14:00:00', end: today + 'T15:00:00', color: 'orange' },
        { id: '6', title: 'Release Call', start: today + 'T16:00:00', end: today + 'T17:00:00', color: 'red', status: 'unconfirmed' },
      ],
    };
  }
}"
  x-h-calendar="cal"
  style="height: 560px"
  @event-click="console.log('event clicked:', $event.detail.event)"
  @date-click="console.log('date clicked:', $event.detail.date, $event.detail.time)"
></div>

Year view

html
<div
  x-data="{
  cal: {},
  init() {
    const today = new Date().toISOString().slice(0, 10);
    this.cal = {
      view: 'year',
      events: [
        { id: '1', title: 'Team Sync', start: today + 'T10:00:00', end: today + 'T11:00:00', color: 'blue' },
        { id: '2', title: 'All Hands', start: today, allDay: true, color: 'green' },
      ],
    };
  }
}"
  x-h-calendar="cal"
  style="height: 560px"
></div>