# Tastatur, Barrierefreiheit und Touch

> Volle Tastaturbedienung aktivieren, Fokusmodell und Ansagen verstehen, Alternativen zum Ziehen bieten und die Zeitleiste an Touch und assistive Technik anpassen.

Source: https://superscheduler.org/de/docs/keyboard-accessibility-touch/
Reviewed: 2026-10-07

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.

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`) |
|---|---|---|
| Tastatur | Immer aktiv: Pfeiltasten bewegen die aktive Zelle, Eingabe oder Leertaste aktiviert sie | Standardmäßig aus; `keyboardEnabled`, dazu `keyboardMode: 'Full'` für das vollständige Modell |
| Ereignisse | Native Buttons: Tab erreicht sie, Eingabe oder Leertaste klickt sie | Mit den Pfeiltasten innerhalb des Rasters erreichbar; Eingabe startet den Klickablauf |
| Bearbeiten per Tastatur | Nein (schreibgeschützte Edition) | Verschieben mit Alt + Pfeiltasten, Dauer ändern mit Alt + Umschalt + Links/Rechts (Modus Full) |
| Ansagen | Nein | Fokus, Auswahl und übernommene Änderungen, in neun Sprachen |
| Name des Rasters | Option `ariaLabel` (Standard „Resource schedule“) | Eingebaut „Scheduler“ („Planificador“ bei spanischen Locales) |
| Touch | Natives Scrollen und Tippen | Gedrü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.

```tsx
// src/AccessiblePlanner.tsx
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:

| Option | Standard | Wirkung |
|---|---|---|
| `pageRows` | sichtbare Zeilen minus eins | Zeilen, um die Bild auf und Bild ab springen |
| `contextMenuKey` | `true` im Modus Full | Menütaste und Umschalt + F10 öffnen das Menü des fokussierten Elements |
| `bubbleOnFocus` | `false` | Zeigt die Blase des Ereignisses, solange es den Tastaturfokus hat |
| `selectAll` | `true` im Modus Full | Strg/Cmd + A wählt jedes sichtbare Ereignis aus (erfordert `allowMultiSelect`) |
| `zoomKeys` | `false` | Strg/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
| Tasten | Modus | Was passiert |
|---|---|---|
| Pfeiltasten | beide | Bewegen den Fokus. Links und Rechts halten an jedem Ereignis und jeder leeren Zelle der Zeile; Hoch und Runter wechseln die Zeile. |
| Eingabe | beide | Auf einem Ereignis: der Klickablauf (`onEventClick`, dann `eventClickHandling`). Auf einer Zelle: wählt sie als Zeitraum aus. |
| Umschalt + Links / Umschalt + Rechts | beide | Erweitert einen Zeitraum ab der fokussierten Zelle; Loslassen der Umschalttaste wählt ihn aus. |
| Leertaste | Full | Auf einer Zelle: fügt sie der Auswahl hinzu oder entfernt sie daraus. Auf einem Ereignis: wie Eingabe. |
| Pos1 / Ende | Full | Erste oder letzte Zelle der Zeile; mit Strg/Cmd die erste oder letzte Zeile. |
| Bild auf / Bild ab | Full | Bewegt den Fokus um `pageRows`. |
| Alt + Pfeiltasten | Full | Startet das Verschieben des fokussierten Ereignisses; die Pfeiltasten bewegen es, Eingabe oder Leertaste legt es ab. |
| Alt + Umschalt + Links / Rechts | Full | Startet das Ändern des Endes des fokussierten Ereignisses; Links und Rechts ändern es, Eingabe bestätigt. Auf einem Spaltentitel: verschiebt die Spalte. |
| Esc | beide | Bricht 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 + F10 | Full | Öffnet das Menü des fokussierten Ereignisses, Zeilenkopfs oder der fokussierten Zelle. |
| Strg/Cmd + A | Full | Wählt jedes sichtbare Ereignis aus. |

> **Behavior:**
> Verschiebungen und Dauer-Änderungen per Tastatur durchlaufen dieselben Regeln wie mit dem Zeiger: `onEventMoving` und `onEventResizing` können eine Position ablehnen, `onEventMove` kann abbrechen oder asynchron bestätigen, und `allowEventOverlap`, gesperrte Ereignisse und gesperrte Zellen gelten. Tasten, die in Eingabefeldern, Textbereichen und bearbeitbaren Elementen getippt werden, behalten ihr normales Verhalten, und eine Taste, die Ihr `onKeyDown`-Handler mit `args.preventDefault()` abbricht, verarbeitet das Raster nicht.

## 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`:

```tsx
// src/labelVisit.ts
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.

> **Limitation:**
> In 0.1.0 sind die Texte der Ansagen fest eingebaut: `keyboardOptions` erlaubt nicht, sie zu ersetzen. Datumsangaben in Ansagen folgen der Locale des Planers.

### Die Menütaste
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`:

```tsx
// src/menuHandlers.ts
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](https://www.w3.org/WAI/WCAG22/Understanding/dragging-movements.html) 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](https://superscheduler.org/de/docs/react-render-slots/) 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.

```tsx
// src/OccupancyBoard.tsx
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.

## Siehe auch
→ https://superscheduler.org/de/examples/clinic-appointments/
- [Ressourcenbäume, Spalten und Auswahl](https://superscheduler.org/de/docs/trees-columns-selection/) behandelt, was Leertaste, Eingabe und Strg/Cmd + A auswählen.
- [Ziehen, Dauer ändern und Geschäftsregeln](https://superscheduler.org/de/docs/drag-resize-rules/) erklärt die Regeln, die Verschiebungen per Tastatur durchlaufen.
- [Themes, Tokens, Tailwind und Dark Mode](https://superscheduler.org/de/docs/theming/) behandelt Fokus- und Auswahlfarben.
