CALENDAR DISPLAY: Events month grid (display-only, not a date picker)
VARIANTS: Full month | Empty month | Compact (container-query collapse)

"Browse by month" view for the Events vertical. A real <table> (caption + th[scope=col]) — no role="grid", no roving tabindex: event entries are ordinary links in the tab order. Zero JS — month prev/next is server-driven navigation (here: anchor links between the two demo months; a disabled button marks a range edge). Event chips are tone-named (-info / -warning / -accent) and bound to meaning by the legend. Below ~560px of container width, chips collapse to dots + a count with a screen-reader-only category breakdown.

Full month with events

Full month: August 2026 (Monday start), today marked on the 12th — filled navy circle, aria-current="date" and a screen-reader-only "(today)" suffix, never colour alone. Out-of-month days are muted and their date numbers carry a screen-reader-only month suffix. Saturday the 15th shows the .calendar-display-more "+2 more" overflow link (a link to the day's list view — no popup). "Previous" is a real disabled button (range edge); "Next" is a real link to the September calendar below.

August 2026

Events in August 2026
Monday Tuesday Wednesday Thursday Friday Saturday Sunday
27 July
28 July
29 July
30 July
31 July
1
2
3
4

1 event: 1 open day

5
6

1 event: 1 taster event

7
8
9
10
11
13
14
16
17
18
19
20

1 event: 1 open day

21
22
23
24
25

1 event: 1 deadline

26
27
28

1 event: 1 taster event

29
30
31
1 September
2 September
3 September
4 September
5 September
6 September
Open days Deadlines Taster events
<div class="calendar-display"> <table class="calendar-display-table"> ... <a class="calendar-display-event calendar-display-event-info" href="...">Open Day — ...</a>

Empty month

Empty month: September 2026 with no events — the grid renders normally (5 week rows here; the row count is markup-driven, never padded to 6), the legend is omitted because there is nothing to key, and a short note under the table tells the parent why it's quiet. "Next" is the disabled range edge; "Previous" links back up to August. This scenario deliberately does not compose .empty-state: the grid isn't empty — every day cell still renders, still shows its date, and is still browsable — so a centred icon + title + action block would cover working content to announce the absence of a subset of it. .empty-state belongs on the Events list view, where the region really is empty.

September 2026

Events in September 2026
Monday Tuesday Wednesday Thursday Friday Saturday Sunday
31 August
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
1 October
2 October
3 October
4 October

No events scheduled in September yet — providers usually publish autumn open days after enrolment closes.

<td class="calendar-display-outside">...</td> — muted out-of-month cell; row count is markup-driven (5 or 6 weeks)

Compact — narrow container

Compact: the same August markup inside a ~340px column. The collapse is a container query on .calendar-display (threshold 560px), so it also fires when the calendar sits in a narrow column beside a filter rail at desktop widths — and on any small viewport. Weekday headers drop to single letters (full names stay for screen readers), and each day's chips are replaced by category dots + a visible count backed by a screen-reader-only breakdown ("2 events: 1 deadline, 1 open day"). The hidden chip links leave the tab order entirely — no sub-24px targets on small screens.

August 2026

Events in August 2026 (compact demo)
Monday Tuesday Wednesday Thursday Friday Saturday Sunday
27 July
28 July
29 July
30 July
31 July
1
2
3
4

1 event: 1 open day

5
6

1 event: 1 taster event

7
8
9
10
11
13
14
16
17
18
19
20

1 event: 1 open day

21
22
23
24
25

1 event: 1 deadline

26
27
28

1 event: 1 taster event

29
30
31
1 September
2 September
3 September
4 September
5 September
6 September
Open days Deadlines Taster events
<p class="calendar-display-summary"><span class="calendar-display-dot calendar-display-dot-info" aria-hidden="true"></span><span class="calendar-display-count" aria-hidden="true">2</span><span class="tw:sr-only">2 events: ...</span></p>

Quick Reference

Shell: .calendar-display > .calendar-display-header (.calendar-display-title + .calendar-display-nav) + .calendar-display-table + .calendar-display-legend
Grid: table > caption(sr-only) + th.calendar-display-weekday[scope=col] + td[.calendar-display-outside] > .calendar-display-day
Day: .calendar-display-date [.calendar-display-date-today + aria-current="date" on the td] + .calendar-display-events > .calendar-display-event-{info|warning|accent} + .calendar-display-more
Compact (@container ≤560px): .calendar-display-summary > .calendar-display-dot-{info|warning|accent} + .calendar-display-count + sr-only breakdown