Skip to content

Mark inputs with a required() validator as aria-required - #11336

Open
ousamabenyounes wants to merge 1 commit into
marmelab:nextfrom
ousamabenyounes:fix/issue-9585
Open

Mark inputs with a required() validator as aria-required#11336
ousamabenyounes wants to merge 1 commit into
marmelab:nextfrom
ousamabenyounes:fix/issue-9585

Conversation

@ousamabenyounes

Copy link
Copy Markdown

Problem

When an input uses a required() validator (e.g. <TextInput source="id" validate={required()} />), react-admin adds the * to the label but leaves the underlying <input> without any required semantics. As a result:

  • Assistive technologies do not announce the field as required.
  • Tests can't assert it: expect(within(edit).getByLabelText('Id *')).toBeRequired() fails.

This was reported in #9585. As discussed there, the fix should use aria-required="true" rather than the native required attribute: react-admin relies on JS validation, and forms render with noValidate=false by default, so a native required attribute would trigger native browser validation. Adding aria-required was the explicit direction given by the maintainer on the issue.

Solution

Route an aria-required flag through the shared ResettableTextField renderer onto the native <input> element, and have TextInput pass aria-required={isRequired || undefined} (where isRequired already reflects the presence of a required() validator or the isRequired prop).

  • The value is applied to the html-input slot only, handling both MUI v5 (inputProps) and MUI v6+ (slotProps.htmlInput, including the callback form).
  • Caller-supplied inputProps / slotProps.htmlInput are preserved (non-lossy merge).
  • Strictly a no-op when the input is not required (no attribute is added).
  • The native required attribute is intentionally not set, so JS validation and label behavior are unchanged.

Scope: this PR fixes the reported TextInput case via the shared ResettableTextField. The same one-line aria-required={isRequired || undefined} wiring can be extended to the other inputs that expose isRequired (SelectInput, NumberInput, DateInput, …) — happy to widen this PR or open a follow-up, whichever the maintainers prefer.

How To Test

<TextInput source="id" validate={required()} /> now renders <input ... aria-required="true"> (and still no native required). A non-required input is unchanged.

Unit test added in TextInput.spec.tsx:

describe('aria-required', () => {
  it('should mark the input as required with aria-required (not the native required attribute) when a required() validator is set', ...)
  it('should not mark the input as required when no required validator is set', ...)
  it('should preserve caller-provided inputProps while adding aria-required', ...)
})

Test verification (RED → GREEN)

RED — on the unmodified next branch (test kept, production change reverted):

aria-required
  ✕ should mark the input as required with aria-required (not the native required attribute) when a required() validator is set
  ✓ should not mark the input as required when no required validator is set
  ✕ should preserve caller-provided inputProps while adding aria-required

  ● should mark the input as required ... › expect(element).toBeRequired()
    Received element is not required:
      <input aria-describedby=":r0:-helper-text" aria-invalid="false" id=":r0:" name="title" type="text" value="" />

Tests: 2 failed, 12 skipped, 1 passed, 15 total

GREEN — with the fix:

aria-required
  ✓ should mark the input as required with aria-required (not the native required attribute) when a required() validator is set
  ✓ should not mark the input as required when no required validator is set
  ✓ should preserve caller-provided inputProps while adding aria-required

Tests: 82 passed, 82 total

No regression across the shared renderer's consumers (TextInput, SelectInput, NumberInput suites all green); ESLint, Prettier, and the ra-corera-ui-materialui build all pass.

Additional Checks

  • The PR targets master for a bugfix or a documentation fix, or next for a feature — targets next as requested on the issue.
  • The PR includes unit tests.
  • The PR includes one or several stories — no new story: aria-required is a non-visual accessibility attribute and existing required stories (TextInput.stories.tsx, useInput.stories.tsx) already cover the required rendering. Happy to add one if preferred.
  • The documentation is up to date — the docs already recommend the aria-required={isRequired} pattern (docs_headless/.../Inputs.md, useInput.md); this makes the built-in TextInput conform, so no doc file drifts.

TextInput only displayed the "*" in its label but left the underlying
input without any required semantics, so assistive technologies (and
tests using toBeRequired()) did not see it as required. Add aria-required
via the shared ResettableTextField renderer, deliberately using
aria-required rather than the native required attribute so react-admin's
JS validation is preserved and no native browser validation is triggered.

Closes marmelab#9585
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.

1 participant