Calendar
A fully accessible, locale-aware calendar component with keyboard navigation following WAI-ARIA APG guidelines.
Demo
Selected: 2026-01-08
Features
- Full keyboard navigation (Arrow keys, Home, End, Page Up/Down)
- Locale-aware month/weekday names via browser's Intl API
- Configurable week start day (Sunday, Monday, etc.)
- Min/max date constraints
- Fixed weeks option for consistent grid height
- WCAG 2.1 AA compliant with proper ARIA attributes
- Headless design - fully customizable styling
- 13 calendar systems: Gregorian, Japanese, Buddhist, Persian, Hebrew, and more
Installation
dotnet add package SummitUIAnatomy
Import the components and structure them as follows:
<CalendarRoot @bind-Value="selectedDate" Context="ctx">
<CalendarHeader>
<CalendarPrevButton />
<CalendarHeading />
<CalendarNextButton />
</CalendarHeader>
<CalendarGrid>
<CalendarGridHead>
<CalendarGridRow>
@for (int i = 0; i < ctx.Weekdays.Length; i++)
{
var idx = i;
<CalendarHeadCell Abbreviation="@ctx.WeekdaysLong[idx]">
@ctx.Weekdays[idx]
</CalendarHeadCell>
}
</CalendarGridRow>
</CalendarGridHead>
<CalendarGridBody>
@foreach (var week in ctx.CurrentMonth.Weeks)
{
<CalendarGridRow>
@foreach (var date in week)
{
<CalendarCell Date="@date">
<CalendarDay />
</CalendarCell>
}
</CalendarGridRow>
}
</CalendarGridBody>
</CalendarGrid>
</CalendarRoot>Sub-components
CalendarRoot
Root container managing calendar state and context.
CalendarHeader
Container for navigation controls and heading.
CalendarHeading
Displays the current month and year with live region.
CalendarPrevButton
Button to navigate to the previous month.
CalendarNextButton
Button to navigate to the next month.
CalendarGrid
The table element with role="grid" for the calendar.
CalendarGridHead
Table header containing weekday names.
CalendarGridBody
Table body containing date cells.
CalendarGridRow
A row in the calendar grid.
CalendarHeadCell
Weekday header cell with abbreviation support.
CalendarCell
A cell representing a single date.
CalendarDay
The interactive day button inside a cell.
API Reference
CalendarRoot
| Property | Type | Default | Description |
|---|---|---|---|
| Value | DateOnly? | null | The currently selected date (two-way bindable) |
| ValueChanged | EventCallback<DateOnly?> | - | Callback when the selected date changes |
| DefaultValue | DateOnly? | null | Default value when uncontrolled |
| OnValueChange | EventCallback<DateOnly?> | - | Alternative callback for value changes |
| Placeholder | DateOnly? | null | Placeholder date for initial display when no value is set |
| Locale | string? | Browser locale | Locale for formatting (e.g., "en-US", "de-DE") |
| WeekStartsOn | WeekStartsOn? | Locale default | First day of the week (Sunday, Monday, etc.) |
| FixedWeeks | bool | false | Always display 6 weeks for consistent height |
| MinValue | DateOnly? | null | Minimum selectable date |
| MaxValue | DateOnly? | null | Maximum selectable date |
| Disabled | bool | false | Disables the entire calendar |
| ReadOnly | bool | false | Allows navigation but prevents selection |
| IsDateDisabled | Func<DateOnly, bool>? | null | Custom function to disable specific dates |
| CalendarSystem | CalendarSystem | Gregorian | Calendar system to use for display (Japanese, Buddhist, Persian, etc.) |
CalendarHeadCell
| Property | Type | Default | Description |
|---|---|---|---|
| Abbreviation | string? | null | Full weekday name for screen readers (placed in abbr element) |
CalendarCell
| Property | Type | Default | Description |
|---|---|---|---|
| Date required | DateOnly | - | The date this cell represents |
Examples
Basic Calendar
@code {
private DateOnly? selectedDate = DateOnly.FromDateTime(DateTime.Today);
}
<CalendarRoot @bind-Value="selectedDate" Context="ctx">
<CalendarHeader class="flex items-center justify-between mb-4">
<CalendarPrevButton class="..." />
<CalendarHeading class="text-sm font-semibold" />
<CalendarNextButton class="..." />
</CalendarHeader>
<CalendarGrid class="w-full">
<!-- Grid content -->
</CalendarGrid>
</CalendarRoot>
<p>Selected: @(selectedDate?.ToString("yyyy-MM-dd") ?? "None")</p>Week Starts on Monday
Configure the first day of the week.
<CalendarRoot @bind-Value="selectedDate"
WeekStartsOn="WeekStartsOn.Monday"
Context="ctx">
<!-- Calendar content -->
</CalendarRoot>Min/Max Date Constraints
Restrict selectable dates to a range.
@code {
private DateOnly? selectedDate;
private DateOnly minDate = DateOnly.FromDateTime(DateTime.Today);
private DateOnly maxDate = DateOnly.FromDateTime(DateTime.Today.AddDays(30));
}
<CalendarRoot @bind-Value="selectedDate"
MinValue="@minDate"
MaxValue="@maxDate"
Context="ctx">
<!-- Calendar content -->
</CalendarRoot>Fixed Weeks
Always show 6 weeks for consistent height.
<CalendarRoot @bind-Value="selectedDate"
FixedWeeks="true"
Context="ctx">
<!-- Always shows 6 weeks -->
</CalendarRoot>Custom Date Disabled
Disable specific dates with a custom function.
@code {
private DateOnly? selectedDate;
// Disable weekends
private bool IsWeekend(DateOnly date) =>
date.DayOfWeek == DayOfWeek.Saturday ||
date.DayOfWeek == DayOfWeek.Sunday;
}
<CalendarRoot @bind-Value="selectedDate"
IsDateDisabled="IsWeekend"
Context="ctx">
<!-- Weekend dates are disabled -->
</CalendarRoot>Calendar Systems
The calendar supports 13 different calendar systems. The selected date value is always stored as a Gregorian DateOnly,
but displayed using the selected calendar system. This allows international users to interact with dates in their familiar format.
Selected (Gregorian): 2026-01-08
The standard Western calendar used worldwide.
Usage
@code {
private DateOnly? selectedDate;
private CalendarSystem calendarSystem = CalendarSystem.Japanese;
}
<CalendarRoot @bind-Value="selectedDate"
CalendarSystem="calendarSystem"
Locale="ja-JP"
Context="ctx">
<!-- Calendar content -->
<!-- Heading will show Japanese era (e.g., 令和7年1月) -->
<!-- Selected value remains Gregorian DateOnly -->
</CalendarRoot>Supported Calendar Systems
| System | Region | Description |
|---|---|---|
| Gregorian | Worldwide | Standard Western calendar |
| Japanese | Japan | Era-based (Reiwa, Heisei, Showa) |
| Buddhist | Thailand, Cambodia, Laos | Year = Gregorian + 543 |
| Taiwan | Taiwan | ROC era, Year = Gregorian - 1911 |
| Persian | Iran, Afghanistan | Solar calendar (Jalali) |
| Indian | India | Saka era, official civil calendar |
| IslamicUmalqura | Saudi Arabia | Lunar, astronomical calculations |
| IslamicCivil | Islamic regions | Lunar, tabular/arithmetic |
| IslamicTabular | Islamic regions | Lunar, algorithmic variant |
| Hebrew | Israel | Lunisolar, 12-13 months |
| Coptic | Egypt | Coptic Orthodox Church calendar |
| Ethiopic | Ethiopia | Official calendar of Ethiopia |
| EthiopicAmeteAlem | Ethiopia | Alternate era calculation |
Styling
Data Attributes
| Attribute | Element | Description |
|---|---|---|
| data-summit-calendar-root | CalendarRoot | Marks the root element |
| data-summit-calendar-grid | CalendarGrid | Marks the grid table |
| data-summit-calendar-day | CalendarDay | Marks day buttons |
| data-date | CalendarDay | ISO date string (YYYY-MM-DD) |
| data-state | CalendarDay | "selected" when date is selected |
| data-today | CalendarDay | Present when date is today |
| data-focused | CalendarDay | Present on keyboard-focused date |
| data-outside-month | CalendarDay | Present for dates outside current month |
| data-unavailable | CalendarDay | Present for dates outside min/max or custom disabled |
| data-disabled | Multiple | Present when calendar or element is disabled |
Tailwind Example
/* Calendar day styling with Tailwind */
.calendar-day {
@apply w-9 h-9 rounded-md text-sm
hover:bg-accent
focus:outline-none focus:ring-2 focus:ring-primary focus:ring-offset-2;
}
.calendar-day[data-today] {
@apply font-semibold text-primary;
}
.calendar-day[data-state="selected"] {
@apply bg-primary text-primary-foreground;
}
.calendar-day[data-outside-month] {
@apply text-muted-foreground opacity-50;
}
.calendar-day[data-unavailable] {
@apply text-muted-foreground opacity-50 cursor-not-allowed;
}
.calendar-day[data-focused] {
@apply ring-2 ring-primary ring-offset-2;
}Accessibility
Keyboard Navigation
| Key | Action |
|---|---|
| ArrowRight | Move focus to next day |
| ArrowLeft | Move focus to previous day |
| ArrowDown | Move focus to same day next week |
| ArrowUp | Move focus to same day previous week |
| Home | Move focus to start of week |
| End | Move focus to end of week |
| PageDown | Move focus to next month |
| PageUp | Move focus to previous month |
| Shift + PageDown | Move focus to next year |
| Shift + PageUp | Move focus to previous year |
| Enter / Space | Select the focused date |
ARIA Attributes
- Grid:
Has
role="grid"witharia-labelledbypointing to heading - Heading:
Uses
aria-live="polite"andaria-atomic="true"to announce month changes - Days:
Each has
aria-selected,aria-disabled, andaria-current="date"for today - Navigation:
Buttons have descriptive
aria-label("Previous month", "Next month") - Focus:
Uses roving tabindex pattern - only focused date has
tabindex="0"