InteracciónSe aplica aLite y Pro
Teclado, accesibilidad y táctil
En Pro, activa keyboardEnabled (desactivado por defecto), añade keyboardMode: 'Full' para el conjunto completo de teclas y keyboardTarget: 'component' para que las teclas solo actúen mientras la cuadrícula tiene el foco. La cuadrícula es una única parada de tabulación con role="grid": el foco se mueve mediante aria-activedescendant y los cambios se anuncian en nueve idiomas. Lite trae de serie la navegación con las flechas. En pantallas táctiles, mantén pulsado un evento para moverlo y arrastra sus tiradores para redimensionarlo.
Un planificador de recursos es una cuadrícula bidimensional grande, y por eso dar soporte al teclado y a los lectores de pantalla es más difícil que en una lista o un formulario. SuperScheduler da a la cuadrícula una única parada de tabulación, un foco itinerante que sobrevive a la virtualización, anuncios hablados para el foco y los cambios, y equivalentes de teclado para mover y redimensionar eventos. Esta guía explica qué hace cada edición, cómo activarlo y qué tiene que aportar todavía tu aplicación.
Lite y Pro de un vistazo
Lite (super-scheduler-lite) | Pro (super-scheduler) | |
|---|---|---|
| Teclado | Siempre activado: las flechas mueven la celda activa, Intro o Espacio la activan | Desactivado por defecto; keyboardEnabled, más keyboardMode: 'Full' para el modelo completo |
| Eventos | Botones nativos: Tab llega a ellos, Intro o Espacio los pulsan | Se llega a ellos con las flechas dentro de la cuadrícula; Intro ejecuta el flujo de clic |
| Edición con teclado | No (edición de solo lectura) | Mover con Alt+flechas, redimensionar con Alt+Mayús+flecha izquierda/derecha (modo Full) |
| Anuncios | No | Foco, selección y cambios confirmados, en nueve idiomas |
| Nombre de la cuadrícula | Opción ariaLabel (por defecto, «Resource schedule») | «Scheduler» integrado («Planificador» para locales en español) |
| Táctil | Scroll y toques nativos | Mantener pulsado para mover, tiradores para redimensionar, pellizcar para hacer zoom |
Activar el teclado en Pro
Pro mantiene el teclado desactivado hasta que pones keyboardEnabled: true. El valor por defecto keyboardMode: 'SuperScheduler' gestiona las flechas, Intro y Mayús+flecha izquierda/derecha. keyboardMode: 'Full' añade el resto del modelo: Inicio/Fin, RePág/AvPág, Espacio, mover y redimensionar eventos, la tecla de menú contextual y el anuncio de textos de ayuda. El modo Full sin keyboardEnabled no hace nada y avisa en desarrollo.
keyboardTarget decide dónde se escuchan las teclas. El valor por defecto, 'document', reacciona a las teclas pulsadas en cualquier parte de la página fuera de los campos de texto, lo que quita las flechas al scroll de la página. Usa 'component' para que las teclas solo actúen mientras la cuadrícula tiene el foco; también es la opción adecuada cuando hay varios schedulers en una misma página.
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>
)
}Deberías poder entrar en la cuadrícula con Tab, moverte con las flechas, pulsar Intro sobre un evento para abrirlo y pulsar Alt+flecha abajo sobre un evento para empezar a moverlo. Si lo llevas a la habitación 104 y pulsas Intro, se anuncia «Not allowed here» y el evento se queda donde estaba.
keyboardOptions ajusta el modo Full:
| Opción | Por defecto | Efecto |
|---|---|---|
pageRows | filas visibles menos una | Filas que avanzan RePág y AvPág |
contextMenuKey | true en modo Full | La tecla de menú y Mayús+F10 abren el menú del elemento con foco |
bubbleOnFocus | false | Muestra la burbuja del evento mientras tiene el foco del teclado |
selectAll | true en modo Full | Ctrl/Cmd+A selecciona todos los eventos visibles (necesita allowMultiSelect) |
zoomKeys | false | Ctrl/Cmd con = o +, - y 0 acercan, alejan y restablecen el zoom |
zoomKeys está desactivado por defecto para que los atajos de zoom de página del propio navegador sigan funcionando.
Teclas
| Teclas | Modo | Qué ocurre |
|---|---|---|
| Flechas | ambos | Mueven el foco. Izquierda y derecha se detienen en cada evento y en cada celda vacía de la fila; arriba y abajo cambian de fila. |
| Intro | ambos | Sobre un evento: el flujo de clic (onEventClick y después eventClickHandling). Sobre una celda: la selecciona como rango de tiempo. |
| Mayús+flecha izquierda / Mayús+flecha derecha | ambos | Amplía un rango de tiempo desde la celda con foco; al soltar Mayús, se selecciona. |
| Espacio | Full | Sobre una celda: la añade a la selección o la quita. Sobre un evento: igual que Intro. |
| Inicio / Fin | Full | Primera o última celda de la fila; con Ctrl/Cmd, primera o última fila. |
| RePág / AvPág | Full | Mueve el foco pageRows filas. |
| Alt+flechas | Full | Empieza a mover el evento con foco; las flechas lo mueven, Intro o Espacio lo sueltan. |
| Alt+Mayús+flecha izquierda / derecha | Full | Empieza a redimensionar el final del evento con foco; izquierda y derecha lo cambian, Intro confirma. Sobre el título de una columna, mueve la columna. |
| Esc | ambos | Cancela un movimiento, un redimensionado o un rango hecho con el teclado. Durante un movimiento o un redimensionado con el teclado, Tab también lo cancela y sale de la cuadrícula. |
| Tecla de menú, Mayús+F10 | Full | Abre el menú del evento, la cabecera de fila o la celda con foco. |
| Ctrl/Cmd+A | Full | Selecciona todos los eventos visibles. |
Modelo de foco y lectores de pantalla
La cuadrícula de Pro tiene role="grid" con aria-rowcount y aria-colcount; las cabeceras de fila son rowheader, las celdas de la cabecera de tiempo columnheader, y las celdas y los eventos gridcell. Las filas y celdas fuera del área visible no están en el DOM, así que el foco no pasa de un elemento a otro. En su lugar:
- la raíz de la cuadrícula es la única parada de tabulación (
tabindex="0") mientras el teclado está activado; - la celda o el evento con foco es un nodo de foco al que la raíz apunta con
aria-activedescendant, y sobrevive al scroll y a la virtualización; - la etiqueta del nodo de foco se lee como «Room 101, Oct 1» para una celda y «Ana, Room 101, Oct 2 – 4» para un evento.
El nombre de un evento sale de su ariaLabel; si no lo hay, de su text; después, de su html como texto plano y, por último, de su id. El nombre de una fila es el name de su recurso. Cuando html muestra algo distinto de text, fija ariaLabel en 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]}`
}Como las etiquetas de foco y los anuncios añaden la fila y las fechas, ariaLabel solo debe contener lo que identifica al evento. SuperScheduler.ColorUtil.contrasting(color) devuelve texto oscuro para rellenos claros y texto claro para los oscuros.
El nombre accesible de la propia cuadrícula es «Scheduler» («Planificador» cuando el locale empieza por es), y la 0.1.0 no tiene ninguna opción para cambiarlo. Coloca el scheduler dentro de una región etiquetada con un encabezado visible, como hace el snippet de configuración.
Desde código, control.keyboard ofrece focusEvent(e or id), focusCell(date, resource), getFocus(), move(direction), clearFocus() y resetFocus(). onKeyboardFocusChange (cancelable) y onKeyboardFocusChanged comunican los cambios de foco con previous y focus ({ e } o { cell }); úsalos para sincronizar un panel de detalle con el teclado.
Anuncios
Una región live discreta (polite) dentro de la cuadrícula anuncia:
- en modo Full, un breve texto de ayuda la primera vez que la cuadrícula recibe el foco;
- las selecciones («Selected: Room 101, Oct 4»), las deselecciones y «N events selected»;
- el inicio de un movimiento o un redimensionado con teclado, con instrucciones;
- los movimientos y redimensionados confirmados («Event moved to Room 101, Oct 3 – 5»), tanto si vienen del teclado como del puntero;
- «Cancelled», «Not allowed here» y los movimientos de columnas.
Los textos siguen el primer segmento del locale del scheduler: inglés, español, catalán, euskera, gallego, alemán, francés, italiano y portugués. Los demás idiomas usan el inglés. Los paquetes de idioma distintos del inglés y el español se cargan bajo demanda.
La tecla de menú contextual
En modo Full, la tecla de menú y Mayús+F10 abren el menú del elemento con foco cuando es un SuperScheduler.Menu: el contextMenu del evento (o el contextMenu del control), contextMenuResource en una cabecera de fila y contextMenuSelection en una celda. Si tu aplicación dibuja su propio menú desde onEventRightClick, gestiona tú la tecla en 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 })
},
}
}Incorpora menuHandlers(open) a las props del scheduler con un spread, y memorízalo. A partir de ahí, tu menú es responsable de su propio foco: mueve el foco a su interior cuando se abra y devuélvelo a la cuadrícula cuando se cierre.
Alternativas accesibles al arrastre
El criterio de conformidad 2.5.7 de WCAG 2.2 (↗) pide una forma de hacer con acciones de un solo puntero lo mismo que se hace arrastrando. El modo de teclado Full cubre a quienes usan el teclado, pero no a quien usa un solo puntero, un conmutador o el control por voz. Da a cada evento un camino que no requiera arrastrar, por ejemplo un panel de detalle o una entrada de menú contextual con campos de inicio, fin y recurso que actualice tu estado (o llame a control.events.update con un objeto nuevo). Antes de guardar, ejecuta la misma validación que usas en onEventMoving, para que ambos caminos apliquen las mismas reglas.
Táctil
En pantallas táctiles, con un dedo se hace scroll por la línea de tiempo. El resto del modelo táctil:
- Mover un evento. Mantén el evento pulsado y quieto durante
tapAndHoldTimeout(300 ms) y después arrastra. Si el dedo se mueve más de unos 8 px antes de ese tiempo, se hace scroll.eventTapAndHoldHandlingdecide qué hace una pulsación larga:'Move'(por defecto),'ContextMenu'(abre elSuperScheduler.Menudel evento) o'Disabled'. Mantener pulsada una cabecera de fila abrecontextMenuResource. - Redimensionar. Un toque en un evento muestra sus tiradores, con áreas táctiles de 44 px (
--super-scheduler-handle-target); arrastra un tirador para redimensionar. Si apoyas el dedo en el borde de un evento, lo mueves en lugar de redimensionarlo. - Seleccionar tiempo. Un toque en una celda vacía la selecciona (
origin: 'click'); mantener pulsado y arrastrar selecciona un rango. - Zoom. Pellizca con dos dedos para hacer zoom (
zoomGesture.pinch, activado por defecto). Un segundo dedo cancela cualquier arrastre en curso. - Hover. En táctil no existe el hover. Las tarjetas emergentes de
eventHoverse pueden fijar con un toque (pin: 'click'); las áreas convisibility: 'TouchVisible'siguen visibles en dispositivos táctiles, mientras que las áreas'Hover'no aparecen.
Lite es de solo lectura: hace scroll de forma nativa y comunica los toques mediante onEventClick y onTimeRangeClick.
Movimiento reducido, contraste y colores forzados
Pro lee las preferencias del usuario mediante CSS y media queries, sin ninguna opción que configurar:
prefers-reduced-motion: reducepone--super-scheduler-durationa0s, hace instantáneocontrol.zoom.animateTo()y elimina las transiciones de las tarjetas emergentes;prefers-contrast: morerefuerza los bordes, las líneas de la cuadrícula, las líneas de fila y el contorno de selección;forced-colors: activecambia el tema a los colores del sistema (Highlight,CanvasText,GrayText) y quita los sombreados decorativos, como los de los fines de semana y el día de hoy.
Lite también adapta sus bordes y su contorno de foco a los colores forzados. Si sustituyes colores con tus propios tokens o tu CSS, vuelve a probar estos modos: tus sobrescrituras pueden anularlos.
Soporte de teclado en Lite
Lite no necesita configuración. La cuadrícula puede recibir el foco, es aria-readonly y su nombre viene de la opción ariaLabel. Las flechas mueven la celda activa (anunciada mediante aria-activedescendant), Intro o Espacio llaman a onTimeRangeClick para ella y cada evento es un botón nativo.
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'))
}
/>
)
}Lo que tu aplicación todavía debe garantizar
La librería se ocupa de la cuadrícula. Estas partes corresponden a tu aplicación:
- Contraste. Los colores de evento que fijas con
backColor,fontColor, CSS o contenido personalizado deben cumplir los requisitos de contraste en modo claro y oscuro. - Nombres en el contenido personalizado. El HTML de
onBeforeEventRender, los slots de React y el marcado de las cabeceras de fila son tuyos: mantén untextcon sentido o fijaariaLabel, da alternativas de texto a los iconos y evita controles interactivos dentro del contenido de los eventos. - Menús, diálogos y paneles. La gestión del foco, las etiquetas y el manejo de Esc de todo lo que abras desde la cuadrícula.
- Un camino sin arrastre para cada acción de arrastre, como se ha descrito arriba.
- Estructura de la página. Un encabezado o una etiqueta para la región que rodea la cuadrícula, y un lugar razonable para ella en el orden de tabulación.
- Pruebas. Comprueba tu configuración con un lector de pantalla y un verificador automático; las sobrescrituras y el contenido personalizado pueden cambiar lo que oyen los usuarios.
Relacionado
- Árboles de recursos, columnas y selección explica qué seleccionan Espacio, Intro y Ctrl/Cmd+A.
- Arrastrar, redimensionar y reglas de negocio explica las reglas por las que pasan los movimientos con teclado.
- Temas, tokens, Tailwind y modo oscuro trata los colores de foco y de selección.