Summary
Sketchbook UI's Modal and Dropdown components currently lack the keyboard accessibility behaviour required by WCAG 2.1 and the WAI-ARIA Authoring Practices. Specifically: the Modal does not trap focus inside the dialog when open, does not close on Escape key, and does not restore focus to the trigger element on close. The Dropdown similarly does not support arrow-key navigation between options. For a published npm library used in real projects, these gaps make the components unusable for keyboard-only users and inaccessible to screen readers.
Problem
Modal: Opening the modal does not move focus inside it. Users navigating by keyboard can Tab through elements behind the modal overlay. Pressing Escape does not close the modal. When the modal closes, focus is not returned to the element that triggered it. The dialog element lacks role="dialog" and aria-modal="true".
Dropdown: Options are not navigable with ArrowUp/ArrowDown keys. Pressing Enter or Space does not select the focused option. The dropdown trigger lacks aria-expanded and aria-haspopup attributes.
- These are not cosmetic issues — they are functional blockers for keyboard-only users and fail WCAG 2.1 Success Criteria 2.1.1 (Keyboard) and 2.1.2 (No Keyboard Trap).
Impact
- The library is unusable by keyboard-only users for any workflow involving Modal or Dropdown.
- Screen readers cannot correctly announce these components to visually impaired users.
- Projects using sketchbook-ui that require accessibility compliance (government, education, enterprise) cannot adopt these components.
- The hand-drawn aesthetic is unique and attractive — it deserves to be fully accessible so it can be used in production without compromise.
Proposed Solution
Modal — focus trap and keyboard handling:
// Inside Modal component
import { useEffect, useRef } from "react";
const FOCUSABLE_SELECTORS =
'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])';
export function Modal({ isOpen, onClose, children, ...props }) {
const modalRef = useRef<HTMLDivElement>(null);
const triggerRef = useRef<Element | null>(null);
useEffect(() => {
if (isOpen) {
triggerRef.current = document.activeElement;
const firstFocusable = modalRef.current?.querySelector<HTMLElement>(FOCUSABLE_SELECTORS);
firstFocusable?.focus();
} else {
(triggerRef.current as HTMLElement)?.focus();
}
}, [isOpen]);
useEffect(() => {
const handleKeyDown = (e: KeyboardEvent) => {
if (!isOpen) return;
if (e.key === "Escape") onClose();
if (e.key === "Tab") {
// focus trap logic — cycle within modal
const focusableEls = Array.from(
modalRef.current?.querySelectorAll<HTMLElement>(FOCUSABLE_SELECTORS) ?? []
);
const first = focusableEls[0];
const last = focusableEls[focusableEls.length - 1];
if (e.shiftKey ? document.activeElement === first : document.activeElement === last) {
e.preventDefault();
(e.shiftKey ? last : first).focus();
}
}
};
document.addEventListener("keydown", handleKeyDown);
return () => document.removeEventListener("keydown", handleKeyDown);
}, [isOpen, onClose]);
return (
<div
ref={modalRef}
role="dialog"
aria-modal="true"
aria-label={props["aria-label"] ?? "Dialog"}
// ... rest of props
>
{children}
</div>
);
}
Dropdown — keyboard navigation:
// Arrow key navigation + Enter/Space selection
const handleKeyDown = (e: React.KeyboardEvent) => {
switch (e.key) {
case "ArrowDown": setHighlightedIndex(i => Math.min(i + 1, options.length - 1)); break;
case "ArrowUp": setHighlightedIndex(i => Math.max(i - 1, 0)); break;
case "Enter":
case " ": onSelect(options[highlightedIndex]); setOpen(false); break;
case "Escape": setOpen(false); break;
}
};
Scope of this PR:
Modal: focus trap, Escape to close, focus restoration, role="dialog", aria-modal="true".
Dropdown: arrow key navigation, Enter/Space to select, aria-expanded, aria-haspopup, aria-activedescendant.
- Unit tests for each accessibility behaviour using
@testing-library/user-event.
- Storybook story updates showing keyboard interaction in the docs.
Could you please assign this issue to me?
Labels: accessibility, bug, enhancement, GSSoC 2026
Summary
Sketchbook UI's
ModalandDropdowncomponents currently lack the keyboard accessibility behaviour required by WCAG 2.1 and the WAI-ARIA Authoring Practices. Specifically: theModaldoes not trap focus inside the dialog when open, does not close onEscapekey, and does not restore focus to the trigger element on close. TheDropdownsimilarly does not support arrow-key navigation between options. For a published npm library used in real projects, these gaps make the components unusable for keyboard-only users and inaccessible to screen readers.Problem
Modal: Opening the modal does not move focus inside it. Users navigating by keyboard can Tab through elements behind the modal overlay. PressingEscapedoes not close the modal. When the modal closes, focus is not returned to the element that triggered it. The dialog element lacksrole="dialog"andaria-modal="true".Dropdown: Options are not navigable withArrowUp/ArrowDownkeys. PressingEnterorSpacedoes not select the focused option. The dropdown trigger lacksaria-expandedandaria-haspopupattributes.Impact
Proposed Solution
Modal — focus trap and keyboard handling:
Dropdown — keyboard navigation:
Scope of this PR:
Modal: focus trap,Escapeto close, focus restoration,role="dialog",aria-modal="true".Dropdown: arrow key navigation,Enter/Spaceto select,aria-expanded,aria-haspopup,aria-activedescendant.@testing-library/user-event.Could you please assign this issue to me?
Labels:
accessibility,bug,enhancement,GSSoC 2026