Make server-rendered Pajamas::ButtonComponent accessible when disabled
Summary
Follow-up from review of !247432 (merged) (removal of the accessible_disabled_button feature flag), specifically this discussion.
GlButton (Vue) now renders aria-disabled="true" instead of the native disabled attribute when disabled, keeping the button focusable and announced by assistive technology, and relies on JS to no-op the click so a focusable-but-disabled button does nothing when activated.
Server-rendered Pajamas::ButtonComponent has no equivalent. Pajamas::ButtonComponent#base_attributes (app/components/pajamas/button_component.rb:129-130) already sets both disabled and aria-disabled when disabled/loading:
attributes['disabled'] = 'disabled' if @disabled || @loading
attributes['aria-disabled'] = true if @disabled || @loadingSo the aria-disabled attribute is present, but the button also still carries the native disabled attribute, which removes it from the tab order — the opposite of the focusable-when-disabled behavior GlButton now provides. And if we wanted to drop the native disabled to match GlButton, there is no JS layer on server-rendered buttons to prevent the click from doing something.
The decision
Settled in !250688 (merged), and it's a third option rather than either of the two below: make it opt-in.
Pajamas::ButtonComponentgains anaccessible_disabledoption defaulting tofalse. Server-rendered buttons keep nativedisabledunless a call site asks for the accessible variant.- In
@gitlab/ui,GlButtonkeeps itsaccessibleDisabledprop and flips the default totrue. Vue buttons stay accessible by default, and authors opt out with:accessible-disabled="false".
Why the two sides differ: GlButton owns both markup and behaviour, so it can guarantee a focusable disabled button does nothing when activated. Server-rendered buttons have no such coupling. Rapid Diffs renders a disabled button that JS re-enables at runtime, with clicks routed through a web component, so the markup on its own doesn't tell you how the button behaves. Defaulting to native disabled server-side avoids guessing.
Proposed by @markrian, endorsed by @trevorpierce, @sdejonge and @aferch. Still open: how the click guard for opted-in buttons should be bound, raised by @thutterer.
The two options originally framed here were (1) add shared JS so all server-rendered buttons drop native disabled, or (2) leave them all on native disabled and accept the inconsistency. Opt-in is the middle.
Scope note
Out of scope for !247432 (merged), which is confined to the flag removal and its GlButton test fallout. Raised here so the server-rendered gap is tracked rather than lost.
References
- MR discussion: !247432 (comment 3623863081)
- Related follow-up: #607085 (remove the
accessibleDisabledButtonconfig key fromapp/assets/javascripts/commons/gitlab_ui.jsonce@gitlab/uimakesaria-disabledtheGlButtondefault). Scope is the config key only — theaccessibleDisabledprop is unaffected and is being kept. - Prior art (GlButton side): gitlab-org/gitlab-services/design.gitlab.com!5912 (merged)