Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@astryxdesign/core': patch
---

[fix] DateRangeInput trigger no longer emits invalid aria-required on its button role
@alex-js-ltd
23 changes: 21 additions & 2 deletions packages/core/src/DateRangeInput/DateRangeInput.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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(
<DateRangeInput
label="Range"
Expand All @@ -102,7 +105,7 @@ describe('DateRangeInput', () => {
/>,
);
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', () => {
Expand All @@ -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(
<DateRangeInput
label="Range"
isRequired
value={null}
onChange={() => {}}
/>,
);
const trigger = getButton(/Range/);
expect(trigger).toHaveAccessibleName(/Required/);
});

it('disables trigger when isDisabled is true', () => {
render(
<DateRangeInput
Expand Down
13 changes: 9 additions & 4 deletions packages/core/src/DateRangeInput/DateRangeInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
<Field
Expand Down Expand Up @@ -598,7 +604,6 @@ export function DateRangeInput({
aria-disabled={showsDisabledMessage ? 'true' : undefined}
aria-label={triggerAriaLabel}
aria-describedby={ariaDescribedBy}
aria-required={isRequired === true ? 'true' : undefined}
aria-invalid={status?.type === 'error' ? 'true' : undefined}
aria-busy={isBusy || undefined}
aria-expanded={popover.isOpen}
Expand Down