Accessibility Regression from a Custom Menu Template
A theme overrides menu--main.html.twig to output a custom <ul> with <li> items and <a> links. The design adds a dropdown submenu using CSS :hover only, with no JavaScript. The original menu used aria-expanded and keyboard focus management. After the override, keyboard users cannot reach submenu items, screen readers do not announce submenus, and the active trail class was dropped. The menu is also used in a mobile off-canvas panel. The team wants the visual design kept but accessibility restored, and wants to know whether this belongs in the theme or a module.
1. Identify the accessibility failures introduced and the root cause.
Read the worked answer
Failures:
- Hover-only submenus are inaccessible to keyboard and touch users;
:hoverhas no keyboard equivalent. - Missing
aria-expandedandaria-haspopupon parent links means screen readers cannot convey submenu state. - Dropping the active trail class removes a visual and programmatic cue for current location.
- The mobile off-canvas needs focus management (focus trap, return focus) that the override removed.
Root cause: the template override replaced the markup contract (attributes, classes, ARIA) provided by the base menu template instead of extending it. Overriding markup without preserving the structural attributes breaks both CSS hooks and assistive technology expectations. The fix is to keep the base attributes (e.g., attributes, item.attributes, is_expanded, in_active_trail) and layer the design on top.
2. Where should the interactive behavior live, and what is the minimal correct implementation?
Read the worked answer
Behavior belongs in JavaScript (a theme library or a small module), not in Twig alone, because state toggling and focus management are runtime concerns. Twig should render correct semantics and initial ARIA state; JS should toggle aria-expanded and visibility on click/keyboard (Enter/Space/Escape).
Minimal correct implementation:
- Twig: preserve
item.attributes, addaria-haspopup="true"andaria-expanded="false"on parent links, keepin_active_trailclass. - JS: on toggle, set
aria-expanded, show/hide submenu, support Escape to close and arrow keys to move; for off-canvas, trap focus and restore focus on close. - CSS: keep hover as a progressive enhancement but not the only mechanism.
Reasoning: semantics in markup, state in JS, styling in CSS - separating concerns keeps it accessible and maintainable.