Skip to content

MudCheckBox, MudSwitch, MudRadio: Respect user-defined tabindex#12808

Merged
danielchalmers merged 2 commits intoMudBlazor:devfrom
danielchalmers:boolean-input-tabindex
Mar 4, 2026
Merged

MudCheckBox, MudSwitch, MudRadio: Respect user-defined tabindex#12808
danielchalmers merged 2 commits intoMudBlazor:devfrom
danielchalmers:boolean-input-tabindex

Conversation

@danielchalmers
Copy link
Member

This fixes a tabindex precedence issue on boolean inputs where user-supplied tabindex values could be ignored.

MudCheckBox, MudSwitch, and MudRadio were applying a default tabindex directly on the internal <input>, which could override user intent in some scenarios. Now, user-provided tabindex is respected.

Changes:

  • Added shared input attribute resolution in MudBooleanInput<T>:
    • GetInputAttributes()
    • Sets default tabindex (0 enabled / -1 disabled)
    • Merges UserAttributes after defaults
    • Uses case-insensitive keys (StringComparer.OrdinalIgnoreCase) so tabindex overrides are reliable
  • Updated:
    • MudCheckBox input markup to use @attributes="GetInputAttributes()"
    • MudSwitch input markup to use @attributes="GetInputAttributes()"
    • MudRadio input markup to use @attributes="GetInputAttributes()"

Closes #7346 and Closes #12800

Checklist:

  • I've read the contribution guidelines
  • My code follows the style of this project
  • I've added or updated relevant unit tests

…`, and `MudRadio`

This fixes a `tabindex` precedence issue on boolean inputs where user-supplied `tabindex` values could be ignored.

`MudCheckBox`, `MudSwitch`, and `MudRadio` were applying a default `tabindex` directly on the internal `<input>`, which could override user intent in some scenarios.
Now, user-provided `tabindex` is respected.

## Root Cause
The components combined `@attributes="UserAttributes"` with an explicit `tabindex="..."` on the same `<input>`, causing conflicting precedence behavior.

## Changes
- Added shared input attribute resolution in `MudBooleanInput<T>`:
  - `GetInputAttributes()`
  - Sets default `tabindex` (`0` enabled / `-1` disabled)
  - Merges `UserAttributes` after defaults
  - Uses case-insensitive keys (`StringComparer.OrdinalIgnoreCase`) so `tabindex` overrides are reliable
- Updated:
  - `MudCheckBox` input markup to use `@attributes="GetInputAttributes()"`
  - `MudSwitch` input markup to use `@attributes="GetInputAttributes()"`
  - `MudRadio` input markup to use `@attributes="GetInputAttributes()"`
@mudbot mudbot bot added accessibility Accessibility concerns (ARIA, keyboard, focus, screen readers, contrast) bug Unexpected behavior or functionality not working as intended labels Mar 4, 2026
@danielchalmers
Copy link
Member Author

@versile2 I did this as a more minimal/targeted version of #12800 from scratch. The tests are also identical between components in this one so there should be less risk of drift between them.

@danielchalmers danielchalmers changed the title MudCheckBox, MudSwitch, MudRadio: Respect tabindex on root element MudCheckBox, MudSwitch, MudRadio: Respect user-defined tabindex Mar 4, 2026

foreach (var userAttribute in UserAttributes)
{
attributes[userAttribute.Key] = userAttribute.Value;
Copy link
Contributor

@Anu6is Anu6is Mar 4, 2026

Choose a reason for hiding this comment

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

This would override the -1 on a disabled field if a UserAttribute includes a tabindex value.

Is this intentional? The previous solution worked differently

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes, so the user's parameter always wins

Copy link
Contributor

Choose a reason for hiding this comment

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

I guess we aren't the ARIA police but it just feels wrong to let it be tabbable if disabled lol.

Copy link
Member Author

Choose a reason for hiding this comment

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

The disabled attribute should still prevents focus/interaction. So overriding tabindex there mainly affects rendered markup, not actual keyboard accessibility behavior.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR fixes tabindex precedence for boolean input components so user-supplied tabindex values are not overridden by component defaults, addressing reported keyboard navigation issues in MudCheckBox, MudSwitch, and MudRadio.

Changes:

  • Centralized input attribute resolution in MudBooleanInput<T> via GetInputAttributes() with default tabindex and case-insensitive merging of UserAttributes.
  • Updated MudCheckBox, MudSwitch, and MudRadio input markup to use @attributes="GetInputAttributes()" (removing hard-coded tabindex from the <input>).
  • Added unit tests for checkbox/switch/radio to verify default, disabled, and custom (case-insensitive) tabindex behavior.

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated no comments.

Show a summary per file
File Description
src/MudBlazor/Base/MudBooleanInput.cs Adds shared GetInputAttributes() that applies a default tabindex and merges UserAttributes case-insensitively so user overrides win reliably.
src/MudBlazor/Components/CheckBox/MudCheckBox.razor Switches <input> attribute splatting to GetInputAttributes() to ensure custom tabindex is respected.
src/MudBlazor/Components/Switch/MudSwitch.razor Switches <input> attribute splatting to GetInputAttributes() to ensure custom tabindex is respected.
src/MudBlazor/Components/Radio/MudRadio.razor Switches <input> attribute splatting to GetInputAttributes() to ensure custom tabindex is respected.
src/MudBlazor.UnitTests/Components/CheckBoxTests.cs Adds tests for default/disabled/custom/case-insensitive tabindex behavior.
src/MudBlazor.UnitTests/Components/SwitchTests.cs Adds tests for default/disabled/custom/case-insensitive tabindex behavior.
src/MudBlazor.UnitTests/Components/RadioTests.cs Adds tests for default/disabled/custom/case-insensitive tabindex behavior.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

["tabindex"] = GetDisabledState() ? -1 : 0
};

foreach (var userAttribute in UserAttributes)
Copy link
Contributor

Choose a reason for hiding this comment

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

What happens if UserAttributes is null? Won't that create an exception?

Copy link
Member Author

Choose a reason for hiding this comment

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

Not in normal usage.

UserAttributes is initialized in MudComponentBase as a non-null empty dictionary:

So when no unmatched attributes are provided, it’s {} and the foreach is safe.

Also, this isn’t a new assumption from this change; MudBlazor already uses UserAttributes as non-null in multiple places (for example FieldId uses UserAttributes.TryGetValue(...) directly).

@danielchalmers danielchalmers merged commit 3e50ee3 into MudBlazor:dev Mar 4, 2026
16 checks passed
@danielchalmers danielchalmers deleted the boolean-input-tabindex branch March 4, 2026 22:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

accessibility Accessibility concerns (ARIA, keyboard, focus, screen readers, contrast) bug Unexpected behavior or functionality not working as intended

Projects

None yet

Development

Successfully merging this pull request may close these issues.

MudCheckBox tabindex is ignored on root element

4 participants