-
Notifications
You must be signed in to change notification settings - Fork 15
feat(Row): Row switch announces when it has expanded content #1462
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
feat(Row): Row switch announces when it has expanded content #1462
Conversation
There was a problem hiding this 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 adds accessibility features for Row components with switches that act as accordion toggles, implementing ARIA patterns for disclosure widgets. It enables Row switches to announce their expanded state and optionally display loading indicators.
Key Changes:
- Added
SwitchDisclosuretype with ARIA attributes (expanded, controls, live) and loading states (busy, showSpinner) - Integrated dynamic aria-label generation that appends "options below" text when expanded
- Added loading state support with switch disabling and optional spinner display
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
src/list.tsx
Outdated
| const computedAriaLabel = | ||
| ariaLabel ?? | ||
| (props.switchDisclosure?.expanded | ||
| ? `${title} ${props.switchDisclosure?.onLabelWhenExpanded ?? 'Options available below.'}` |
Copilot
AI
Nov 18, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The fallback text 'Options available below.' is hardcoded in English. This should be internationalized or derived from a translation function to support multiple languages, as the PR description shows usage with I18N.translate() in the webapp integration examples.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i haven't found any I18N.translate() in this repo. How should I approach this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Search in the repo for usage examples of: const {texts, t} = useTheme();
Texts are defined in the context provider and can be overriden, for example:
texts.formCreditCardNumberLabel || t(tokens.formCreditCardNumberLabel)
texts.formCreditCardNumberLabelis the user defined text override via provider (optional)t(tokens.formCreditCardNumberLabel)calls the translate function for the token
src/list.tsx
Outdated
| </Box> | ||
| <div | ||
| aria-live={props.switchDisclosure?.live ?? 'off'} | ||
| aria-atomic |
Copilot
AI
Nov 18, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The aria-atomic attribute is always set to true, but aria-live defaults to 'off'. When aria-live is 'off', aria-atomic has no effect. Consider only setting aria-atomic when aria-live is not 'off', or documenting why aria-atomic should always be present.
| aria-atomic | |
| {...(props.switchDisclosure?.live && props.switchDisclosure.live !== 'off' ? {'aria-atomic': true} : {})} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have adjusted aria-atomic so that it is only added when aria-live is not “off”.
In practice, our case always uses live: “assertive”, but we leave the type open to “polite” | “off” | “assertive”, so the wrapper behaves correctly in any of them
| <div | ||
| aria-live={props.switchDisclosure?.live ?? 'off'} | ||
| aria-atomic | ||
| aria-busy={rowIsBusy || undefined} |
Copilot
AI
Nov 18, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using || undefined to conditionally render the attribute is unnecessary. The aria-busy attribute should be explicitly set to 'true' or 'false' as a string, or omitted entirely. Use aria-busy={rowIsBusy ? 'true' : undefined} or aria-busy={rowIsBusy ? true : undefined} instead.
| aria-busy={rowIsBusy || undefined} | |
| aria-busy={rowIsBusy ? 'true' : undefined} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i thought is a type Booleanish = boolean | 'true' | 'false'; so booleans are also valid, right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think so
|
Size stats
|
|
Accessibility report ℹ️ You can run this locally by executing |
|
nada, ya enconte el enlace ⬇️ |
|
Deploy preview for mistica-web ready! ✅ Preview Built with commit 29943be. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull Request Overview
Copilot reviewed 1 out of 1 changed files in this pull request and generated 1 comment.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
pladaria
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please, implement a store (can be private) with an usage example of this feature.
A playroom snippet would also be nice
src/list.tsx
Outdated
| atomicReading?: boolean; | ||
| } | ||
|
|
||
| type SwitchDisclosure = { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we consider a more open approach rather than the row with switch only?
I was reviewing requests from design teams and in this case the control used is a radio instead of a switch:
Telefonica/mistica-design#2172
Can (or should) this approach be abstracted to work with any type of control the list allows?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
agree, I think we should make this more generic
src/list.tsx
Outdated
| busy?: boolean; // blocks UI and sets aria-busy | ||
| showSpinner?: boolean; // shows spinner on the right |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
showSpinner and busy aren't the same?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
one was for showing the spinner the other for setting aria-busy, but as the spinner is not in the spec, i'll delete the showSpinner
src/list.tsx
Outdated
| atomicReading?: boolean; | ||
| } | ||
|
|
||
| type SwitchDisclosure = { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
agree, I think we should make this more generic
src/list.tsx
Outdated
| } | ||
|
|
||
| type SwitchDisclosure = { | ||
| expanded: boolean; // is the related content expanded |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you want comments to be shown by vscode as jsdoc, you need to write them in jsdoc format:
{
/** some doc about foo */
foo: string;
}
src/list.tsx
Outdated
| controlsId?: string; // id of the related content (if any) | ||
| live?: 'off' | 'polite' | 'assertive'; // announcement channel when changing expanded | ||
| busy?: boolean; // blocks UI and sets aria-busy |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I prefer using aria-xxx naming
src/list.tsx
Outdated
| live?: 'off' | 'polite' | 'assertive'; // announcement channel when changing expanded | ||
| busy?: boolean; // blocks UI and sets aria-busy | ||
| showSpinner?: boolean; // shows spinner on the right | ||
| onLabelWhenExpanded?: string; // message when expanded = true |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why do we need a different label for the expanded state? can't this be controlled by Row users by changing the label when needed?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is for changing the announcement of the SR, in case you wanted something diffferent form "Options below"
src/list.tsx
Outdated
| trackingEvent?: TrackingEvent | ReadonlyArray<TrackingEvent>; | ||
|
|
||
| switch: ControlProps | undefined; | ||
| switchDisclosure?: SwitchDisclosure; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why a different switchDisclosure prop? shouldn't most of that props be part of the ControlProps type?
src/list.tsx
Outdated
| aria-controls={props.switchDisclosure?.controlsId} | ||
| aria-expanded={props.switchDisclosure?.expanded} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this doesn't work. You need to add these aria props in Switch component. TS doesn't warn you because there is a TS bug that allows you passing any aria-xxx prop to any component
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| <div | ||
| aria-live={props.switchDisclosure?.live ?? 'off'} | ||
| aria-atomic={props.switchDisclosure?.live !== 'off'} | ||
| aria-busy={rowIsBusy || undefined} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why is this aria-live/atomic/busy part not needed in the renderRowWithDoubleInteraction case?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it's needed as well, i'll added it
src/list.tsx
Outdated
| </Box> | ||
| )} | ||
| /> | ||
| {showSpinner && ( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is this spinner in the spec?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I recall the spinner was part of other conversation rather than expand/collapse states.
Telefonica/mistica-design#2151
The row is also using a switch but not necessarily expands or collapses content. If we want to cover coth cases i can update specs.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
you’re right, the spinner is not part of the task, it comes from the original call-forwarding loading states (CHANGING / LENGTHY), which is a separate matter.
I’ll remove showSpinner and the built–in spinner rendering from Row
…witch-announces-when-it-has-expanded-content
dev playroom |

ticket: https://jira.tid.es/browse/WEB-2283
Cambios
Añadir una “capa de accesibilidad para patrón acordeón con switch”:
Añadir estado de carga integrado en Row:
Y todo ello solo se activa si pasas controlDisclosure en ControlProps. Si no lo pasas, una Row con switch / checkbox / radio se sigue comportando como siempre.
Cambios que se deben hacer en webapp:
En pages/call-forwarding/ui/components/first-level-setting.tsx sustituir el SwitchRow por:
En pages/call-forwarding/ui/steps/main.tsx sustituir el SwitchRow por:
Probado con voiceOver activado y en la brand de movistar:
test-local-expanded-rows.MP4
playroom