Skip to content

Structure tree row action menus are visibility:hidden and unreachable by keyboard #15392

Closed

Description

@bpmore

Bug description

Each row in a structure tree carries an "Open dropdown menu" button holding that entry's actions. The button is styled visibility: hidden and revealed on hover, which removes it from the tab order entirely.

Keyboard users cannot reach any per-entry action on the page tree or the navigation tree.

Verified on /cp/collections/pages with a full 70-step walk of the tab order. The walk reaches:

... Toggle tree -> Create Entry -> Collapse -> Expand -> (svg) -> "Home" -> wraps to top

and never reaches a row menu. Computed style on that button:

aria-label: "Open dropdown menu"
visibility:  hidden
display:     flex
opacity:     1

Source: https://github.com/statamic/cms/blob/v6.31.0/resources/js/components/structures/Branch.vue#L59

WCAG 2.1 SC 2.1.1 Keyboard (Level A).

How to reproduce

  1. composer create-project statamic/statamic
  2. Visit /cp/collections/pages
  3. Put focus in the address bar, then press Tab repeatedly through the whole page

Expected: at some point focus lands on a row's actions button. Actual: focus goes from the tree controls straight to the "Home" entry link and then wraps, never stopping on a row menu.

Mouse users see the button appear on hover, so the actions are reachable with a pointer only.

Suggested fix

Reveal the button on :focus-within as well as :hover, or keep it rendered at reduced opacity and raise it on hover/focus. Hiding a control from the pointer is fine; taking it out of the tab order is not.

Worth auditing other hover-revealed controls in the CP for the same pattern.

Additional note

While walking the tab order on this screen, a bare <svg> with no accessible name and no role also appears as a tab stop (between "Expand" and the "Home" link). It announces nothing and does nothing. Probably wants focusable="false" and aria-hidden="true". Happy to split that out into its own issue if preferred.

Environment

Environment
Laravel Version: 13.30.1
PHP Version: 8.4.23
Composer Version: 2.10.2
Environment: local
Debug Mode: ENABLED
Maintenance Mode: OFF
Timezone: UTC
Locale: en

Cache
Config: NOT CACHED
Events: NOT CACHED
Routes: NOT CACHED
Views: CACHED

Drivers
Broadcasting: log
Cache: file
Database: sqlite
Logs: stack / single
Mail: log
Queue: sync
Session: file

Storage
public/storage: NOT LINKED

Statamic
Addons: 0
License Key: Not set
Sites: 1
Stache Watcher: Enabled (auto)
Static Caching: Disabled
Version: 6.31.0 PRO

Installation

Fresh statamic/statamic site via CLI

Additional details

Found during a WCAG 2.1 AA audit of a vanilla composer create-project statamic/statamic install with no addons and no custom code. Tested with axe-core 4.x on Chromium 153 at 1440x1000, plus manual keyboard traversal. Source references point at v6.31.0.

Activity

  1. changed the title [-][6.x] Structure tree row action menus are visibility:hidden and unreachable by keyboard[/-] [+]Structure tree row action menus are visibility:hidden and unreachable by keyboard[/+] on Sep 8, 2026
  2. duncanmcclean commented on Sep 8, 2026

    @duncanmcclean
    Member

    Thanks for the thorough audit. I've had a look and this one isn't quite what it seems.

    There's no hover-reveal on tree rows. Every row's actions button is rendered visible and sits in the tab order. The only exception is the root page, whose button is intentionally hidden because the root has no actions (it can't have children or be deleted from the tree).

    A fresh statamic/statamic install only has the "Home" entry, which is the root page, so every row in your tab walk was that one hidden button. Add a second entry to the Pages collection and you'll find its "Open dropdown menu" button is reachable with Tab. Same on the navigation tree, where there's no root at all.

    The bare <svg> tab stop is a separate thing: it's the "root page" home icon, which has a tooltip. Our tooltip directive listens for focus so tooltips work from the keyboard, and Chromium treats an svg with focus listeners as focusable. Happy for you to open that as its own issue so we can decide how to handle it across the CP.

    Closing this one, but shout if you can reproduce an unreachable menu on a non-root row.

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

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions