# Calendar

A month-grid calendar over a model you supply — days, ranges and per-day states, named by an absolute day serial.

```slint
import { Calendar } from "@glint/components/calendar.slint";
import { Tokens } from "@glint/theme/tokens.slint";

export component Demo inherits Window {
    width: 380px;
    height: 360px;
    background: Tokens.color-background;

    // 14 May 2026, on this host's own day scale — chrono's
    // `num_days_from_ce()` for that day.
    in-out property <int> chosen: 739750;

    VerticalLayout {
        padding: 16px;
        alignment: center;

        HorizontalLayout {
            alignment: center;

            Rectangle {
                border-width: 1px;
                border-color: Tokens.color-border-hairline;
                border-radius: Tokens.radius-lg;
                background: Tokens.color-card;

                VerticalLayout {
                    // 1 May 2026 is a Friday, so the grid starts five cells in.
                    Calendar {
                        months: [{
                            label: "May 2026",
                            day-count: 31,
                            first-day-offset: 5,
                            first-serial: 739737,
                        }];
                        selected-serial <=> root.chosen;
                    }
                }
            }
        }
    }
}
```

## Usage

```slint
import { Calendar, CalendarMonth } from "@glint/components/calendar.slint";

export component AppWindow inherits Window {
    // Everything about a month is your date library's arithmetic: how long it
    // is, what weekday it opens on, and the serial of its first day.
    in property <[CalendarMonth]> months;
    in-out property <int> chosen;

    callback page-back();
    callback page-forward();

    Calendar {
        months: root.months;
        selected-serial <=> root.chosen;
        day-selected(serial) => { root.chosen = serial; }
        // The component holds no date model, so paging is rolling yours.
        prev-month => { root.page-back(); }
        next-month => { root.page-forward(); }
        // If your range is bounded, say where it ends.
        can-prev-month: true;
        can-next-month: true;
    }
}
```

**Slint 1.17 has no `Date` type and no date arithmetic, so the dates belong to you.** You describe each month you want drawn and the calendar renders the grid, walks it, and reports what was picked. It formats nothing and assumes no calendar system, which is what makes a Persian or Hijri month cost exactly what a Gregorian one does.

**A day is a serial.** It is named by an absolute day number of your own, counted on whatever scale your date library gives you: `chrono`’s `num_days_from_ce()`, an epoch day count, anything monotonic and positive. A month carries the serial of its first day and every cell after it counts on from there.

That one number is what lets a single comparison answer “is this day inside the range?” across a month boundary, across two months drawn side by side, and for a range end that is not on screen at all (ADR-0031). A day-of-month cannot: 3 June is not “after” 28 May by it.

**Serial `0` is never a day**, so it is the empty selection — and what a blank leading or trailing cell carries, which is what makes those cells refuse a click that would otherwise land in the next month.

`CalendarMonth` is one month of the grid:

| Field              | What it is                                                                            |
| ------------------ | ------------------------------------------------------------------------------------- |
| `label`            | The caption, e.g. “May 2026” — it also names the month’s grid to assistive technology |
| `day-count`        | How many days the month has                                                           |
| `first-day-offset` | Weekday of day 1, `0` = Sunday — the leading blanks                                   |
| `first-serial`     | The serial of day 1, one past the last serial of the month before it                  |
| `week-numbers`     | ISO week numbers for the six rows; empty draws no column                              |

`CalendarDay` is what you know about one day that the grid cannot work out:

| Field      | What it is                                                                                                                           |
| ---------- | ------------------------------------------------------------------------------------------------------------------------------------ |
| `disabled` | An unavailable day: it dims, refuses every activation and reports itself disabled, while keeping its cell and its place in the month |
| `note`     | What is special about this day — “Booked”, “Public holiday”. It draws a dot under the number and rides the cell’s description        |

`day-states` is **a flat array indexed from the first rendered day**, not a set the cells search: Slint’s expression language has no loop, so membership can only be answered by position (ADR-0031). It degrades well — a read past the end is the default `CalendarDay`, so supplying fewer states than the grid draws leaves the rest ordinary days.

## Examples

### Picking a day

`CalendarMode.single` — the default — is one selected day: each pick replaces `selected-serial` and reports itself through `day-selected(serial)`. Map the serial back through the model you built it from.

```slint
import { Calendar, CalendarMode } from "@glint/components/calendar.slint";
import { Tokens } from "@glint/theme/tokens.slint";

export component Demo inherits Window {
    width: 380px;
    height: 400px;
    background: Tokens.color-background;

    in-out property <int> chosen: 0;

    VerticalLayout {
        padding: 16px;
        spacing: 12px;
        alignment: center;

        HorizontalLayout {
            alignment: center;

            Rectangle {
                border-width: 1px;
                border-color: Tokens.color-border-hairline;
                border-radius: Tokens.radius-lg;
                background: Tokens.color-card;

                VerticalLayout {
                    Calendar {
                        mode: CalendarMode.single;
                        months: [{
                            label: "May 2026",
                            day-count: 31,
                            first-day-offset: 5,
                            first-serial: 739737,
                        }];
                        selected-serial <=> root.chosen;
                    }
                }
            }
        }

        Text {
            text: root.chosen == 0
                ? "No day selected."
                : "Selected: " + (root.chosen - 739736) + " May 2026 (serial " + root.chosen + ")";
            color: Tokens.color-muted-foreground;
            font-size: Tokens.typography-body-sm-size;
            horizontal-alignment: center;
        }
    }
}
```

### A range

`CalendarMode.range` takes two picks: the first opens the interval, the second closes it and reports the pair through `range-selected(from, to)`. They are the ends in either order, so a user who draws backwards gets the range they drew rather than a dead end, and picking again once a range is complete starts the next one.

While an interval is forming, the day under the pointer is its open end — `preview-serial`, which paints the band and is what a trigger shows before the second pick lands. It is paint rather than state: a preview is something the pointer is doing, so only a *settled* range announces itself.

```slint
import { Calendar, CalendarMode } from "@glint/components/calendar.slint";
import { Tokens } from "@glint/theme/tokens.slint";

export component Demo inherits Window {
    width: 380px;
    height: 420px;
    background: Tokens.color-background;

    in-out property <int> from-day: 0;
    in-out property <int> to-day: 0;
    property <int> forming: cal.preview-serial;

    VerticalLayout {
        padding: 16px;
        spacing: 12px;
        alignment: center;

        HorizontalLayout {
            alignment: center;

            Rectangle {
                border-width: 1px;
                border-color: Tokens.color-border-hairline;
                border-radius: Tokens.radius-lg;
                background: Tokens.color-card;

                VerticalLayout {
                    cal := Calendar {
                        mode: CalendarMode.range;
                        months: [{
                            label: "May 2026",
                            day-count: 31,
                            first-day-offset: 5,
                            first-serial: 739737,
                        }];
                        range-from <=> root.from-day;
                        range-to <=> root.to-day;
                    }
                }
            }
        }

        Text {
            text: root.from-day == 0
                ? "Pick the first day of the range."
                : (root.to-day != 0
                    ? "Booked " + (root.from-day - 739736) + " – " + (root.to-day - 739736) + " May"
                    : (root.forming != 0
                        ? "Ends " + (root.forming - 739736) + " May?"
                        : "Now pick the day it ends on."));
            color: Tokens.color-muted-foreground;
            font-size: Tokens.typography-body-sm-size;
            horizontal-alignment: center;
        }
    }
}
```

### Two months at once

Give `months` more than one entry and they are drawn side by side from the one instance, each grid named for its own month. The serials have to be consecutive across them — May’s 31 days run out at 739767 and June opens at 739768 — which is what makes a range whose ends sit in two different grids one interval rather than two.

The chevrons page the whole strip, because you are the one rolling the model.

```slint
import { Calendar, CalendarMode } from "@glint/components/calendar.slint";
import { Tokens } from "@glint/theme/tokens.slint";

export component Demo inherits Window {
    width: 660px;
    height: 380px;
    background: Tokens.color-background;

    VerticalLayout {
        padding: 16px;
        alignment: center;

        HorizontalLayout {
            alignment: center;

            Rectangle {
                border-width: 1px;
                border-color: Tokens.color-border-hairline;
                border-radius: Tokens.radius-lg;
                background: Tokens.color-card;

                VerticalLayout {
                    Calendar {
                        mode: CalendarMode.range;
                        months: [
                            { label: "May 2026",  day-count: 31, first-day-offset: 5, first-serial: 739737 },
                            { label: "June 2026", day-count: 30, first-day-offset: 1, first-serial: 739768 },
                        ];
                        // 29 May to 5 June — one interval across two grids.
                        range-from: 739765;
                        range-to: 739772;
                    }
                }
            }
        }
    }
}
```

### Unavailable days, and days with a note

`day-states` says what you know about each day. A `disabled` day dims, refuses the pointer, `Enter` and the accessible default action alike, and still keeps its cell and its index — the way every Glint control refuses. A `note` draws a dot under the number and is read after the day’s name, because the accessibility tree has no property for “this day is special” (ADR-0013).

The array is indexed from the first rendered day, so its first entry is 1 May.

```slint
import { Calendar } from "@glint/components/calendar.slint";
import { Tokens } from "@glint/theme/tokens.slint";

export component Demo inherits Window {
    width: 380px;
    height: 400px;
    background: Tokens.color-background;

    in-out property <int> chosen: 0;

    VerticalLayout {
        padding: 16px;
        spacing: 12px;
        alignment: center;

        HorizontalLayout {
            alignment: center;

            Rectangle {
                border-width: 1px;
                border-color: Tokens.color-border-hairline;
                border-radius: Tokens.radius-lg;
                background: Tokens.color-card;

                VerticalLayout {
                    Calendar {
                        months: [{
                            label: "May 2026",
                            day-count: 31,
                            first-day-offset: 5,
                            first-serial: 739737,
                        }];
                        // Index 0 is 1 May: the first two days are taken, and
                        // the fourth is a holiday nobody is unavailable on.
                        day-states: [
                            { disabled: true },
                            { disabled: true },
                            { disabled: false },
                            { note: "Public holiday" },
                        ];
                        selected-serial <=> root.chosen;
                    }
                }
            }
        }

        Text {
            text: "1 and 2 May refuse every activation path; 4 May carries a note.";
            color: Tokens.color-muted-foreground;
            font-size: Tokens.typography-body-sm-size;
            horizontal-alignment: center;
            wrap: word-wrap;
        }
    }
}
```

### Paging, and the bounds

The calendar has no notion of a bound of its own — the year/month model is yours — so a host that restricts a range says so with `can-prev-month` and `can-next-month`. Both default to true, so an unbounded calendar is unchanged. A chevron at a bound dims, fires nothing and reports itself disabled, the same contract [Pagination](/docs/components/pagination)’s steps carry at the ends of their range.

Here the model is three months and the chevrons roll it. Walking off the edge of the grid with the arrow keys pages too, and the focus lands on the day you aimed at once the new month arrives.

```slint
import { Calendar, CalendarMonth } from "@glint/components/calendar.slint";
import { Tokens } from "@glint/theme/tokens.slint";

export component Demo inherits Window {
    width: 380px;
    height: 440px;
    background: Tokens.color-background;

    // Slint arrays do not grow and one cannot be built from another, so a
    // host picks between the months it prepared rather than assembling one.
    in-out property <int> month: 1;
    property <[CalendarMonth]> april: [
        { label: "April 2026", day-count: 30, first-day-offset: 3, first-serial: 739707 },
    ];
    property <[CalendarMonth]> may: [
        { label: "May 2026", day-count: 31, first-day-offset: 5, first-serial: 739737 },
    ];
    property <[CalendarMonth]> june: [
        { label: "June 2026", day-count: 30, first-day-offset: 1, first-serial: 739768 },
    ];

    VerticalLayout {
        padding: 16px;
        spacing: 12px;
        alignment: center;

        HorizontalLayout {
            alignment: center;

            Rectangle {
                border-width: 1px;
                border-color: Tokens.color-border-hairline;
                border-radius: Tokens.radius-lg;
                background: Tokens.color-card;

                VerticalLayout {
                    Calendar {
                        months: root.month == 0
                            ? root.april
                            : (root.month == 1 ? root.may : root.june);
                        // April is the first month this host has, June the last.
                        can-prev-month: root.month > 0;
                        can-next-month: root.month < 2;
                        prev-month => { root.month -= 1; }
                        next-month => { root.month += 1; }
                    }
                }
            }
        }

        Text {
            text: "April is the first month and June the last, so a chevron at either end is disabled.";
            color: Tokens.color-muted-foreground;
            font-size: Tokens.typography-body-sm-size;
            horizontal-alignment: center;
            wrap: word-wrap;
        }
    }
}
```

### The caption dropdowns

`CalendarCaption.label` is the month’s own text. `CalendarCaption.dropdown` replaces it with a month picker and a year picker, so a distant month is one pick away instead of forty chevron clicks.

The dropdowns pick nothing: each reports the index it was given through `month-picked(index)` / `year-picked(index)` and you roll `months` to match — the same division `can-prev-month` already draws. Their open list is a card drawn inside the calendar rather than a popup of its own, because the calendar may already be inside one (ADR-0020), and closing it hands the keyboard to the day grid, which is where a user picking a month was going.

```slint
import { Calendar, CalendarCaption } from "@glint/components/calendar.slint";
import { Tokens } from "@glint/theme/tokens.slint";

export component Demo inherits Window {
    width: 380px;
    height: 420px;
    background: Tokens.color-background;

    in-out property <string> picked: "Take a month or a year from the caption.";

    VerticalLayout {
        padding: 16px;
        spacing: 12px;
        alignment: center;

        HorizontalLayout {
            alignment: center;

            Rectangle {
                border-width: 1px;
                border-color: Tokens.color-border-hairline;
                border-radius: Tokens.radius-lg;
                background: Tokens.color-card;

                VerticalLayout {
                    Calendar {
                        caption: CalendarCaption.dropdown;
                        months: [{
                            label: "May 2026",
                            day-count: 31,
                            first-day-offset: 5,
                            first-serial: 739737,
                        }];
                        month-options: [
                            "January", "February", "March", "April", "May", "June",
                            "July", "August", "September", "October", "November", "December",
                        ];
                        year-options: ["2024", "2025", "2026", "2027"];
                        month-index: 4;
                        year-index: 2;
                        month-picked(index) => {
                            root.picked = "Month " + index + " — roll your model to it.";
                        }
                        year-picked(index) => {
                            root.picked = "Year " + index + " — roll your model to it.";
                        }
                    }
                }
            }
        }

        Text {
            text: root.picked;
            color: Tokens.color-muted-foreground;
            font-size: Tokens.typography-body-sm-size;
            horizontal-alignment: center;
        }
    }
}
```

### Week numbers

A month that carries `week-numbers` prepends them as a leading column under `week-number-label`; a month that carries none draws no column at all. There are six entries, one per row of the grid, and they are yours for the same reason the rest of the model is: an ISO week number is arithmetic the component can no more do than it can name a month.

```slint
import { Calendar } from "@glint/components/calendar.slint";
import { Tokens } from "@glint/theme/tokens.slint";

export component Demo inherits Window {
    width: 400px;
    height: 360px;
    background: Tokens.color-background;

    VerticalLayout {
        padding: 16px;
        alignment: center;

        HorizontalLayout {
            alignment: center;

            Rectangle {
                border-width: 1px;
                border-color: Tokens.color-border-hairline;
                border-radius: Tokens.radius-lg;
                background: Tokens.color-card;

                VerticalLayout {
                    Calendar {
                        months: [{
                            label: "May 2026",
                            day-count: 31,
                            first-day-offset: 5,
                            first-serial: 739737,
                            week-numbers: [18, 19, 20, 21, 22, 23],
                        }];
                        week-number-label: "Wk";
                    }
                }
            }
        }
    }
}
```

### A Monday-first week

`weekday-labels` is the column header, left to right — seven entries in the grid’s own order, which the defaults spell Sunday-first. Pass your own to translate them, to spell them out, or to start the week on Monday: rotate the labels and shift `first-day-offset` in your model to match, since the grid counts the offset in the same order it draws the header.

Every other string the calendar ships is a property too, and they all default through the `CalendarStrings` global below.

```slint
import { Calendar } from "@glint/components/calendar.slint";
import { Tokens } from "@glint/theme/tokens.slint";

export component Demo inherits Window {
    width: 440px;
    height: 360px;
    background: Tokens.color-background;

    VerticalLayout {
        padding: 16px;
        alignment: center;

        HorizontalLayout {
            alignment: center;

            Rectangle {
                border-width: 1px;
                border-color: Tokens.color-border-hairline;
                border-radius: Tokens.radius-lg;
                background: Tokens.color-card;

                VerticalLayout {
                    Calendar {
                        weekday-labels: ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"];
                        months: [{
                            label: "May 2026",
                            day-count: 31,
                            // 1 May 2026 is a Friday: the fifth column of a
                            // Sunday-first week, the fourth of this one.
                            first-day-offset: 4,
                            first-serial: 739737,
                        }];
                    }
                }
            }
        }
    }
}
```

## API Reference

### Properties

| Property             | Type                 | Default                            | Description                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     |
| -------------------- | -------------------- | ---------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `months`             | `in [CalendarMonth]` | `[ }]`                             | The months to draw, left to right. One is the ordinary calendar; more than one is the two-month range view, paged as a strip.                                                                                                                                                                                                                                                                                                                                                                                                   |
| `day-states`         | `in [CalendarDay]`   | no default                         | Per-day states, indexed from the first rendered day — index 0 is `months[0].first-serial`. Shorter than the grid, or absent, is a grid of plain days: a read past the end is the default `CalendarDay`.                                                                                                                                                                                                                                                                                                                         |
| `weekday-labels`     | `in [string]`        | `CalendarStrings.weekday-initials` | Column headers, left to right — seven entries matching the grid's Sunday-first order. Defaults to the English initials (translatable); pass your own for another language or a Monday-first week (rotate the labels and shift `first-day-offset` in your model to match).                                                                                                                                                                                                                                                       |
| `mode`               | `in CalendarMode`    | `CalendarMode.single`              | Whether a pick is a day or an end of an interval.                                                                                                                                                                                                                                                                                                                                                                                                                                                                               |
| `selected-serial`    | `in-out int`         | `0`                                | Two-way; the serial of the selected day, or 0 for none. A range calendar leaves it alone — its selection is the two ends below.                                                                                                                                                                                                                                                                                                                                                                                                 |
| `range-from`         | `in-out int`         | `0`                                | Two-way; the ends of the selected interval, or 0 for none. `range-to` is 0 while the range is still forming, which is what makes the next pick close the interval rather than start another.                                                                                                                                                                                                                                                                                                                                    |
| `range-to`           | `in-out int`         | `0`                                |                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 |
| `focused-serial`     | `in-out int`         | `0`                                | Two-way; where the keyboard stands. 0 falls back to the selection, and then to the first day on screen — so a consumer that never touches it gets a sensible starting cell, and one that does can place the walk.                                                                                                                                                                                                                                                                                                               |
| `can-prev-month`     | `in bool`            | `true`                             | Whether there is a month to page to in each direction. The component has no notion of a bound of its own — the year/month model belongs to the consumer, which is what lets a host page the calendar however it likes — so a host that restricts a range says so here. A chevron at a bound dims, fires nothing and reports itself disabled, the same contract Pagination's steps carry at the ends of their range. Both default to true, so a host that never sets them keeps two live chevrons and behaves exactly as before. |
| `can-next-month`     | `in bool`            | `true`                             |                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 |
| `prev-month-label`   | `in string`          | `CalendarStrings.previous-month`   | Accessible names for the two chevrons. The month is not in them: a screen reader reads the button's name, and "previous month" is what it does regardless of which month is on screen.                                                                                                                                                                                                                                                                                                                                          |
| `next-month-label`   | `in string`          | `CalendarStrings.next-month`       |                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 |
| `caption`            | `in CalendarCaption` | `CalendarCaption.label`            | What stands between the chevrons: the months' own captions, or the month and year dropdowns. The dropdowns drive the view rather than one of its grids, so a strip of months has one pair of them and each grid keeps its own name in the accessibility tree.                                                                                                                                                                                                                                                                   |
| `month-options`      | `in [string]`        | no default                         | What the two dropdowns offer, and which entry each stands on. The component neither builds nor formats these — same division as the rest of the model — and it picks no month either: it reports the index and the host rolls `months` to match.                                                                                                                                                                                                                                                                                |
| `year-options`       | `in [string]`        | no default                         |                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 |
| `month-index`        | `in int`             | `-1`                               |                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 |
| `year-index`         | `in int`             | `-1`                               |                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 |
| `month-picker-label` | `in string`          | `CalendarStrings.month`            | The dropdowns' own names, as against the values they stand on.                                                                                                                                                                                                                                                                                                                                                                                                                                                                  |
| `year-picker-label`  | `in string`          | `CalendarStrings.year`             |                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 |
| `week-number-label`  | `in string`          | `CalendarStrings.week-number`      | Heading of the week-number column, for the months that carry one.                                                                                                                                                                                                                                                                                                                                                                                                                                                               |
| `in-range-note`      | `in string`          | `CalendarStrings.in-range`         | What a day between the two ends of a settled range announces. The tree has no property for "inside the selection", so it rides the cell's description (ADR-0013).                                                                                                                                                                                                                                                                                                                                                               |
| `auto-focus`         | `in bool`            | `false`                            | Take the keyboard as the grid is built. A `Panel` rider cannot reach in and focus it — `focus()` does not cross the PopupWindow boundary (panel.slint), and calling a public function on content inlined into one is rejected outright — so the surface asks for it with a property instead. `DatePicker` sets it, which is what makes the arrows, Home/End, the pages and Enter reachable at all inside its popup. Same shape `Command` uses for its search field.                                                             |
| `preview-serial`     | `out int`            | no default                         | The open end of a forming range: the day under the pointer while `range-from` is set and `range-to` is not. 0 the rest of the time — including over a complete range, which has both its ends already. It is what paints the preview, and what a trigger shows before the second pick lands.                                                                                                                                                                                                                                    |

### Callbacks

| Callback                   | Description                                                                                                                                                             |
| -------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `day-selected(int)`        | Fired when the user picks a day, by pointer, by Enter or through the accessible default action. Carries the day's serial; a day the host disabled fires nothing at all. |
| `range-selected(int, int)` | Fired when the second end of a range lands, with the two ends in order. A range calendar reports both: `day-selected` for each pick, this once the interval is whole.   |
| `month-picked(int)`        | Fired with the index of the month or year taken from the caption dropdown.                                                                                              |
| `year-picked(int)`         |                                                                                                                                                                         |
| `prev-month()`             | Fired when the previous-month chevron is clicked, unless it is at a bound.                                                                                              |
| `next-month()`             | Fired when the next-month chevron is clicked, unless it is at a bound.                                                                                                  |

### Enums

| Enum              | Values              |
| ----------------- | ------------------- |
| `CalendarMode`    | `single`, `range`   |
| `CalendarCaption` | `label`, `dropdown` |

### CalendarStrings

Every string the calendar ships as a default, in one place. A composite wrapping a `Calendar` cannot alias what is inside a `PopupWindow`, so [DatePicker](/docs/components/date-picker) declares the same properties and defaults them from here — spelling them twice is how the two would drift apart.

### Properties

| Property           | Type           | Default    | Description |
| ------------------ | -------------- | ---------- | ----------- |
| `weekday-initials` | `out [string]` | no default |             |
| `previous-month`   | `out string`   | no default |             |
| `next-month`       | `out string`   | no default |             |
| `week-number`      | `out string`   | no default |             |
| `month`            | `out string`   | no default |             |
| `year`             | `out string`   | no default |             |
| `in-range`         | `out string`   | no default |             |

## Accessibility

- **A month is a table, and a day is one of its cells.** Each grid carries the `table` role under its own `label`, and every day of the month is a `list-item` naming itself — “14 May 2026” — with its index and the month’s own day count, so assistive technology can say “14 of 31”.
- **Selecting a day is its default action**, which is what lets a screen reader pick one without a pointer. The cell reports itself selected, and in a range both ends do.
- **A blank is nothing at all.** The leading and trailing cells outside the month are in no accessibility tree, take no click, and carry serial `0` — which the one refusal path turns away, so the six dead cells under May cannot pick June 1–6 by any route.
- **A disabled day stays in the grid.** It keeps its cell and its index, reports itself disabled, and refuses the pointer, `Enter` and the accessible default action alike.
- **What has no property rides the description.** A day’s `note` and, inside a settled range, `in-range-note` are read after the day’s name, because the tree has no property for either (ADR-0013).
- **The chevrons are named buttons.** `prev-month-label` and `next-month-label` name them; the month is deliberately not in the name, because “previous month” is what the button does whichever month is on screen. At a bound a chevron dims, leaves the tab order and reports itself disabled.
- **The caption dropdowns are comboboxes**, each named for what it is (“Month”, “Year”) and reporting the value it stands on. Their open list is a `list` whose rows are its items, so a screen reader can say “5 of 12”.
- **The grid is one tab stop, not forty-two.** `←` / `→` move a day, `↑` / `↓` a week, `Home` / `End` reach the ends of the focused week without leaving the strip, and `Page Up` / `Page Down` are the chevrons under another name. `Enter` and `Space` pick whatever the walk landed on.
- **A walk off the strip pages.** The focus goes to the day you aimed at and the host’s model catches up; at a bound the move is refused outright, because nothing will be drawn there.
- **Keyboard-only focus ring**, around the day the arrows landed on rather than around the whole grid.
