Expand description
Calendar — month-grid date picker, standalone widget.
A self-contained calendar with month/year navigation, a 6×7 day grid,
keyboard navigation matching the WAI-ARIA grid pattern, and full
AccessKit instrumentation (Role::Grid + per-cell Role::GridCell).
Used standalone for event apps and scheduling, and embedded in
DateEdit’s popover.
§Selection modes
Calendar::single— pick one day. Bound toSignal<Option<Date>>.Calendar::range— pick a start + end day. Bound toSignal<Option<DateRange>>. Click first day → click second day to commit. Escape mid-selection cancels the in-progress anchor.
§Behaviour
- Visible month is independent of the selection — navigating past the selected month doesn’t lose the selection.
- Today highlight draws a ring around today’s cell whenever it’s
in the visible month. Color comes from
BorderRole::Focused, painted by the activeCalendarStyle::make_day_cell. - Out-of-month cells (the leading days from the previous month
and trailing days from the next month that fill the 6×7 grid) are
rendered with
TextRole::Disabledand remain selectable (matching macOS / Material). To prevent selection usedisabled_date_filter. - Keyboard (matches the WAI-ARIA
gridpattern):- Arrow keys: move focus by one day.
- Home / End: first / last day of week.
- Ctrl+Home / Ctrl+End: first / last day of month.
- PageUp / PageDown: previous / next month.
- Shift+PageUp / Shift+PageDown: previous / next year.
- Enter / Space: commit focused day to selection.
- Escape: in range mode mid-selection, cancel anchor; otherwise bubble (popover hosts close).
T: jump focus to today.
- Keyboard in the months and years views: the cursor is the month
(or year) shown, highlighted in the grid.
- Arrow keys: move by one month (year) across a row, by a row of three up and down.
- PageUp / PageDown: a year back or on (a decade in the years view).
- Enter / Space: open the month under the cursor on its days (the year on its months). Nothing is selected.
- Escape: back to the days of the month under the cursor. A date field’s popup takes Escape first and closes, and opens again on its days.
- Activating the header title zooms out and takes the keyboard into the grid, on the month (year) shown.
§Accessibility
Every string below is in the user’s language. The words come from the
framework’s Fluent bundle, so an application that registers
framework_locales() gets them translated; every date inside them is
written by ICU for the widget tree’s locale, in the locale’s own order
and grammar and on the Gregorian calendar the grid is laid out in, never
assembled from numbers or from translated names (see
common::datetime::written).
- Container:
Role::Grid, named after the visible month (“Calendar, May 2026”, “Calendrier, mai 2026”). It is not a live region: a live grid announced its name and each weekday header as it opened, and then focus said its name again. A change of month made from the keyboard moves the focus to a day of the new month (see the day cells below), whose name says the month. One made from a header arrow or the Today button, where focus stays on the button, is announced once through the tree’s announcer: the title as it now reads, or today’s date in full. The value is the day under the keyboard cursor in full, then the selection when there is one: “Saturday, May 2, 2026 (selected: Friday, May 1, 2026)”. With the cursor on the one selected day, the day is said once: “Friday, May 1, 2026 (selected)”. A range is joined by words (“… to …”), not an en-dash, which some screen readers skip. The value is for a client that reads the grid; the cursor is heard through the focus. UIA and macOS raise a value-changed event on the grid, which is not the focus while a day is; AT-SPI carries a string value on no interface. - Header arrow buttons:
Role::Buttonwith localized labels (“Previous month”, “Next month”) andAction::Clickadvertised. - Header month/year label:
Role::Button, aGhostButtonwhose activation demotesCalendarModeone level, swapping the body for the coarser grid in place, and moves focus to the grid; no popup is opened. Its name is its title: the month and year, the year, or the decade in words (“2020 to 2029”). - Month and year cells of the zoomed views:
Role::GridCell, a month named with its year (“mai 2026”),Action::Clickadvertised, no Tab stop of their own: the grid names the one under the cursor as its active descendant, as it does a day. - Weekday header row:
Role::RowofRole::ColumnHeadercells, each labelled with the long weekday name (e.g. “Monday”). - Weeks:
Role::Row, each holding its seven day cells, so the grid isGrid > Row > GridCellin every platform’s tree. - Day cells:
Role::GridCellnamed by the day in full (“Saturday, May 2, 2026”, “samedi 2 mai 2026”),set_selected,set_aria_current(Date)on today,set_disabledfor filter rejections, andAction::Clickadvertised. Keyboard focus stays on the Calendar root, which names the cell under the cursor as its active descendant while it holds focus in the day view. AccessKit reports that cell as the focus on AT-SPI, UIA and macOS, so each arrow press, and each change of month, is a focus change to the new day, which is what a screen reader speaks.
§Example
use teksilo::widgets::{Calendar, common::datetime::Date};
let date = ctx.signal(Some(Date::constant(2026, 5, 2)));
ctx.add(
Calendar::single(date.clone())
.show_today_button(true)
.on_selection_changed(|d, ctx| ctx.send_intent(MyIntent::DateChanged(d))),
);§Touch and pen
A day cell is a tap target, not a manipulator, and the distinction
decides what it declares. Its activation is on_tap, so it already lands on
the release; its 32 dp box already clears the WCAG 2.2 SC 2.5.8 floor at
Compact and follows the density ladder above it; and it produces no value
from the press position and owns no drag. It therefore does not declare
touch_action(NONE): a finger that comes to rest on a day and then drags is
scrolling the dialog or form the calendar sits in, which is what a user
expects and what declaring NONE would forbid for nothing gained.
Structs§
- Calendar
- Standalone month-grid date picker. See the module docs for the full feature list and a usage example.
- Date
Range - Inclusive range of two dates, with
start <= endenforced at construction. Used byCalendar::range.
Enums§
- Calendar
Mode - What the calendar body is showing — drives the WPF/Avalonia
“header-zoom” UX where clicking the title cycles to a coarser
grid, letting the user reach any year in 2-3 clicks instead of
many chevron presses. Default
CalendarMode::Days. - Week
Number Display - Whether and how week numbers are displayed in the leading column of the day grid.