Skip to content

Conversation

@romaricpascal
Copy link
Member

@romaricpascal romaricpascal commented Sep 30, 2025

On desktop, with Safari, prevents Voice Over's 'Form controls' section of the rotor from showing the toggles revealing/hiding the sections of the mobile menu.

Before After
VoiceOver's rotor showing 5 'collapsed button' items in its Form Controls menu VoiceOver's rotor not showing 5 'collapsed button' items in its Form Controls menu

Note

Given the markup, only the buttons have extra aria attributes. The other hidden elements from that component are:

  • the list of links for each sub-section of the navigation => the content is not listed in the 'Links' section of the rotor
  • the link for each section that appears on desktop => they are not listed in the 'Links' section of the rotor either

That makes me think we're OK in terms of what Voice Over sees

How it works

Adds an aria-hidden attribute in complement to the hidden attribute already added to the toggles on desktop viewports.

While in there, it also adds a disabled attribute while the toggles are hidden, to prevent their activation in case another bug keeps them accessible (and to prevent their activation through .click() in JavaScript) as triggering a programmatic click leads to a fairly broken display on wide viewports.

Thoughts

@selfthinker This might partially address #4879. A similar fix to the Service Navigation should be applied in GOV.UK Frontend so the button toggling the whole mobile menu is appropriately hidden from VoiceOver.

@netlify
Copy link

netlify bot commented Sep 30, 2025

You can preview this change here:

Name Link
🔨 Latest commit 0a05b2f
🔍 Latest deploy log https://app.netlify.com/projects/govuk-design-system-preview/deploys/68f24fc3aa2b590008b607c4
😎 Deploy Preview https://deploy-preview-4892--govuk-design-system-preview.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@romaricpascal romaricpascal force-pushed the fix-voiceover-menu-button-access branch from 971b5a2 to 66aa4bb Compare September 30, 2025 16:13
Comment on lines +125 to +131
/**
* Collection of attributes that needs setting on a `<button>`
* to fully hide it, both visually and from screen-readers,
* and prevent its activation while hidden
*/
const attributesForHidingButton = {
hidden: '',
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

note To ensure we set an unset the same list of attributes, this object acts as the source of truth.

@romaricpascal
Copy link
Member Author

I've tried to see if it was a more general issue with <button hidden> tags, using a reduced test page with various ways to set the hidden attribute (in initial markup, added with JavaScript, already in a button JavaScript is adding on the page, added to a button JavaScript is added on the page). VoiceOver's rotor was only showing the visible button consistently.

Reduced test page source
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
  <h1>A web page</h1>
  <button>A visible button</button>
  <button hidden>A button with <code>hidden</code></button>
  <button data-hide>A button to which <code>hidden</code> is added in JavaScript</button>
</body>
<script type="module">
  // Hide an existing button
  document.querySelector('[data-hide]').setAttribute('hidden', '')

  // Add an already hidden button
  const hiddenButton = document.createElement('button');
  hiddenButton.textContent = 'A button'
  hiddenButton.hidden = true
  document.body.append(hiddenButton)

  // Add a button then hides it immediately
  const buttonImmediatelyHidden = document.createElement('button');
  buttonImmediatelyHidden.textContent = 'A button'
  document.body.append(buttonImmediatelyHidden)
  buttonImmediatelyHidden.setAttribute('hidden', true)

  // Add a button hidden at next frame
  const buttonHiddenNextFrame = document.createElement('button');
  buttonHiddenNextFrame.textContent = 'A button'
  document.body.append(buttonHiddenNextFrame)
  requestAnimationFrame(() => {
    buttonHiddenNextFrame.setAttribute('hidden', true)
  })

  // Add a button hidden after 1s
  const buttonHiddenNextSecond = document.createElement('button');
  buttonHiddenNextSecond.textContent = 'A button'
  document.body.append(buttonHiddenNextSecond)
  setTimeout(() => {
    buttonHiddenNextSecond.setAttribute('hidden', true)
  }, 1000)
</script>
</html>

@selfthinker
Copy link
Contributor

@selfthinker This might partially address #4879. A similar fix to the Service Navigation should be applied in GOV.UK Frontend so the button toggling the whole mobile menu is appropriately hidden from VoiceOver.

@romaricpascal, I've just tested this in my iPad and that has indeed fixed the problem. 🎉

There is still the "menu" button that is visually hidden but VoiceOver can access. I assume that needs the same treatment.

* to fully hide it, both visually and from screen-readers,
* and prevent its activation while hidden
*/
const attributesForHidingButton = {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if there is a point in making this more global and generic? I assume every element that uses hidden would benefit from this?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, thinking we may want a hideFormControl function and a corresponding showFormControl function. This feels like something for GOV.UK Frontend though, but it'd need to be in the public JS API for other teams (and our website to benefit from it). @36degrees does that sound like expanding our API too much?

@romaricpascal
Copy link
Member Author

romaricpascal commented Oct 2, 2025

@romaricpascal, I've just tested this in my iPad and that has indeed fixed the problem. 🎉

🥳 🥳 🥳 Glad to hear it fixes things on the iPad as well

There is still the "menu" button that is visually hidden but VoiceOver can access. I assume that needs the same treatment.

That one needs to be fixed in GOV.UK Frontend itself as that menu is managed by the Service Navigation component. That'll mean waiting for a release before we can benefit from it on the Design System site. For now, I've created an issue for the Service Navigation button.

Adds an `aria-hidden` attribute in complement to the `hidden`
attribute already added to the toggles on desktop viewports.

While in there, also adds a `disabled` attribute while the toggles
are hidden, to prevent their activation in case another bug keeps
them accessible (and to prevent their activation through `.click()`
in JavaScript).
@romaricpascal romaricpascal force-pushed the fix-voiceover-menu-button-access branch from 5b6f38d to 0a05b2f Compare October 17, 2025 14:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants