Accessibility
Built with WCAG 2.2 AA compliance and full keyboard support
ARIA Support
Timepicker-UI includes comprehensive ARIA attributes for screen reader compatibility:
role="dialog"witharia-modal="true"for modal timepickeraria-labelledbyconnecting dialog to titlearia-labelon all interactive elements (inputs, buttons)role="spinbutton"witharia-valuenowfor hour/minute inputs (dynamically updated)role="listbox"for clock face wrappersrole="option"witharia-selectedfor active time values on clockaria-disabledfor disabled times (respects disabledTime configuration)aria-pressedfor AM/PM toggle buttons (dynamically updated)role="status"+aria-live="polite"+aria-atomic="true"for time change announcementsaria-hidden="true"for decorative elements (dots, clock hand)
Keyboard Navigation
Complete keyboard support for users who cannot or prefer not to use a mouse:
Keyboard Shortcuts
Note: Clock face numbers are Tab-focusable. Use arrow keys while focused to adjust time by 1 minute/hour. The clock hand and input values update in real-time, but focus stays on the current element for easier fine-tuning.
Navigation Flow
Enabling Keyboard Navigation
1const picker = new TimepickerUI(input, {2 // Keyboard navigation is enabled by default3 // All interactive elements are focusable4});56picker.create();78// Arrow keys work on both inputs and clock face9// - On hour/minute inputs: Up/Down adjusts by ±110// - On clock face numbers: Up/Down/Left/Right adjusts by ±111// - Disabled times are automatically skipped1213// A/P shortcuts work from anywhere except inputs14document.addEventListener('keydown', (e) => {15 if (e.key === 'a' || e.key === 'A') {16 // Switches to AM (when not typing in input)17 }18 if (e.key === 'p' || e.key === 'P') {19 // Switches to PM (when not typing in input)20 }21});
Screen Reader Support
Optimized for screen readers like NVDA, JAWS, and VoiceOver:
ARIA Labels
1const picker = new TimepickerUI(input, {2 labels: {3 // Visible button text (also announced by screen readers)4 ok: 'Confirm',5 cancel: 'Cancel',67 // Accessible names for the picker controls8 clockLabel: 'Clock',9 hourLabel: 'Hour',10 minuteLabel: 'Minute',11 periodLabel: 'Period',12 timeLabel: 'Select appointment time'13 }14});1516// Add a custom aria-label / hint directly on the input element17<input18 type="time"19 id="timepicker"20 aria-label="Select appointment time"21 aria-describedby="time-help"22/>23<span id="time-help" class="sr-only">24 Use arrow keys to select time. Press Enter to confirm.25</span>
Live Regions
1<!-- Live region for screen reader announcements -->2<div3 class="timepicker-announcer"4 role="status"5 aria-live="polite"6 aria-atomic="true"7 style="position: absolute; width: 1px; height: 1px;8 overflow: hidden; clip: rect(0,0,0,0);"9>10 <!-- Dynamically updated by the timepicker -->11 <!-- Announces: "Hour changed to 14", "Minute changed to 30", etc. -->12</div>1314<script>15// Timepicker automatically announces changes via live region16// No manual setup required - works out of the box1718// Example announcements:19// - "Hour changed to 14"20// - "Minute changed to 30"21// - "AM selected"22// - "PM selected"2324// Live region updates happen on:25// - Clock hand drag26// - Clock number click27// - Arrow key navigation28// - AM/PM toggle29</script>
Descriptive Labels
1<label for="meeting-time" class="form-label">2 Meeting Time3 <span class="text-muted">(Required)</span>4</label>5<input6 type="time"7 id="meeting-time"8 required9 aria-required="true"10 aria-describedby="meeting-time-hint"11/>12<div id="meeting-time-hint" class="form-hint">13 Select a time between 9:00 AM and 5:00 PM14</div>
Focus Management
Proper focus handling for keyboard and screen reader users:
Focus Trapping
1// Focus is automatically trapped within the modal2// Users cannot tab outside the timepicker when open34const picker = new TimepickerUI(input, {5 behavior: {6 // Modal automatically manages focus7 focusTrap: true // Default behavior8 }9});1011picker.create();1213// When modal opens:14// 1. Focus moves to the wrapper element15// 2. Tab cycles through all focusable elements in order:16// - Hour input17// - Minute input18// - Clock face numbers (all visible hour/minute values)19// - AM/PM buttons (in 12h mode)20// - Cancel button21// - OK button22// 3. Shift+Tab cycles backwards23// 4. When last element is focused and Tab is pressed, focus returns to first element24// 5. Cannot tab to elements behind modal2526// When modal closes:27// - Focus returns to the input field that triggered the modal2829// Note: Clock face numbers are included in Tab order, allowing keyboard-only30// users to reach them. Once focused, arrow keys can adjust time precisely.
Visible Focus Indicators
1/* Default focus styles for all interactive elements */2.tp-ui-hour:focus-visible,3.tp-ui-minutes:focus-visible,4.tp-ui-am:focus-visible,5.tp-ui-pm:focus-visible,6.tp-ui-cancel-btn:focus-visible,7.tp-ui-ok-btn:focus-visible,8.tp-ui-keyboard-icon-wrapper:focus-visible {9 outline: 3px solid var(--tp-primary);10 outline-offset: 2px;11 box-shadow: 0 0 0 4px rgba(98, 0, 238, 0.2);12}1314/* High contrast mode support */15@media (prefers-contrast: high) {16 .tp-ui-hour:focus-visible,17 .tp-ui-minutes:focus-visible,18 .tp-ui-am:focus-visible,19 .tp-ui-pm:focus-visible,20 .tp-ui-cancel-btn:focus-visible,21 .tp-ui-ok-btn:focus-visible {22 outline-width: 4px;23 }24}2526/* Focus styles use :focus-visible to only show for keyboard navigation */27/* Mouse clicks won't show focus ring, but Tab navigation will */
Disabled Time Handling
1// Disabled times are properly marked and skipped23const picker = new TimepickerUI(input, {4 clock: {5 disabledTime: {6 // Disable one or more time ranges (string or array of strings)7 interval: ['9:00 AM - 12:00 PM', '6:00 PM - 11:59 PM'],89 // Optionally disable specific hours / minutes outright10 hours: [0, 1, 2],11 minutes: [15, 45]12 }13 }14});1516// Disabled times have:17// - tabIndex = -1 (not focusable via Tab)18// - aria-disabled="true" (announced by screen readers)19// - Visual styling (grayed out)20// - Skipped during arrow key navigation2122// Arrow key navigation automatically skips disabled values23// If all minutes in an hour are disabled, that hour is also skipped
Color Contrast
Ensure WCAG AA compliance with proper color contrast:
Contrast Requirements
High Contrast Theme
1/* High contrast accessible override (CSS variables use the --tp- prefix) */2.tp-ui-modal[data-theme="dark"] {3 --tp-bg: #000000;4 --tp-text: #ffffff;5 --tp-primary: #ffff00;6 --tp-border: #ffffff;78 /* Ensure all text meets 7:1 contrast ratio */9}1011/* Respect user's contrast preference */12@media (prefers-contrast: high) {13 .tp-ui-wrapper {14 border: 2px solid currentColor;15 }1617 .tp-ui-clock-face {18 border: 2px solid currentColor;19 }20}2122@media (prefers-contrast: more) {23 /* Enhanced contrast for users who need it */24 .tp-ui-wrapper {25 background: #000000;26 color: #ffffff;27 }28}
Reduced Motion
Respect user preferences for reduced motion:
Disable Animations
1/* CSS: Respect prefers-reduced-motion */2@media (prefers-reduced-motion: reduce) {3 .tp-ui-wrapper {4 animation: none !important;5 transition: none !important;6 }78 .tp-ui-clock-hand {9 transition: none !important;10 }1112 .tp-ui-modal {13 animation: none !important;14 }15}1617// JavaScript: Detect user preference18const prefersReducedMotion = window.matchMedia(19 '(prefers-reduced-motion: reduce)'20).matches;2122const picker = new TimepickerUI(input, {23 ui: {24 animation: !prefersReducedMotion25 }26});2728picker.create();
Testing Accessibility
Tools and techniques for testing accessibility:
Automated Testing
1// Using axe-core for accessibility testing2import { axe } from 'jest-axe';34test('timepicker should have no accessibility violations', async () => {5 const { container } = render(6 <TimepickerComponent />7 );89 const results = await axe(container);10 expect(results).toHaveNoViolations();11});1213// Using @testing-library for keyboard testing14import { render, screen } from '@testing-library/react';15import userEvent from '@testing-library/user-event';1617test('can navigate with keyboard', async () => {18 const user = userEvent.setup();19 render(<TimepickerComponent />);2021 const input = screen.getByRole('textbox');22 await user.click(input);23 await user.keyboard('{Enter}');2425 // Verify modal opened26 expect(screen.getByRole('dialog')).toBeInTheDocument();2728 // Test arrow key navigation29 await user.keyboard('{ArrowUp}');30 await user.keyboard('{Enter}');31});
Manual Testing Checklist
Accessibility Resources
Learn more about web accessibility standards and best practices: