A fully accessible, ADA-compliant calendar with Day, Week, Month, and Year views
Get a fully functional calendar up and running in under 2 minutes:
<!-- Include CSS (calendar + dependencies) -->
<link rel="stylesheet" href="styles/calendar.css">
<link rel="stylesheet" href="styles/date-picker.css">
<link rel="stylesheet" href="styles/time-picker.css">
<link rel="stylesheet" href="styles/select.css">
<!-- Calendar Container -->
<div data-rsl-calendar
data-calendar-view="month"
data-calendar-editable="true">
</div>
<!-- Include JavaScript (calendar + dependencies) -->
<script src="javascript/date-picker.js"></script>
<script src="javascript/time-picker.js"></script>
<script src="javascript/select.js"></script>
<script src="javascript/calendar.js"></script>
Day, Week, Month, and Year views with smooth transitions between them.
Full keyboard navigation, ARIA roles, screen reader announcements, and focus management.
Create, edit, and delete events with an accessible slide-out panel form.
Show upcoming events below, in a sidebar, or as a standalone widget.
Support for daily, weekly, monthly, and yearly recurring events.
Filter events by category using RSL's FilterBus pub/sub system.
Automatic dark mode support with CSS custom properties.
The Calendar component requires these RSL components for full functionality:
The event editing panel uses these RSL components internally:
date-picker.css + date-picker.js) - For selecting event start/end datestime-picker.css + time-picker.js) - For selecting event start/end timesselect.css + select.js) - For category and recurrence dropdownsAll dependencies must be loaded before calendar.js.
Configure the calendar using HTML data attributes:
| Attribute | Type | Default | Description |
|---|---|---|---|
data-rsl-calendar |
- | - | Required. Marks element as a calendar container. |
data-calendar-view |
string | month |
Initial view: day, week, month, year |
data-calendar-date |
string | Today | Initial date in ISO format (YYYY-MM-DD) |
data-calendar-first-day |
number | 0 |
Week start day: 0=Sunday, 1=Monday, etc. |
data-calendar-editable |
boolean | true |
Enable event creation and editing |
data-calendar-show-toolbar |
boolean | true |
Show navigation toolbar |
data-calendar-show-week-numbers |
boolean | false |
Display week numbers |
data-calendar-filter-key |
string | null |
FilterBus key for category filtering |
data-calendar-events |
JSON | [] |
Initial events array (JSON stringified) |
data-calendar-categories |
JSON | Default | Custom categories array (JSON stringified) |
Display upcoming events in a sidebar, below the calendar, or as a standalone list:
| Attribute | Type | Default | Description |
|---|---|---|---|
data-calendar-upcoming |
string | none |
Display mode: none, below, sidebar, only |
data-calendar-upcoming-count |
number | 5 |
Number of upcoming events to display |
data-calendar-upcoming-days |
number | 30 |
How many days ahead to look for events |
data-calendar-upcoming-title |
string | Upcoming Events |
Title shown above the upcoming events list |
none - No upcoming events display (default)below - List appears below the calendar gridsidebar - List appears to the right of the calendar (stacks on mobile)only - Only show the upcoming events list, hide calendar grid<!-- Calendar with sidebar upcoming events -->
<div data-rsl-calendar
data-calendar-view="month"
data-calendar-upcoming="sidebar"
data-calendar-upcoming-count="10"
data-calendar-upcoming-days="60"
data-calendar-upcoming-title="What's Coming Up">
</div>
<!-- Standalone upcoming events widget -->
<div data-rsl-calendar
data-calendar-upcoming="only"
data-calendar-upcoming-count="5"
data-calendar-editable="false">
</div>
| Method | Parameters | Returns | Description |
|---|---|---|---|
RSL.Calendar.init() |
- | void | Initialize all calendars with data-rsl-calendar |
RSL.Calendar.create(el, opts) |
element, options | Calendar | Create calendar on specific element |
RSL.Calendar.getInstance(el) |
element | Calendar|null | Get calendar instance for element |
RSL.Calendar.destroy(el) |
element | void | Destroy calendar instance |
| Method | Parameters | Returns | Description |
|---|---|---|---|
getEvents() |
- | Array | Get all events |
setEvents(events) |
Array | void | Replace all events |
addEvent(event) |
Object | Object | Add new event, returns event with ID |
updateEvent(id, updates) |
string, Object | Object|null | Update existing event |
removeEvent(id) |
string | void | Remove event by ID |
setView(view) |
string | void | Switch view (day/week/month/year) |
getView() |
- | string | Get current view |
goToDate(date) |
Date|string | void | Navigate to specific date |
getDate() |
- | Date | Get current displayed date |
destroy() |
- | void | Destroy the calendar instance |
getUpcomingEvents() |
- | Array | Get filtered/sorted upcoming events |
setUpcomingMode(mode) |
string | void | Change upcoming display mode (none/below/sidebar/only) |
The calendar dispatches custom events for integration:
| Event | Detail | Description |
|---|---|---|
rsl-calendar-select |
{ date: Date } |
Fired when a date is selected |
rsl-calendar-event-save |
{ event: Object, isNew: boolean } |
Fired when an event is saved |
rsl-calendar-event-delete |
{ eventId: string } |
Fired when an event is deleted |
const calendar = document.querySelector('[data-rsl-calendar]');
calendar.addEventListener('rsl-calendar-select', function(e) {
console.log('Selected date:', e.detail.date);
});
calendar.addEventListener('rsl-calendar-event-save', function(e) {
console.log('Event saved:', e.detail.event);
// Sync with backend
fetch('/api/events', {
method: e.detail.isNew ? 'POST' : 'PUT',
body: JSON.stringify(e.detail.event)
});
});
| Property | Type | Required | Description |
|---|---|---|---|
id |
string | Auto-generated | Unique event identifier |
title |
string | Yes | Event title/name |
start |
string | Yes | Start date/time (ISO format) |
end |
string | No | End date/time (ISO format) |
category |
string | No | Category ID (work, personal, etc.) |
color |
string | No | Custom color (hex) |
description |
string | No | Event description |
recurrence |
Object | No | Recurrence rule { frequency, interval, until } |
{
"id": "event-123",
"title": "Team Meeting",
"start": "2025-12-04T09:00:00",
"end": "2025-12-04T10:00:00",
"category": "meeting",
"color": "#f4b400",
"description": "Weekly team sync",
"recurrence": {
"frequency": "weekly",
"interval": 1,
"until": "2026-01-01"
}
}
| Key | Action |
|---|---|
Arrow Left/Right |
Navigate previous/next day |
Arrow Up/Down |
Navigate previous/next week |
Page Up |
Previous month/week/day (based on view) |
Page Down |
Next month/week/day (based on view) |
Shift + Page Up |
Previous year |
Shift + Page Down |
Next year |
Home |
Go to today |
Enter / Space |
Select date, open event form |
Escape |
Close event panel |
Tab |
Move through interactive elements |
grid, gridcell, columnheader) and live regions to announce view changes to screen readers.
The calendar includes these default categories. You can customize them using data-calendar-categories.
| ID | Label | Color | Preview |
|---|---|---|---|
work |
Work | #4285f4 | |
personal |
Personal | #0f9d58 | |
holiday |
Holiday | #db4437 | |
meeting |
Meeting | #f4b400 | |
travel |
Travel | #ab47bc | |
health |
Health | #00acc1 |
Override CSS custom properties to theme the calendar:
:root {
/* Calendar dimensions */
--rsl-calendar-min-height: 600px;
--rsl-calendar-cell-min-height: 100px;
/* Colors */
--rsl-calendar-bg: #ffffff;
--rsl-calendar-border: #e0e0e0;
--rsl-calendar-header-bg: #f8f9fa;
--rsl-calendar-cell-today: #fff8e1;
--rsl-calendar-btn-active: #1976d2;
/* Event colors */
--rsl-calendar-event-default: #1976d2;
--rsl-calendar-category-work: #4285f4;
--rsl-calendar-category-personal: #0f9d58;
}
Map, CustomEvent, and Array.includes.