Interaction

Keyboard & Accessibility

Everything a pointer can do, the keyboard can do. The grid is one tab stop with a name, every move inside it is announced, and the parts that open are real dialogs.

One Tab Stop

The grid of every view is a single element with role="application" and a name such as "Calendar, Week view", so Tab passes through the calendar in one step and the arrow keys work inside it. Focus starts on today at 9:00 when today is in view, or on the first day otherwise; a focus ring marks the slot. Event chips are buttons, so Tab also visits them one by one for a reader who prefers that.

Keys

⇥

Into the grid. Focus starts on today at 9:00, or on the first day of the range

LeftRight

Previous or next day. Past the edge the view steps a period and focus wraps

UpDown

Previous or next slot. In the month grid, previous or next week

↖↘

First or last slot of the day; first or last day of the row in the month

PgUpPgDn

Previous or next period, keeping the focused position

␣

Select the event at the focused slot. Press again to cycle through overlapping ones

↵

Create an event at the focused slot, open the createPanel, or report the slot when creatable is off

⌦⌫

Delete the selected event. A locked event or an occurrence stays, and the reason is announced

Esc

Cancel a drag in progress; otherwise clear the selection

// The grid is one tab stop. Inside it:
//
//   Left, Right        previous or next day; past the edge the view steps a period and wraps
//   Up, Down           previous or next slot in the time grid; previous or next week in the month
//   Home, End          first or last slot of the day; first or last day of the row in the month
//   Page Up, Page Down previous or next period, keeping the focused position
//   Space              select the event at the focused slot; again for the next one there
//   Enter              create an event at the focused slot, open createPanel, or report it
//   Delete, Backspace  delete the selected event; a locked one or an occurrence says why it stays
//   Escape             cancel a drag in progress, else clear the selection
//   Tab                leave the grid; chips are buttons, so Tab also walks them
//
// Left and Right swap under dir="rtl".

Try It

Click into the grid or press Tab until it is focused, then use the keys above. The log reads the scheduler's live region, so it shows exactly what a screen reader would say. Move to a slot with an event, press Space to select it, then Delete. The daily standup is a series, so Delete leaves it and says why: an occurrence is not editable, and only its rule can remove it.

Oct 19 – 25, 2026

GMT+7
Mon 19
Tue 20
Wed 21
Thu 22
Fri 23
Sat 24
Sun 25
All day
Lunch break

Screen reader announcements

  • Focus the grid and move around to hear what is announced.
// A polite live region at the end of the scheduler. What it says, in the default English:
'Saturday, September 19, 2026, 9:30 AM'       // after every focus move
'Standup, 9:00 AM to 9:15 AM'                 // when the focused slot holds the selected event
'Selected Standup'                            // Space
'Created New event'                           // Enter, a drag, a drop, createPanel.create()
'Moved Standup to 10:00 AM'                   // a move
'Standup now ends at 9:30 AM'                 // a resize
'Updated Standup'                             // eventPanel.update()
'Deleted Standup'                             // Delete, the popover, eventPanel.remove()
'Cancelled'                                   // Escape during a gesture
'Could not save Standup, change reverted'     // a rollback
'Standup was updated elsewhere'               // keep-server after a conflict
'Standup cannot be edited'                    // Delete on a locked event or an occurrence of a series

Announcements

Every focus move announces the date and time, or the selected event when the slot holds it. Every change announces what happened, including the ones the user did not ask for: a rollback after a failed save, and the server's version winning a conflict. The sentences are labels, so a pack translates them; see Localization.

<!-- Every sentence is a label. Change one, or ship a pack: see Localization. -->
<Scheduler
  bind:events
  labels={{
    grid: (view) => `Team calendar, ${view}`,
    announce: { moved: (event, start) => `${event.title} now starts at ${start}` }
  }}
/>

Names and Roles

The ARIA shape

ElementRole and name
The gridrole="application", one tab stop, aria-label from labels.grid
A month cellrole="group", aria-label with the date, the holiday and the event count
Todayaria-current="date" on its header or cell
An event chipA button. aria-pressed when selected; aria-haspopup="dialog" and aria-expanded when it opens a popover
The popover and the panelsDialogs from sv5ui: focus is trapped, Escape closes, focus returns to the chip
Popover actionsNamed buttons with a tooltip that opens on keyboard focus only
The toolbarNamed arrows and Today; the view switcher is a group named labels.views; the title is a polite live region
Week numbersThe short form is aria-hidden; a visually hidden span reads "Week 38"
The live regionaria-live="polite" aria-atomic, visually hidden, at the end of the scheduler

Focus Management

Closing a popover returns focus to the chip that opened it. The event panel, the create panel and the sidebar slide-over are sv5ui dialogs rendered inside the scheduler rather than at the end of the document: focus is trapped while they are open, Escape closes them, and the page behind them keeps its scroll position.

Right to Left

Under dir="rtl" Left and Right follow the reading direction, resize edges swap, and the sidebar and panels open from the other side. Nothing else changes.

<!-- Left and Right follow the reading direction. Drag, resize edges and the
     sidebar side follow it too. -->
<Scheduler bind:events dir="rtl" locale="ar-EG" labels={ar} />

Reduced Motion

The slide back after a failed save and the sidebar's slide are skipped when the reader asks for reduced motion. The result is the same, without the animation.

Colour Contrast

Text on the surface, the grid lines and the muted labels all meet 4.5:1 in both themes. Two solid event colours do not, in the default sv5ui palette: white text on success measures 3.2:1 and on info 3.9:1. Those are the theme's tokens rather than the scheduler's classes, so a theme with darker success and info colours fixes both; until then, prefer the other six for events that appear as solid pills in the month view and the all-day row.

Props

The props that shape what the keyboard and a screen reader get.

PropTypeDefault
labelsPartialLabelsEnglish
dir'ltr' | 'rtl'inherited
refHTMLElement | nullnull
editablebooleantrue
creatablebooleantrue