Event Calendar
A scheduling calendar for displaying events across month, week, and
agenda views. The month view is a day grid with event pills and a "+N more"
overflow; the week view is an hour-row time grid with overlap-split event blocks
and a current-time line; the agenda view is a chronological list grouped by day.
Navigation (previous / next / today) and the view switcher recompute the visible
window. Clicking an event emits an event-click event you handle in Livewire.
This is the scheduling grid — distinct from calendar,
which is a date-picker widget. It is token-themed with no third-party calendar
library.
Month View
The default view — a 7-column day grid. Events render as pills; days with more than three events show a "+N more" indicator. All-day events and day markers (see Day Markers below) appear as full-width labels at the top of the cell.
Week View
The time grid positions each event by its start and duration. Overlapping events split into side-by-side columns; a red line marks the current time on today's column. All-day events and day markers sit in a dedicated band above the hour grid (timed events have an hour position; all-day ones do not).
Agenda View
A compact chronological list — the best view on mobile and for a quick "what's next" glance.
Day Markers
dayMarkers is a day-level dimension, separate from timed events — use it
to mark holidays, working exception days, or notes. Each marker carries a date,
a label, an optional type, and an optional blocked flag, and renders across
all three views: a full-width band in month, a chip in the week all-day band, and
its own line in agenda.
A blocked day reads as unavailable — a muted surface with a diagonal hatch,
plus an "unavailable" suffix in its accessible name, so the state is never
conveyed by color or texture alone. WireKit renders the visual and semantic state
only; enforce any booking logic in your own application behind a blocked day.
Day-Marker Shape
| Key | Required | Description |
|---|---|---|
date |
yes | YYYY-MM-DD (parsed date-only — no timezone, so it never drifts a day) |
label |
yes | Display name (e.g. Christmas Day) |
type |
no | holiday (default) · working · note — drives the tint |
blocked |
no | true marks the day unavailable (muted surface + hatch + accessible state) |
Week Start
weekStartsOn sets the first column of the month and week grids — 0 for Sunday
through 6 for Saturday (1, Monday, is the default). Set it per instance or
globally in config/wirekit.php.
Livewire Integration
Pass events from the server and handle clicks (and view changes) in your component:
{{-- 1. Open a detail modal when an event is clicked --}}
<x-wirekit::event-calendar
:events="$this->events"
view="month"
x-on:event-click="$wire.openEvent($event.detail.id)"
x-on:view-change="$wire.set('view', $event.detail.view)"
/>
// 2. Shape events for the calendar (start/end are ISO 8601)
public function getEventsProperty(): array
{
return Appointment::query()
->whereBetween('starts_at', [now()->startOfMonth(), now()->endOfMonth()])
->get()
->map(fn ($a) => [
'id' => $a->id,
'title' => $a->title,
'start' => $a->starts_at->toIso8601String(),
'end' => $a->ends_at?->toIso8601String(),
'intent' => $a->status === 'confirmed' ? 'success' : 'warning',
])
->all();
}
// 3. Open the clicked event
public function openEvent(int $id): void
{
$this->editing = Appointment::find($id);
}
Event Shape
| Key | Required | Description |
|---|---|---|
id |
yes | Unique identifier (emitted on click) |
title |
yes | Event title |
start |
yes | ISO 8601 start datetime |
end |
no | ISO 8601 end (defaults to start + 1 hour) |
allDay |
no | true renders "All day" in agenda |
intent |
no | accent (default) · success · warning · danger · neutral |
Props
| Prop | Type | Default | Description |
|---|---|---|---|
events |
array | [] |
Events (see shape above) |
dayMarkers |
array | [] |
Day-level holiday / working / note markers (see shape) |
view |
string | 'month' |
month · week · agenda |
date |
string|null | null |
ISO date the calendar opens on (default today) |
weekStartsOn |
int | 1 |
First column: 0 (Sunday) – 6 (Saturday); 1 (Monday) is the default |
ariaLabel |
string | 'Calendar' |
Accessible name for the calendar |
scope |
string|null | null |
Scoped personalization name |
Accessibility
- Each event is a focusable button with a full accessible label ("Design review, Wednesday June 10, 2:00 PM"), so the schedule is operable without relying on the event's color.
- The week time grid is a labeled
role="region"withtabindex="0"and a focus ring, so the scrolling grid is keyboard-reachable (WCAG 2.1.1). - The title is an
aria-live="polite"region, so navigating months / weeks is announced. - The view switcher is a
role="radiogroup"ofrole="radio"buttons witharia-checked+ rovingtabindex— arrow keys move and select, wrapping at the ends (the buttons own no tabpanels, so tab semantics would be a broken contract).
Keyboard Interaction
| Key | Action |
|---|---|
Tab |
Move through navigation, the view switcher, and event blocks |
Enter / Space |
Activate an event, navigate, or switch view |
Design Tokens
| Element | Token |
|---|---|
| Today highlight | --color-wk-accent |
| Current-time line | --color-wk-danger |
| Grid borders | --color-wk-border |
| Out-of-month days | --color-wk-bg-subtle |
| Event (accent / success / warning / danger) | --color-wk-accent / --color-wk-success / --color-wk-warning / --color-wk-danger |
| Focus ring | --color-wk-ring |
Customization
Set the default view and week start via config/wirekit.php:
'components' => [
'event-calendar' => [
'view' => 'week',
'week-starts-on' => 0, // Sunday
],
],
Usage & Conventions
This build focuses on displaying a schedule. Drag-to-create / move / resize, recurrence (RRULE), the multi-resource view, and ICS export are planned as follow-ups.