Skip to content

[2.x] fix: name the pane pin button and expose whether it is pinned - #4890

Merged
imorland merged 1 commit into
2.xfrom
im/pane-pin-button-a11y
Aug 3, 2026
Merged

[2.x] fix: name the pane pin button and expose whether it is pinned#4890
imorland merged 1 commit into
2.xfrom
im/pane-pin-button-a11y

Conversation

@imorland

@imorland imorland commented Aug 3, 2026

Copy link
Copy Markdown
Member

The button that pins the discussion list is icon-only and has no accessible name, so a screen reader can announce it as nothing more than "button". The back and drawer buttons on either side of it in the same component both label themselves, so this is an omission rather than a house style.

Core already detects it. Button.oncreate fires a debug warning when it finds a button with no content and no accessible label, and this button has been tripping that warning:

[Flarum Accessibility Warning] Button has no content and no accessible label. This means that screen-readers will not be able to interpret its meaning and just read "Button".

It is also a toggle. Its state was carried entirely by CSS — the thumbtack rotates 45° when unpinned — so whether the list was pinned was never exposed to assistive technology.

Change

  • aria-label from a new core.lib.nav.pin_pane_button key, sitting alongside the existing drawer_button.
  • aria-pressed reflecting pane.pinned.

The label names the control ("Pin Discussion List") rather than the action, so it stays stable while the state changes beneath it. Labelling it for the action instead ("Pin" / "Unpin") would mean a screen reader reading a button whose name changes under the user, with the state only ever implied.

Why aria-pressed is a string

Passing the boolean straight through does not work, and it fails silently. Mithril treats a boolean as an HTML boolean attribute:

passed rendered
true aria-pressed=""
false attribute absent
"true" / "false" aria-pressed="true" / aria-pressed="false"

ARIA needs the words. An empty value is invalid, and an absent aria-pressed makes the control read as a plain button rather than a toggle that happens to be off — so the boolean form is broken in both states. The first revision of this used aria-pressed={pane.pinned} and rendered no attribute at all; it typechecked and built cleanly, and only showed up when the rendered DOM was inspected.

There is no other use of aria-pressed in core, so there was no existing precedent to match.

Verified

Driven in a browser at a viewport wide enough for the pane, on a discussion page so the button renders:

  • accessible name Pin Discussion List, role button
  • aria-pressed moves falsetruefalse across two toggles, in step with the active class
  • 0 core accessibility debug warnings on the page, where this button previously produced one
  • back and drawer button labels unchanged; 149 unit tests pass; typecheck clean

Not in scope

Only this button. Since core uses aria-pressed nowhere else, other icon-only toggles may have the same gap — worth a sweep as separate work rather than widening this.

The button that pins the discussion list carries only an icon, and unlike
the back and drawer buttons either side of it, nothing that names it. A
screen reader reaches it and can say no more than "button". Core already
notices: Button warns in the console when it finds no content and no
accessible label, and this button has been tripping that warning rather
than being read as an omission.

It is also a toggle, and its state was carried entirely by the icon
rotating forty-five degrees when unpinned. Nothing about being pinned was
exposed to assistive technology at all.

The label names the control rather than the action, so it holds still while
the state changes underneath it; `aria-pressed` reports whether it is
currently on. Naming it for the action instead would mean a screen reader
reading a button whose name changes under it, with the state never stated.

`aria-pressed` is given as a string rather than a boolean on purpose.
Mithril renders a boolean as an HTML boolean attribute, so `false` omits
the attribute entirely and `true` renders it empty. Neither is valid ARIA,
and an absent `aria-pressed` reads as a plain button rather than as a toggle
that happens to be off. Core has no other use of the attribute, so there
was no local precedent to follow here.
@imorland
imorland requested a review from a team as a code owner August 3, 2026 20:58
@imorland imorland changed the title fix: name the pane pin button and expose whether it is pinned [2.x] fix: name the pane pin button and expose whether it is pinned Aug 3, 2026
@imorland imorland added the type/accessibility Issues relating to accessibility (keyboard navigation, screenreaders, text contrast, etc.) label Aug 3, 2026
@imorland imorland added this to the 2.0.0-rc.6 milestone Aug 3, 2026
@imorland
imorland merged commit 801d6a6 into 2.x Aug 3, 2026
25 checks passed
@imorland
imorland deleted the im/pane-pin-button-a11y branch August 3, 2026 21:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

type/accessibility Issues relating to accessibility (keyboard navigation, screenreaders, text contrast, etc.)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant