Zum Inhalt springen
SuperScheduler

InteraktionGilt fürLite und Pro

Tastatur, Barrierefreiheit und Touch

Setzen Sie in Pro keyboardEnabled (standardmäßig aus), ergänzen Sie keyboardMode: 'Full' für den vollständigen Tastensatz und keyboardTarget: 'component', damit Tasten nur wirken, solange das Raster den Fokus hat. Das Raster ist ein einziger Tab-Stopp mit role="grid": Der Fokus wandert über aria-activedescendant, und Änderungen werden in neun Sprachen angesagt. Lite bringt die Navigation mit den Pfeiltasten von Haus aus mit. Auf Touchscreens halten Sie ein Ereignis gedrückt, um es zu verschieben, und ziehen an seinen Griffen, um seine Dauer zu ändern.

Geprüft mit v0.1.0 · überarbeitet am 7. Oktober 2026.md

Ein Ressourcenplaner ist ein großes zweidimensionales Raster; das macht die Unterstützung von Tastatur und Screenreader schwieriger als bei einer Liste oder einem Formular. SuperScheduler gibt dem Raster einen einzigen Tab-Stopp, einen wandernden Fokus, der die Virtualisierung übersteht, gesprochene Ansagen für Fokus und Änderungen sowie Tastaturentsprechungen zum Verschieben von Ereignissen und Ändern ihrer Dauer. Dieser Leitfaden erklärt, was jede Edition leistet, wie Sie es aktivieren und was Ihre Anwendung noch selbst bereitstellen muss.

Lite und Pro im Überblick

Lite (super-scheduler-lite)Pro (super-scheduler)
TastaturImmer aktiv: Pfeiltasten bewegen die aktive Zelle, Eingabe oder Leertaste aktiviert sieStandardmäßig aus; keyboardEnabled, dazu keyboardMode: 'Full' für das vollständige Modell
EreignisseNative Buttons: Tab erreicht sie, Eingabe oder Leertaste klickt sieMit den Pfeiltasten innerhalb des Rasters erreichbar; Eingabe startet den Klickablauf
Bearbeiten per TastaturNein (schreibgeschützte Edition)Verschieben mit Alt + Pfeiltasten, Dauer ändern mit Alt + Umschalt + Links/Rechts (Modus Full)
AnsagenNeinFokus, Auswahl und übernommene Änderungen, in neun Sprachen
Name des RastersOption ariaLabel (Standard „Resource schedule“)Eingebaut „Scheduler“ („Planificador“ bei spanischen Locales)
TouchNatives Scrollen und TippenGedrückt halten zum Verschieben, Griffe zum Ändern der Dauer, Pinch-Geste zum Zoomen

Die Tastatur in Pro aktivieren

Pro lässt die Tastatur ausgeschaltet, bis Sie keyboardEnabled: true setzen. Der Standard keyboardMode: 'SuperScheduler' verarbeitet Pfeiltasten, Eingabe und Umschalt + Links/Rechts. keyboardMode: 'Full' ergänzt den Rest des Modells: Pos1/Ende, Bild auf/Bild ab, Leertaste, Verschieben und Dauer ändern von Ereignissen, die Menütaste und die Ansage von Hilfetexten. Der Modus Full ohne keyboardEnabled bewirkt nichts und warnt in der Entwicklung.

keyboardTarget bestimmt, wo Tasten gehört werden. Der Standard, 'document', reagiert auf Tasten, die irgendwo auf der Seite außerhalb von Textfeldern gedrückt werden, und nimmt damit dem Scrollen der Seite die Pfeiltasten weg. Verwenden Sie 'component', damit Tasten nur wirken, solange das Raster den Fokus hat; das ist auch die richtige Wahl bei mehreren Planern auf einer Seite.

src/AccessiblePlanner.tsxtsx
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>
  )
}

Sie sollten mit Tab in das Raster gelangen, sich mit den Pfeiltasten bewegen, ein Ereignis mit der Eingabetaste öffnen und mit Alt + Pfeil nach unten das Verschieben eines Ereignisses starten können. Verschieben Sie es auf Zimmer 104 und drücken Sie die Eingabetaste, wird „Not allowed here“ angesagt, und das Ereignis bleibt, wo es war.

keyboardOptions stimmt den Modus Full ab:

OptionStandardWirkung
pageRowssichtbare Zeilen minus einsZeilen, um die Bild auf und Bild ab springen
contextMenuKeytrue im Modus FullMenütaste und Umschalt + F10 öffnen das Menü des fokussierten Elements
bubbleOnFocusfalseZeigt die Blase des Ereignisses, solange es den Tastaturfokus hat
selectAlltrue im Modus FullStrg/Cmd + A wählt jedes sichtbare Ereignis aus (erfordert allowMultiSelect)
zoomKeysfalseStrg/Cmd mit = oder +, - und 0 zoomen hinein, heraus und zurück

zoomKeys ist standardmäßig aus, damit die browsereigenen Tastenkürzel für den Seitenzoom weiter funktionieren.

Tasten

TastenModusWas passiert
PfeiltastenbeideBewegen den Fokus. Links und Rechts halten an jedem Ereignis und jeder leeren Zelle der Zeile; Hoch und Runter wechseln die Zeile.
EingabebeideAuf einem Ereignis: der Klickablauf (onEventClick, dann eventClickHandling). Auf einer Zelle: wählt sie als Zeitraum aus.
Umschalt + Links / Umschalt + RechtsbeideErweitert einen Zeitraum ab der fokussierten Zelle; Loslassen der Umschalttaste wählt ihn aus.
LeertasteFullAuf einer Zelle: fügt sie der Auswahl hinzu oder entfernt sie daraus. Auf einem Ereignis: wie Eingabe.
Pos1 / EndeFullErste oder letzte Zelle der Zeile; mit Strg/Cmd die erste oder letzte Zeile.
Bild auf / Bild abFullBewegt den Fokus um pageRows.
Alt + PfeiltastenFullStartet das Verschieben des fokussierten Ereignisses; die Pfeiltasten bewegen es, Eingabe oder Leertaste legt es ab.
Alt + Umschalt + Links / RechtsFullStartet das Ändern des Endes des fokussierten Ereignisses; Links und Rechts ändern es, Eingabe bestätigt. Auf einem Spaltentitel: verschiebt die Spalte.
EscbeideBricht ein Verschieben, Dauer ändern oder Auswählen eines Zeitraums per Tastatur ab. Während eines Verschiebens oder Dauer-Änderns per Tastatur bricht auch Tab ab und verlässt das Raster.
Menütaste, Umschalt + F10FullÖffnet das Menü des fokussierten Ereignisses, Zeilenkopfs oder der fokussierten Zelle.
Strg/Cmd + AFullWählt jedes sichtbare Ereignis aus.

Fokusmodell und Screenreader

Das Pro-Raster hat role="grid" mit aria-rowcount und aria-colcount; Zeilenköpfe sind rowheader, Zellen des Zeitkopfs columnheader, und Zellen und Ereignisse gridcell. Zeilen und Zellen außerhalb des Viewports sind nicht im DOM, daher wandert der Fokus nicht von Element zu Element. Stattdessen gilt:

  • Die Wurzel des Rasters ist der einzige Tab-Stopp (tabindex="0"), solange die Tastatur aktiviert ist.
  • Die fokussierte Zelle oder das fokussierte Ereignis ist ein Fokusknoten, auf den die Wurzel mit aria-activedescendant verweist; er übersteht Scrollen und Virtualisierung.
  • Die Beschriftung des Fokusknotens lautet etwa „Room 101, Oct 1“ für eine Zelle und „Ana, Room 101, Oct 2 – 4“ für ein Ereignis.

Der Name eines Ereignisses stammt aus seinem ariaLabel, dann aus seinem text, dann aus seinem html als reiner Text, dann aus seiner ID. Der Name einer Zeile ist der name ihrer Ressource. Wenn html etwas anderes zeigt als text, setzen Sie ariaLabel in onBeforeEventRender:

