Skip to content

Zoom Menu popover is not keyboard-reachable — items sit last in tab order and .focus() is silently reverted #797

Description

@etuan

Summary

Opening the Zoom Menu popover ([data-epdf-i="zoom-menu"], triggered from the toolbar's "Zoom Menu" button) does not move focus into the popover. Its 15 menu items are real, enabled, tabbable <button role="menuitem"> elements, but they're placed at the very end of the DOM (as a late sibling near the end of the [data-epdf] root, not adjacent to the trigger), so keyboard users must Tab through every other unrelated toolbar control to reach them. This fails WCAG 2.1.1 (Keyboard) for keyboard-only users.

Separately — and this may be the more useful clue for a fix — calling .focus() programmatically on a menu item from outside embedpdf is silently reverted. activeElement snaps back to the trigger button synchronously and stays there indefinitely, even when nothing else in the page is doing any focus management. This suggests something internal is fighting external focus assignment into the popover, which is likely also the root cause of #1.

Environment

  • @embedpdf/react-pdf-viewer: 2.15.0 (latest at time of filing)
  • Also present with @embedpdf/core, @embedpdf/engines, @embedpdf/plugin-document-manager, @embedpdf/plugin-render, @embedpdf/plugin-scroll, @embedpdf/plugin-viewport at the same version
  • React 17.0.2
  • Reproduced in Chrome (headless, via Puppeteer) with zero surrounding app code — see minimal repro below

Minimal repro

import React from 'react';
import ReactDOM from 'react-dom';
import PDFViewer, { ZoomMode } from '@embedpdf/react-pdf-viewer';

ReactDOM.render(
  <div style={{ height: '100vh' }}>
    <PDFViewer
      config={{
        src: 'https://snippet.embedpdf.com/ebook.pdf',
        theme: { preference: 'light' },
        zoom: { defaultZoomLevel: ZoomMode.FitPage },
      }}
    />
  </div>,
  document.getElementById('root'),
);

Steps (keyboard-only):

  1. Tab to the "Zoom Menu" button in the toolbar, press Enter to open it.
  2. Press Tab.

Expected: Focus moves into the popover (ideally straight to the first item, or at minimum reachable within a couple of Tabs).

Actual: Focus walks through 15 unrelated toolbar controls first — Zoom Out, Zoom In, Pan, Pointer, all six mode tabs, Search, Comment, Previous Page, the page-number field, Next Page — landing on the popover's first item ("25%") only on the 16th Tab press.

Steps (programmatic):

const root = document.querySelector('embedpdf-container').shadowRoot;
const menu = root.querySelector('[data-epdf-i="zoom-menu"]');
const firstItem = menu.querySelector('button');
firstItem.focus();
console.log(root.activeElement === firstItem); // expected: true

Actual: root.activeElement remains the trigger button — false — immediately, and still false checked again after a microtask, a 0ms timeout, and a 100ms timeout.

Suggested fix direction

Move focus to the first (or currently-selected) menu item when the popover opens, and either trap Tab within it or ensure DOM/tab order places it adjacent to the trigger. Since external .focus() calls are also being reverted, whatever code is doing that reversion is probably the same code that should instead be performing the correct initial focus-move.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions