From 86cf944373aa4f5e31c5e12178b523cfc606267d Mon Sep 17 00:00:00 2001 From: alex-js-ltd Date: Tue, 4 Aug 2026 13:44:42 +0100 Subject: [PATCH 1/3] test(date-range-input): add failing test for aria-required on trigger button role MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit aria-required is not a supported attribute for role="button" per the ARIA spec (WCAG 4.1.2, axe: aria-allowed-attr) — it's meant for editable/selectable widget roles like textbox or combobox, not activation controls. The trigger button in DateRangeInput hardcodes aria-required when isRequired is true, which is invalid. Replaces the existing test that asserted this buggy behavior as correct, and adds a companion test confirming the required state still reaches assistive tech via the trigger's accessible name. Both currently fail; the fix follows in a separate commit. --- .../DateRangeInput/DateRangeInput.test.tsx | 23 +++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/packages/core/src/DateRangeInput/DateRangeInput.test.tsx b/packages/core/src/DateRangeInput/DateRangeInput.test.tsx index e6baa64d1d08..3565d3e49793 100644 --- a/packages/core/src/DateRangeInput/DateRangeInput.test.tsx +++ b/packages/core/src/DateRangeInput/DateRangeInput.test.tsx @@ -92,7 +92,10 @@ describe('DateRangeInput', () => { expect(screen.getByText('Range')).toBeInTheDocument(); }); - it('sets aria-required when isRequired is true', () => { + it('does not put aria-required on the trigger button even when isRequired is true', () => { + // aria-required is not a supported attribute for role="button" (axe: + // aria-allowed-attr, WCAG 4.1.2) — the trigger is a plain button, not an + // editable/selectable widget role like textbox or combobox. render( { />, ); const trigger = getButton(/Range/); - expect(trigger).toHaveAttribute('aria-required', 'true'); + expect(trigger).not.toHaveAttribute('aria-required'); }); it('does not set aria-required when isRequired is false', () => { @@ -111,6 +114,22 @@ describe('DateRangeInput', () => { expect(trigger).not.toHaveAttribute('aria-required'); }); + it('exposes required state via the trigger accessible name instead', () => { + // With aria-required unusable on role="button", the required state must + // still reach assistive tech — via the accessible name (aria-label), + // which is legal on any role. + render( + {}} + />, + ); + const trigger = getButton(/Range/); + expect(trigger.getAttribute('aria-label')).toMatch(/Required/); + }); + it('disables trigger when isDisabled is true', () => { render( Date: Tue, 4 Aug 2026 13:57:15 +0100 Subject: [PATCH 2/3] fix(date-range-input): drop invalid aria-required from trigger button MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit aria-required is not a supported attribute for role="button" (the trigger's implicit role) — it's defined for editable/selectable widget roles like textbox or combobox, not activation controls. The trigger hardcoded it whenever isRequired was true, an aria-allowed-attr violation (WCAG 4.1.2) flagged in #4681 against the Required story. The required state now folds into the trigger's accessible name (aria-label) instead, reusing the same "Required" word the visible Field label already shows, so the information isn't lost — just moved somewhere legal for the role. --- ...einput-trigger-no-longer-emits-invalid-a-d9dw.md | 6 ++++++ packages/core/src/DateRangeInput/DateRangeInput.tsx | 13 +++++++++---- 2 files changed, 15 insertions(+), 4 deletions(-) create mode 100644 .changeset/daterangeinput-trigger-no-longer-emits-invalid-a-d9dw.md diff --git a/.changeset/daterangeinput-trigger-no-longer-emits-invalid-a-d9dw.md b/.changeset/daterangeinput-trigger-no-longer-emits-invalid-a-d9dw.md new file mode 100644 index 000000000000..e79f12e706de --- /dev/null +++ b/.changeset/daterangeinput-trigger-no-longer-emits-invalid-a-d9dw.md @@ -0,0 +1,6 @@ +--- +'@astryxdesign/core': patch +--- + +[fix] DateRangeInput trigger no longer emits invalid aria-required on its button role +@alex-js-ltd diff --git a/packages/core/src/DateRangeInput/DateRangeInput.tsx b/packages/core/src/DateRangeInput/DateRangeInput.tsx index c1e3e4235e4f..365123c0f4ed 100644 --- a/packages/core/src/DateRangeInput/DateRangeInput.tsx +++ b/packages/core/src/DateRangeInput/DateRangeInput.tsx @@ -509,9 +509,15 @@ export function DateRangeInput({ [fireChange], ); - const triggerAriaLabel = value - ? `${label}: ${displayValue}` - : `${label}: ${placeholder}`; + // aria-required is not a supported attribute for role="button" (the + // trigger's implicit role), so a required state can't be exposed that way. + // Fold it into the accessible name instead — legal on any role, and reuses + // the same word the visible Field label already shows next to it. + const triggerAriaLabel = isRequired + ? `${label}: ${value ? displayValue : placeholder}, ${t('@astryx.field.required')}` + : value + ? `${label}: ${displayValue}` + : `${label}: ${placeholder}`; return ( Date: Tue, 4 Aug 2026 14:20:35 +0100 Subject: [PATCH 3/3] test(date-range-input): assert accessible name instead of raw aria-label attribute toHaveAccessibleName computes the actual accessible name the same way assistive tech does, so the test verifies the real contract (what gets announced) rather than pinning to aria-label as the specific mechanism. A future change to aria-labelledby or a native