src/labelVisit.tstsx
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]}`
}

Da Fokusbeschriftungen und Ansagen die Zeile und die Daten anhängen, sollte ariaLabel nur enthalten, was das Ereignis identifiziert. SuperScheduler.ColorUtil.contrasting(color) liefert dunklen Text für helle Füllungen und hellen Text für dunkle.

Der eigene barrierefreie Name des Rasters ist „Scheduler“ („Planificador“, wenn die Locale mit es beginnt), und 0.1.0 hat keine Option, ihn zu ändern. Setzen Sie den Planer in einen Bereich, der durch eine sichtbare Überschrift benannt ist, wie es das Setup-Snippet tut.

Aus Code bietet control.keyboard die Methoden focusEvent(e or id), focusCell(date, resource), getFocus(), move(direction), clearFocus() und resetFocus(). onKeyboardFocusChange (abbrechbar) und onKeyboardFocusChanged melden Fokuswechsel mit previous und focus ({ e } oder { cell }); damit halten Sie ein Detailpanel mit der Tastatur synchron.

Ansagen

Eine höfliche Live-Region (polite) im Raster sagt an:

  • im Modus Full einen kurzen Hilfetext, wenn das Raster zum ersten Mal den Fokus erhält;
  • Auswahlen („Selected: Room 101, Oct 4“), aufgehobene Auswahlen und „N events selected“;
  • den Beginn eines Verschiebens oder Dauer-Änderns per Tastatur, mit Anweisungen;
  • übernommene Verschiebungen und Dauer-Änderungen („Event moved to Room 101, Oct 3 – 5“), egal ob sie von der Tastatur oder vom Zeiger kamen;
  • „Cancelled“, „Not allowed here“ und Spaltenverschiebungen.

Die Texte folgen dem ersten Segment der locale des Planers: Englisch, Spanisch, Katalanisch, Baskisch, Galicisch, Deutsch, Französisch, Italienisch und Portugiesisch. Andere Sprachen fallen auf Englisch zurück. Sprachpakete außer Englisch und Spanisch werden bei Bedarf geladen.

Im Modus Full öffnen die Menütaste und Umschalt + F10 das Menü des fokussierten Elements, wenn es ein SuperScheduler.Menu ist: das contextMenu des Ereignisses (oder das contextMenu des Controls), contextMenuResource auf einem Zeilenkopf und contextMenuSelection auf einer Zelle. Zeichnet Ihre Anwendung ihr eigenes Menü aus onEventRightClick, verarbeiten Sie die Taste selbst in onKeyDown:

src/menuHandlers.tstsx
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 })
    },
  }
}

Übergeben Sie menuHandlers(open) memoisiert per Spread an die Props des Planers. Ihr Menü ist dann selbst für seinen Fokus verantwortlich: Verschieben Sie den Fokus beim Öffnen hinein und geben Sie ihn beim Schließen an das Raster zurück.

Barrierefreie Alternativen zum Ziehen

Das WCAG-2.2-Erfolgskriterium 2.5.7 (↗) verlangt eine Möglichkeit, mit einfachen Zeigeraktionen zu erledigen, was Ziehen erledigt. Der vollständige Tastaturmodus deckt Tastaturnutzer ab, aber nicht jemanden, der einen einzelnen Zeiger, einen Schalter oder Sprachsteuerung verwendet. Geben Sie jedem Ereignis einen Weg ohne Ziehen, zum Beispiel ein Detailpanel oder einen Kontextmenüeintrag mit Feldern für Beginn, Ende und Ressource, der Ihren State aktualisiert (oder control.events.update mit einem neuen Objekt aufruft). Führen Sie vor dem Speichern dieselbe Validierung aus, die Sie in onEventMoving verwenden, damit beide Wege dieselben Regeln durchsetzen.

Touch

Auf Touchscreens scrollt ein Finger die Zeitleiste. Der Rest des Touch-Modells:

  • Ein Ereignis verschieben. Halten Sie das Ereignis tapAndHoldTimeout lang (300 ms) still und ziehen Sie dann. Ein Finger, der sich vorher mehr als etwa 8 px bewegt, scrollt stattdessen. eventTapAndHoldHandling bestimmt, was das Gedrückthalten bewirkt: 'Move' (Standard), 'ContextMenu' (öffnet das SuperScheduler.Menu des Ereignisses) oder 'Disabled'. Gedrückthalten auf einem Zeilenkopf öffnet contextMenuResource.
  • Dauer ändern. Ein Tippen auf ein Ereignis zeigt seine Griffe mit 44 px großen Touch-Zielen (--super-scheduler-handle-target); ziehen Sie einen Griff, um die Dauer zu ändern. Der Rand eines Ereignisses unter einem Finger verschiebt es, statt seine Dauer zu ändern.
  • Zeit auswählen. Ein Tippen auf eine leere Zelle wählt sie aus (origin: 'click'); Gedrückthalten und Ziehen wählt einen Zeitraum aus.
  • Zoom. Zwei Finger zoomen per Pinch-Geste (zoomGesture.pinch, standardmäßig aktiv). Ein zweiter Finger bricht jedes laufende Ziehen ab.
  • Hover. Touch kennt kein Hover. Hover-Karten aus eventHover lassen sich per Tippen anheften (pin: 'click'); Areas mit visibility: 'TouchVisible' bleiben auf Touch-Geräten sichtbar, während Areas mit 'Hover' nicht erscheinen.

Lite ist schreibgeschützt: Es scrollt nativ und meldet Tippen über onEventClick und onTimeRangeClick.

Reduzierte Bewegung, Kontrast und erzwungene Farben

Pro liest die Präferenzen des Nutzers über CSS und Media Queries, ohne dass Sie eine Option setzen müssen:

  • prefers-reduced-motion: reduce setzt --super-scheduler-duration auf 0s, lässt control.zoom.animateTo() ohne Animation springen und entfernt die Übergänge der Hover-Karten;
  • prefers-contrast: more verstärkt Rahmen, Rasterlinien, Zeilenlinien und die Umrandung der Auswahl;
  • forced-colors: active stellt das Theme auf Systemfarben um (Highlight, CanvasText, GrayText) und verzichtet auf dekorative Schattierungen wie Wochenenden und den heutigen Tag.

Auch Lite passt seine Rahmen und die Fokusumrandung an erzwungene Farben an. Wenn Sie Farben durch eigene Tokens oder eigenes CSS ersetzen, testen Sie diese Modi erneut: Ihre Überschreibungen können sie zunichtemachen.

Tastaturunterstützung in Lite

Lite braucht keine Konfiguration. Das Raster ist fokussierbar, aria-readonly und über die Option ariaLabel benannt. Pfeiltasten bewegen die aktive Zelle (bekanntgegeben über aria-activedescendant), Eingabe oder Leertaste ruft onTimeRangeClick für sie auf, und jedes Ereignis ist ein nativer Button.

src/OccupancyBoard.tsxtsx
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'))
      }
    />
  )
}

Was Ihre Anwendung noch sicherstellen muss

Die Bibliothek kümmert sich um das Raster. Diese Teile gehören Ihrer Anwendung:

  • Kontrast. Farben von Ereignissen, die Sie mit backColor, fontColor, CSS oder eigenen Inhalten setzen, müssen die Kontrastanforderungen im hellen und dunklen Modus erfüllen.
  • Namen in eigenen Inhalten. HTML aus onBeforeEventRender, React-Slots und Markup im Zeilenkopf liegen bei Ihnen: Halten Sie text aussagekräftig oder setzen Sie ariaLabel, geben Sie Icons Textalternativen und vermeiden Sie interaktive Steuerelemente im Inhalt von Ereignissen.
  • Menüs, Dialoge und Panels. Fokusverwaltung, Beschriftungen und Esc-Verhalten für alles, was Sie aus dem Raster heraus öffnen.
  • Ein Weg ohne Ziehen für jede Ziehaktion, wie oben beschrieben.
  • Seitenstruktur. Eine Überschrift oder Beschriftung für den Bereich um das Raster und ein sinnvoller Platz in der Tab-Reihenfolge.
  • Tests. Prüfen Sie Ihre Konfiguration mit einem Screenreader und einem automatisierten Prüfwerkzeug; Überschreibungen und eigene Inhalte können ändern, was Nutzer hören.

Termine in der PhysiotherapiepraxisEin Patient kann um 10 Uhr nicht. Finden Sie den nächsten Slot, der Pausen und Reinigung respektiert.