InteractionS’applique àLite et Pro
Clavier, accessibilité et tactile
Dans Pro, activez keyboardEnabled (désactivé par défaut), ajoutez keyboardMode: 'Full' pour le jeu de touches complet et keyboardTarget: 'component' pour que les touches n’agissent que lorsque la grille a le focus. La grille est un arrêt de tabulation unique avec role="grid" : le focus se déplace via aria-activedescendant et les modifications sont annoncées en neuf langues. Lite intègre la navigation aux flèches. Sur écran tactile, maintenez le doigt sur un événement pour le déplacer et faites glisser ses poignées pour le redimensionner.
Un planificateur de ressources est une grande grille à deux dimensions, ce qui rend la prise en charge du clavier et des lecteurs d’écran plus difficile que pour une liste ou un formulaire. SuperScheduler donne à la grille un arrêt de tabulation unique, un focus itinérant qui résiste à la virtualisation, des annonces vocales pour le focus et les modifications, et des équivalents clavier pour déplacer et redimensionner les événements. Ce guide explique ce que fait chaque édition, comment l’activer et ce que votre application doit encore fournir.
Lite et Pro en un coup d’œil
Lite (super-scheduler-lite) | Pro (super-scheduler) | |
|---|---|---|
| Clavier | Toujours actif : les flèches déplacent la cellule active, Entrée ou Espace l’active | Désactivé par défaut ; keyboardEnabled, plus keyboardMode: 'Full' pour le modèle complet |
| Événements | Boutons natifs : Tab les atteint, Entrée ou Espace les clique | Atteints avec les flèches à l’intérieur de la grille ; Entrée lance le flux de clic |
| Édition au clavier | Non (édition en lecture seule) | Déplacement avec Alt+flèches, redimensionnement avec Alt+Maj+Flèche gauche/droite (mode Full) |
| Annonces | Non | Focus, sélection et modifications validées, en neuf langues |
| Nom de la grille | Option ariaLabel (« Resource schedule » par défaut) | « Scheduler » intégré (« Planificador » pour les locales espagnoles) |
| Tactile | Défilement et appuis natifs | Appui long pour déplacer, poignées pour redimensionner, pincement pour zoomer |
Activer le clavier dans Pro
Pro garde le clavier désactivé tant que vous ne définissez pas keyboardEnabled: true. Le mode par défaut, keyboardMode: 'SuperScheduler', gère les flèches, Entrée et Maj+Flèche gauche/droite. keyboardMode: 'Full' ajoute le reste du modèle : Début/Fin, Page précédente/Page suivante, Espace, le déplacement et le redimensionnement des événements, la touche Menu et l’annonce de textes d’aide. Le mode Full sans keyboardEnabled ne fait rien et émet un avertissement en développement.
keyboardTarget détermine où les touches sont écoutées. La valeur par défaut, 'document', réagit aux touches pressées n’importe où dans la page en dehors des champs de texte, ce qui prive le défilement de la page des flèches. Utilisez 'component' pour que les touches n’agissent que lorsque la grille a le focus ; c’est aussi le bon choix quand une page contient plusieurs planificateurs.
import { useMemo } from 'react'
import { SuperSchedulerComponent } from 'super-scheduler'
import type { SchedulerProps, SuperScheduler } from 'super-scheduler'
import 'super-scheduler/styles.css'
// Module-level: the same object on every render.
const KEYBOARD: SchedulerProps = {
keyboardEnabled: true,
// Keys act only while the grid has focus; the page keeps its own arrow-key scrolling.
keyboardTarget: 'component',
keyboardMode: 'Full',
keyboardOptions: { pageRows: 10, zoomKeys: true },
}
const UNDER_MAINTENANCE = new Set<SuperScheduler.ResourceId>(['room-104'])
export function AccessiblePlanner(props: {
resources: SuperScheduler.ResourceData[]
events: SuperScheduler.EventData[]
onOpen: (id: SuperScheduler.EventId) => void
}) {
const { onOpen } = props
const events = useMemo(() => props.events.slice(), [props.events])
const config = useMemo<SchedulerProps>(
() => ({
...KEYBOARD,
// Enter on a focused event runs the same click flow as the pointer.
onEventClick: (args) => onOpen(args.e.id()),
// Alt + arrow moves go through the same rules as drags.
onEventMoving: (args) => {
if (UNDER_MAINTENANCE.has(args.resource)) {
args.allowed = false
args.message = 'Room under maintenance'
}
},
}),
[onOpen],
)
return (
// The grid's own accessible name is generic: label the region around it.
<section aria-labelledby="room-plan-title">
<h2 id="room-plan-title">Room plan, October 2026</h2>
<SuperSchedulerComponent
{...config}
startDate="2026-10-01"
days={31}
scale="Day"
locale="en-us"
resources={props.resources}
events={events}
/>
</section>
)
}Vous devriez pouvoir entrer dans la grille avec Tab, vous déplacer avec les flèches, appuyer sur Entrée sur un événement pour l’ouvrir et sur Alt+Flèche bas sur un événement pour commencer à le déplacer. Le déplacer sur la chambre 104 puis appuyer sur Entrée annonce « Not allowed here », et l’événement reste à sa place.
keyboardOptions règle le mode Full :
| Option | Défaut | Effet |
|---|---|---|
pageRows | lignes visibles moins une | Nombre de lignes parcourues par Page précédente et Page suivante |
contextMenuKey | true en mode Full | La touche Menu et Maj+F10 ouvrent le menu de l’élément qui a le focus |
bubbleOnFocus | false | Affiche la bulle de l’événement tant qu’il a le focus clavier |
selectAll | true en mode Full | Ctrl/Cmd+A sélectionne tous les événements visibles (nécessite allowMultiSelect) |
zoomKeys | false | Ctrl/Cmd avec = ou +, - et 0 zooment vers l’avant, vers l’arrière et reviennent au niveau initial |
zoomKeys est désactivé par défaut pour que les raccourcis de zoom de page du navigateur continuent de fonctionner.
Touches
| Touches | Mode | Effet |
|---|---|---|
| Flèches | les deux | Déplacent le focus. Gauche et Droite s’arrêtent sur chaque événement et chaque cellule vide de la ligne ; Haut et Bas changent de ligne. |
| Entrée | les deux | Sur un événement : le flux de clic (onEventClick, puis eventClickHandling). Sur une cellule : la sélectionne comme plage de temps. |
| Maj+Flèche gauche / Maj+Flèche droite | les deux | Étend une plage de temps à partir de la cellule qui a le focus ; relâcher Maj la sélectionne. |
| Espace | Full | Sur une cellule : l’ajoute à la sélection ou l’en retire. Sur un événement : comme Entrée. |
| Début / Fin | Full | Première ou dernière cellule de la ligne ; avec Ctrl/Cmd, première ou dernière ligne. |
| Page précédente / Page suivante | Full | Déplace le focus de pageRows lignes. |
| Alt+flèches | Full | Commence à déplacer l’événement qui a le focus ; les flèches le déplacent, Entrée ou Espace le dépose. |
| Alt+Maj+Flèche gauche / droite | Full | Commence à redimensionner la fin de l’événement qui a le focus ; Gauche et Droite la modifient, Entrée confirme. Sur un titre de colonne, déplace la colonne. |
| Échap | les deux | Annule un déplacement, un redimensionnement ou une plage au clavier. Pendant un déplacement ou un redimensionnement au clavier, Tab l’annule aussi et quitte la grille. |
| Touche Menu, Maj+F10 | Full | Ouvre le menu de l’événement, de l’en-tête de ligne ou de la cellule qui a le focus. |
| Ctrl/Cmd+A | Full | Sélectionne tous les événements visibles. |
Modèle de focus et lecteurs d’écran
La grille Pro a role="grid" avec aria-rowcount et aria-colcount ; les en-têtes de ligne sont des rowheader, les cellules d’en-tête de temps des columnheader, et les cellules et événements des gridcell. Les lignes et cellules hors de la zone visible ne sont pas dans le DOM : le focus ne passe donc pas d’un élément à l’autre. À la place :
- la racine de la grille est l’unique arrêt de tabulation (
tabindex="0") tant que le clavier est activé ; - la cellule ou l’événement qui a le focus est un nœud de focus vers lequel la racine pointe avec
aria-activedescendant, et ce nœud résiste au défilement et à la virtualisation ; - le libellé du nœud de focus se lit comme « Room 101, Oct 1 » pour une cellule et « Ana, Room 101, Oct 2 – 4 » pour un événement.
Le nom d’un événement vient de son ariaLabel, puis de son text, puis de son html en texte brut, puis de son id. Le nom d’une ligne est le name de sa ressource. Quand html affiche autre chose que text, définissez ariaLabel dans onBeforeEventRender :
import { SuperScheduler } from 'super-scheduler'
import type { SchedulerProps } from 'super-scheduler'
type Visit = { patient: string; kind: 'checkup' | 'surgery'; color: string }
const KIND_LABEL: Record<Visit['kind'], string> = { checkup: 'check-up', surgery: 'surgery' }
/** Pass as `onBeforeEventRender` (module-level, so its identity never changes). */
export const labelVisit: NonNullable<SchedulerProps['onBeforeEventRender']> = (args) => {
const visit = args.data as SuperScheduler.EventRenderData<Visit>
args.data.backColor = visit.color
// Dark text on light fills and light text on dark ones (WCAG contrast).
args.data.fontColor = SuperScheduler.ColorUtil.contrasting(visit.color)
// `html` is trusted markup: escape what users typed.
args.data.html = `<strong>${SuperScheduler.Util.escapeHtml(visit.patient)}</strong>`
// The accessible name of the event. Focus labels and announcements append
// the row name and the dates, so they are not repeated here.
args.data.ariaLabel = `${visit.patient}, ${KIND_LABEL[visit.kind]}`
}Comme les libellés de focus et les annonces ajoutent la ligne et les dates, ariaLabel ne doit contenir que ce qui identifie l’événement. SuperScheduler.ColorUtil.contrasting(color) renvoie un texte foncé pour les fonds clairs et un texte clair pour les fonds foncés.
Le nom accessible de la grille elle-même est « Scheduler » (« Planificador » quand la locale commence par es), et la version 0.1.0 ne propose aucune option pour le changer. Placez le planificateur dans une région étiquetée par un titre visible, comme le fait le snippet de configuration.
Depuis le code, control.keyboard propose focusEvent(e or id), focusCell(date, resource), getFocus(), move(direction), clearFocus() et resetFocus(). onKeyboardFocusChange (annulable) et onKeyboardFocusChanged signalent les changements de focus avec previous et focus ({ e } ou { cell }) ; utilisez-les pour synchroniser un panneau de détail avec le clavier.
Annonces
Une région live non intrusive (polite), placée dans la grille, annonce :
- en mode Full, un court texte d’aide la première fois que la grille reçoit le focus ;
- les sélections (« Selected: Room 101, Oct 4 »), les désélections et « N events selected » ;
- le début d’un déplacement ou d’un redimensionnement au clavier, avec des instructions ;
- les déplacements et redimensionnements validés (« Event moved to Room 101, Oct 3 – 5 »), qu’ils viennent du clavier ou du pointeur ;
- « Cancelled », « Not allowed here » et les déplacements de colonnes.
Les textes suivent le premier segment de la locale du planificateur : anglais, espagnol, catalan, basque, galicien, allemand, français, italien et portugais. Les autres langues se rabattent sur l’anglais. Les packs de langue autres que l’anglais et l’espagnol sont chargés à la demande.
La touche Menu
En mode Full, la touche Menu et Maj+F10 ouvrent le menu de l’élément qui a le focus lorsqu’il s’agit d’un SuperScheduler.Menu : le contextMenu de l’événement (ou le contextMenu du contrôle), contextMenuResource sur un en-tête de ligne, et contextMenuSelection sur une cellule. Si votre application dessine son propre menu depuis onEventRightClick, gérez vous-même la touche dans onKeyDown :
import type { SchedulerProps, SuperScheduler } from 'super-scheduler'
export interface MenuRequest {
readonly eventId: SuperScheduler.EventId
/** Viewport coordinates where the application opens its own menu. */
readonly x: number
readonly y: number
}
/**
* Opens the application's menu from the pointer (right click) and from the keyboard
* (Menu key, Shift+F10). The library's Menu key support covers `SuperScheduler.Menu`
* objects only, so a custom menu handles the key in `onKeyDown`.
*/
export function menuHandlers(open: (request: MenuRequest) => void): SchedulerProps {
return {
onEventRightClick: (args) => {
args.preventDefault()
open({ eventId: args.e.id(), x: args.originalEvent.clientX, y: args.originalEvent.clientY })
},
onKeyDown(args) {
const key = args.originalEvent
if (key.key !== 'ContextMenu' && !(key.key === 'F10' && key.shiftKey)) return
const focused = this.keyboard.getFocus().e
const root = key.target
if (focused === undefined || !(root instanceof HTMLElement)) return
// Skips the library's own handling of the key.
args.preventDefault()
// The grid points at the focused item with aria-activedescendant.
const ring = root.ownerDocument.getElementById(
root.getAttribute('aria-activedescendant') ?? '',
)
const box = (ring ?? root).getBoundingClientRect()
open({ eventId: focused.id(), x: box.left, y: box.bottom })
},
}
}Étalez menuHandlers(open) dans les props du planificateur, en le mémoïsant. Votre menu est alors responsable de son propre focus : déplacez le focus dans le menu à son ouverture et rendez-le à la grille à sa fermeture.
Alternatives accessibles au glisser
Le critère de succès 2.5.7 des WCAG 2.2 (↗) exige un moyen d’accomplir avec de simples actions de pointeur ce que permet le glisser. Le mode clavier Full couvre les utilisateurs du clavier, mais pas une personne qui utilise un pointeur simple, un contacteur ou la commande vocale. Donnez à chaque événement un chemin sans glisser, par exemple un panneau de détail ou une entrée de menu contextuel avec des champs début, fin et ressource qui met à jour votre state (ou appelle control.events.update avec un nouvel objet). Exécutez la même validation que dans onEventMoving avant d’enregistrer, pour que les deux chemins appliquent les mêmes règles.
Tactile
Sur écran tactile, un doigt fait défiler la frise. Le reste du modèle tactile :
- Déplacer un événement. Maintenez le doigt immobile sur l’événement pendant
tapAndHoldTimeout(300 ms), puis faites glisser. Un doigt qui bouge de plus de 8 px environ avant ce délai fait défiler à la place.eventTapAndHoldHandlingdétermine l’effet d’un appui long :'Move'(par défaut),'ContextMenu'(ouvre leSuperScheduler.Menude l’événement) ou'Disabled'. Un appui long sur un en-tête de ligne ouvrecontextMenuResource. - Redimensionner. Un appui sur un événement affiche ses poignées, avec des cibles tactiles de 44 px (
--super-scheduler-handle-target) ; faites glisser une poignée pour redimensionner. Le bord d’un événement sous un doigt le déplace au lieu de le redimensionner. - Sélectionner du temps. Un appui sur une cellule vide la sélectionne (
origin: 'click') ; un appui long suivi d’un glisser sélectionne une plage. - Zoom. Un pincement à deux doigts zoome (
zoomGesture.pinch, activé par défaut). Un deuxième doigt annule tout glisser en cours. - Survol. Le tactile n’a pas de survol. Les cartes de survol de
eventHoverpeuvent être épinglées d’un appui (pin: 'click') ; les zones avecvisibility: 'TouchVisible'restent visibles sur les appareils tactiles, alors que les zones'Hover'n’apparaissent pas.
Lite est en lecture seule : il défile nativement et signale les appuis via onEventClick et onTimeRangeClick.
Animations réduites, contraste et couleurs forcées
Pro lit les préférences de l’utilisateur via le CSS et les media queries, sans option à définir :
prefers-reduced-motion: reducerègle--super-scheduler-durationsur0s, rendcontrol.zoom.animateTo()instantané et supprime les transitions des cartes de survol ;prefers-contrast: morerenforce les bordures, les lignes de grille, les séparateurs de lignes et le contour de sélection ;forced-colors: activefait passer le thème aux couleurs système (Highlight,CanvasText,GrayText) et supprime les grisés décoratifs, comme ceux des week-ends et du jour courant.
Lite adapte aussi ses bordures et son contour de focus aux couleurs forcées. Si vous remplacez les couleurs par vos propres tokens ou votre propre CSS, testez de nouveau ces modes : vos surcharges peuvent les annuler.
Clavier dans Lite
Lite ne demande aucune configuration. La grille peut recevoir le focus, porte aria-readonly et tient son nom de l’option ariaLabel. Les flèches déplacent la cellule active (annoncée via aria-activedescendant), Entrée ou Espace appelle onTimeRangeClick pour cette cellule, et chaque événement est un bouton natif.
import { SuperSchedulerComponent } from 'super-scheduler-lite'
import type { SuperScheduler } from 'super-scheduler-lite'
import 'super-scheduler-lite/styles.css'
const ROOMS: SuperScheduler.ResourceData[] = [
{ id: 'r101', name: 'Room 101' },
{ id: 'r102', name: 'Room 102' },
]
const STAYS: SuperScheduler.EventData[] = [
{ id: 'b1', resource: 'r101', start: '2026-10-02', end: '2026-10-05', text: 'Lena Fischer' },
]
export function OccupancyBoard(props: {
onOpenBooking: (id: SuperScheduler.EventData['id']) => void
onOpenDay: (resource: SuperScheduler.ResourceData['id'], day: string) => void
}) {
return (
<SuperSchedulerComponent
// The grid's accessible name (default "Resource schedule").
ariaLabel="Room occupancy, October 2026"
startDate="2026-10-01"
days={31}
scale="Day"
resources={ROOMS}
events={STAYS}
// Events are native buttons: Tab reaches them, Enter and Space click them.
onEventClick={({ e }) => props.onOpenBooking(e.data.id)}
// Arrow keys move the active cell; Enter or Space activates it.
onTimeRangeClick={({ start, resource }) =>
props.onOpenDay(resource, start.toString('yyyy-MM-dd'))
}
/>
)
}Ce que votre application doit encore garantir
La bibliothèque gère la grille. Les points suivants relèvent de votre application :
- Contraste. Les couleurs d’événement que vous définissez avec
backColor,fontColor, du CSS ou du contenu personnalisé doivent respecter les exigences de contraste en mode clair comme en mode sombre. - Noms dans le contenu personnalisé. Le HTML de
onBeforeEventRender, les slots React et le balisage des en-têtes de ligne sont sous votre responsabilité : gardez untextexplicite ou définissezariaLabel, donnez des alternatives textuelles aux icônes et évitez les contrôles interactifs dans le contenu des événements. - Menus, boîtes de dialogue et panneaux. Gestion du focus, libellés et prise en charge d’Échap pour tout ce que vous ouvrez depuis la grille.
- Un chemin sans glisser pour chaque action de glisser, comme décrit plus haut.
- Structure de la page. Un titre ou un libellé pour la région qui entoure la grille, et une place cohérente dans l’ordre de tabulation.
- Tests. Vérifiez votre configuration avec un lecteur d’écran et un outil de contrôle automatique ; les surcharges et le contenu personnalisé peuvent changer ce qu’entendent les utilisateurs.
Voir aussi
- Arborescences de ressources, colonnes et sélection précise ce que sélectionnent Espace, Entrée et Ctrl/Cmd+A.
- Glisser, redimensionner et règles métier explique les règles que suivent les déplacements au clavier.
- Thèmes, tokens, Tailwind et mode sombre traite des couleurs de focus et de sélection.