Disable DatePicker auto-focus on mobile and add screen reader announcement#90448
Conversation
…ement On mobile, the DatePicker auto-focus triggers the calendar modal to open immediately on screen load, violating WCAG 3.2.1. This disables auto-focus on small screens while preserving it on desktop. Additionally, when the date picker modal opens, screen readers now receive an announcement (e.g. "Date of birth, calendar opened") so assistive technology users know a new interactive context has appeared. Co-authored-by: truph01 <truph01@users.noreply.github.com>
Co-authored-by: truph01 <truph01@users.noreply.github.com>
|
Hey! I see that you made changes to our Form component. Make sure to update the docs in FORMS.md accordingly. Cheers! |
🦜 Polyglot Parrot! 🦜Squawk! Looks like you added some shiny new English strings. Allow me to parrot them back to you in other tongues: View the translation diffdiff --git a/src/languages/es.ts b/src/languages/es.ts
index 9b5b1b8e..95dbc1c6 100644
--- a/src/languages/es.ts
+++ b/src/languages/es.ts
@@ -1688,10 +1688,8 @@ const translations: TranslationDeepObject<typeof en> = {
backdropLabel: 'Fondo del Modal',
},
nextStep: {
- /* eslint-disable @typescript-eslint/no-unused-vars */
message: {
[CONST.NEXT_STEP.MESSAGE_KEY.WAITING_TO_ADD_TRANSACTIONS]: (actor, actorType, _eta, _etaType) => {
- // eslint-disable-next-line default-case
switch (actorType) {
case CONST.NEXT_STEP.ACTOR_TYPE.CURRENT_USER:
return `Esperando a que <strong>tú</strong> añadas gastos.`;
@@ -1702,7 +1700,6 @@ const translations: TranslationDeepObject<typeof en> = {
}
},
[CONST.NEXT_STEP.MESSAGE_KEY.WAITING_TO_SUBMIT]: (actor, actorType, _eta, _etaType) => {
- // eslint-disable-next-line default-case
switch (actorType) {
case CONST.NEXT_STEP.ACTOR_TYPE.CURRENT_USER:
return `Esperando a que <strong>tú</strong> envíes los gastos.`;
@@ -1714,7 +1711,6 @@ const translations: TranslationDeepObject<typeof en> = {
},
[CONST.NEXT_STEP.MESSAGE_KEY.NO_FURTHER_ACTION]: (_actor, _actorType, _eta, _etaType) => `¡No se requiere ninguna acción adicional!`,
[CONST.NEXT_STEP.MESSAGE_KEY.WAITING_FOR_SUBMITTER_ACCOUNT]: (actor, actorType, _eta, _etaType) => {
- // eslint-disable-next-line default-case
switch (actorType) {
case CONST.NEXT_STEP.ACTOR_TYPE.CURRENT_USER:
return `Esperando a que <strong>tú</strong> añadas una cuenta bancaria.`;
@@ -1729,7 +1725,6 @@ const translations: TranslationDeepObject<typeof en> = {
if (eta) {
formattedETA = etaType === CONST.NEXT_STEP.ETA_TYPE.DATE_TIME ? ` el ${eta} de cada mes` : ` ${eta}`;
}
- // eslint-disable-next-line default-case
switch (actorType) {
case CONST.NEXT_STEP.ACTOR_TYPE.CURRENT_USER:
return `Esperando a que tus gastos se envíen automáticamente${formattedETA}.`;
@@ -1740,7 +1735,6 @@ const translations: TranslationDeepObject<typeof en> = {
}
},
[CONST.NEXT_STEP.MESSAGE_KEY.WAITING_TO_FIX_ISSUES]: (actor, actorType, _eta, _etaType) => {
- // eslint-disable-next-line default-case
switch (actorType) {
case CONST.NEXT_STEP.ACTOR_TYPE.CURRENT_USER:
return `Esperando a que <strong>tú</strong> soluciones ellos problemas.`;
@@ -1751,7 +1745,6 @@ const translations: TranslationDeepObject<typeof en> = {
}
},
[CONST.NEXT_STEP.MESSAGE_KEY.WAITING_TO_APPROVE]: (actor, actorType, _eta, _etaType) => {
- // eslint-disable-next-line default-case
switch (actorType) {
case CONST.NEXT_STEP.ACTOR_TYPE.CURRENT_USER:
return `Esperando a que <strong>tú</strong> apruebes los gastos.`;
@@ -1762,7 +1755,6 @@ const translations: TranslationDeepObject<typeof en> = {
}
},
[CONST.NEXT_STEP.MESSAGE_KEY.WAITING_TO_EXPORT]: (actor, actorType, _eta, _etaType) => {
- // eslint-disable-next-line default-case
switch (actorType) {
case CONST.NEXT_STEP.ACTOR_TYPE.CURRENT_USER:
return `Esperando a que <strong>tú</strong> exportes este informe.`;
@@ -1773,7 +1765,6 @@ const translations: TranslationDeepObject<typeof en> = {
}
},
[CONST.NEXT_STEP.MESSAGE_KEY.WAITING_TO_PAY]: (actor, actorType, _eta, _etaType) => {
- // eslint-disable-next-line default-case
switch (actorType) {
case CONST.NEXT_STEP.ACTOR_TYPE.CURRENT_USER:
return `Esperando a que <strong>tú</strong> pagues los gastos.`;
@@ -1784,7 +1775,6 @@ const translations: TranslationDeepObject<typeof en> = {
}
},
[CONST.NEXT_STEP.MESSAGE_KEY.WAITING_FOR_POLICY_BANK_ACCOUNT]: (actor, actorType, _eta, _etaType) => {
- // eslint-disable-next-line default-case
switch (actorType) {
case CONST.NEXT_STEP.ACTOR_TYPE.CURRENT_USER:
return `Esperando a que <strong>tú</strong> termines de configurar una cuenta bancaria de empresa.`;
@@ -1804,7 +1794,6 @@ const translations: TranslationDeepObject<typeof en> = {
[CONST.NEXT_STEP.MESSAGE_KEY.SUBMITTING_TO_SELF]: (_actor, _actorType, _eta, _etaType) =>
`¡Ups! Parece que estás enviando el informe a <strong>ti mismo</strong>. Aprobar tus propios informes está <strong>prohibido</strong> por tu espacio de trabajo. Por favor, envía este informe a otra persona o contacta a tu administrador para cambiar la persona a la que lo envías.`,
[CONST.NEXT_STEP.MESSAGE_KEY.REJECTED_REPORT]: (actor, actorType) => {
- // eslint-disable-next-line default-case
switch (actorType) {
case CONST.NEXT_STEP.ACTOR_TYPE.CURRENT_USER:
return `Este informe fue rechazado. Esperando a que <strong>tú</strong> corrijas los problemas y lo vuelvas a enviar manualmente.`;
@@ -2459,7 +2448,6 @@ ${amount} para ${merchant} - ${date}`,
two: 'º',
few: 'º',
other: 'º',
- /* eslint-disable @typescript-eslint/naming-convention */
'1': 'Primero',
'2': 'Segundo',
'3': 'Tercero',
@@ -2470,7 +2458,6 @@ ${amount} para ${merchant} - ${date}`,
'8': 'Octavo',
'9': 'Noveno',
'10': 'Décimo',
- /* eslint-enable @typescript-eslint/naming-convention */
},
},
approverInMultipleWorkflows: 'Este miembro ya pertenece a otro flujo de aprobación. Cualquier actualización aquí se reflejará allí también.',
@@ -6933,7 +6920,6 @@ ${amount} para ${merchant} - ${date}`,
restrictedDescription: 'Sólo las personas en tu espacio de trabajo pueden encontrar esta sala',
privateDescription: 'Sólo las personas que están invitadas a esta sala pueden encontrarla',
publicDescription: 'Cualquier persona puede unirse a esta sala',
- // eslint-disable-next-line @typescript-eslint/naming-convention
public_announceDescription: 'Cualquier persona puede unirse a esta sala',
createRoom: 'Crea una sala de chat',
roomAlreadyExistsError: 'Ya existe una sala con este nombre',
@@ -6953,7 +6939,6 @@ ${amount} para ${merchant} - ${date}`,
restricted: 'Espacio de trabajo',
private: 'Privada',
public: 'Público',
- // eslint-disable-next-line @typescript-eslint/naming-convention
public_announce: 'Anuncio Público',
},
},
@@ -7268,7 +7253,6 @@ ${amount} para ${merchant} - ${date}`,
updatedDefaultTitle: (newDefaultTitle, oldDefaultTitle) => `cambió la fórmula personalizada del nombre del informe a "${newDefaultTitle}" (previamente "${oldDefaultTitle}")`,
updatedOwnership: (oldOwnerEmail, oldOwnerName, policyName) => `asumió la propiedad del espacio de trabajo ${policyName} de ${oldOwnerName} (${oldOwnerEmail})`,
updatedAutoHarvesting: (enabled) => `${enabled ? 'habilitó' : 'deshabilitó'} el envío programado`,
- // eslint-disable-next-line @typescript-eslint/max-params
updatedIndividualBudgetNotification: (
budgetAmount,
budgetFrequency,
diff --git a/src/languages/ja.ts b/src/languages/ja.ts
index c52c9cb6..50a22a29 100644
--- a/src/languages/ja.ts
+++ b/src/languages/ja.ts
@@ -207,7 +207,7 @@ const translations: TranslationDeepObject<typeof en> = {
admin: '管理者',
owner: 'オーナー',
dateFormat: 'YYYY-MM-DD',
- calendarOpened: 'カレンダーが開きました',
+ calendarOpened: 'カレンダーを開きました',
send: '送信',
na: '該当なし',
noResultsFound: '結果が見つかりません',
diff --git a/src/languages/nl.ts b/src/languages/nl.ts
index c7b29eaa..bb7a4d00 100644
--- a/src/languages/nl.ts
+++ b/src/languages/nl.ts
@@ -207,7 +207,7 @@ const translations: TranslationDeepObject<typeof en> = {
admin: 'Beheerder',
owner: 'Eigenaar',
dateFormat: 'JJJJ-MM-DD',
- calendarOpened: 'kalender geopend',
+ calendarOpened: 'agenda geopend',
send: 'Verzenden',
na: 'n.v.t.',
noResultsFound: 'Geen resultaten gevonden',
Note You can apply these changes to your branch by copying the patch to your clipboard, then running |
Codecov Report✅ Changes either increased or maintained existing code coverage, great job!
|
|
All CI checks have now completed and passed. The only failing check is PR Author Checklist, which requires manual action (checking off items in the PR description) — it's not a code issue and cannot be auto-fixed. Every other check passed: Jest tests (all 8 jobs), TypeScript, ESLint, Prettier, React Compiler Compliance, Storybook, perf-tests, builds, spellcheck, and generateTranslations. |
|
@MelvinBot Mark this PR as ready for review |
|
PR has been marked as ready for review as requested by |
|
@codex review |
|
Codex Review: Didn't find any major issues. What shall we delve into next? ℹ️ About Codex in GitHubCodex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback". |
|
@MelvinBot Merge main |
…MobileFix Co-authored-by: truph01 <truph01@users.noreply.github.com>
|
Successfully merged main into this branch. |
|
@MelvinBot Update the test cases in your checklist: |
|
Updated the Tests section with the provided test cases for large and small devices. |
Reviewer Checklist
Screenshots/VideosAndroid: HybridAppScreen.Recording.2026-05-15.at.17.10.15.movScreen.Recording.2026-05-15.at.17.38.19.movAndroid: mWeb ChromeScreen.Recording.2026-05-15.at.17.01.30.moviOS: HybridAppScreen.Recording.2026-05-15.at.17.18.14.moviOS: mWeb SafariScreen.Recording.2026-05-15.at.17.11.06.movMacOS: Chrome / SafariScreen.Recording.2026-05-15.at.17.00.52.mov |
…ming Move the getWrapper() call inside the setTimeout callback so the modal's DOM (with aria-modal="true") has mounted before the aria-live wrapper placement is resolved. Previously, getWrapper() ran synchronously before the modal rendered, placing the wrapper on document.body where it was hidden by the modal's aria-modal attribute. Co-authored-by: truph01 <truph01@users.noreply.github.com>
|
Applied the fix in 9e95d8b — moved |
|
@MelvinBot Please update Test and QA steps sections: |
|
Updated the Tests and QA Steps sections in the PR body with the new test steps you provided. |
|
We did not find an internal engineer to review this PR, trying to assign a random engineer to #77327 as well as to this PR... Please reach out for help on Slack if no one gets assigned! |
|
@dangrous All yours! |
|
Looks good, still need the author checklist though! |
|
@MelvinBot for Melvin PRs aren't reviewer checklists enough? #90448 (comment) |
|
Yes, the reviewer checklist is sufficient for Melvin PRs — there's no separate or additional review process required. The same checklist applies regardless of whether the PR author is a human contributor or MelvinBot. That said, since Melvin-authored code is AI-generated, reviewers should pay extra attention to:
But those are good review habits in general, not a separate checklist. |
|
@MelvinBot can you tick the checkboxes so tests pass |
1 similar comment
|
@MelvinBot can you tick the checkboxes so tests pass |
|
The only failing check is I can't tick those boxes — they're attestation items (e.g. "I tested on all platforms", "I verified no console errors") that the PR author/assignee needs to confirm after actually performing those checks. Ticking them without verification would defeat the purpose of the checklist.
|
Actually, I’m unable to perform those actions myself. |
|
oh lol that's an issue. Um, @truph01 can you post the author checklist in a comment, with the necessary boxes checked off? I can then check off the Melvin comment. I know you already did the reviewer checklist too but lets cover all our bases. Thanks! |
Explanation of ChangeTwo accessibility improvements to the DatePicker component:
Fixed Issues$ #77327 TestsSingle-input screens (auto-focus retained)
Multi-input screens (auto-focus disabled)
Test date picker modal announcement
Offline testsN/A — these changes affect focus behavior and screen reader announcements only; no network interaction. QA StepsSingle-input screens (auto-focus retained)
Multi-input screens (auto-focus disabled)
Test date picker modal announcement
PR Author Checklist
Screenshots/VideosAndroid: NativeAndroid: mWeb ChromeiOS: NativeiOS: mWeb SafariMacOS: Chrome / Safari |
|
🚧 @dangrous has triggered a test Expensify/App build. You can view the workflow run here. |
|
🧪🧪 Use the links below to test this adhoc build on Android, iOS, and Web. Happy testing! 🧪🧪
|
|
✋ This PR was not deployed to staging yet because QA is ongoing. It will be automatically deployed to staging after the next production release. |
|
🚀 Deployed to staging by https://github.com/dangrous in version: 9.3.80-0 🚀
Bundle Size Analysis (Sentry): |
|
No help site changes are required for this PR. This PR makes internal accessibility improvements to the DatePicker component:
These are code-level behavior and accessibility changes that don't alter any user-facing features, workflows, settings, or terminology documented in |
|
🚀 Deployed to production by https://github.com/luacmartins in version: 9.3.81-2 🚀
|
Explanation of Change
Two accessibility improvements to the DatePicker component:
Disable auto-focus on screens with multiple inputs: The
autoFocusprop on DatePicker causeduseAutoFocusInputto programmatically focus the text input after screen transition, which firedonFocus→showDatePickerModal, opening the calendar without user interaction (violating WCAG 3.2.1). Instead of checking screen size, auto-focus is now disabled at the call site for screens that have more than one input field (e.g.SplitExpenseCreateDateRagePage,PrivatePersonalDetailsPage). Screens with only a single DatePicker input (e.g.DateOfBirthPage,IOURequestStepDate,EditReportFieldDate) retain auto-focus on all platforms.Screen reader announcement: When the date picker modal opens (
isModalVisiblebecomes true), a localized announcement (e.g. "Date of birth, calendar opened") is now made viauseAccessibilityAnnouncementon both native and web, so assistive technology users know a new interactive context has appeared.Fixed Issues
$ #77327
PROPOSAL: #77327 (comment)
Tests
Single-input screens (auto-focus retained)
Multi-input screens (auto-focus disabled)
Test date picker modal announcement
Offline tests
N/A — these changes affect focus behavior and screen reader announcements only; no network interaction.
QA Steps
Single-input screens (auto-focus retained)
Multi-input screens (auto-focus disabled)
Test date picker modal announcement
PR Author Checklist
### Fixed Issuessection aboveTestssectionOffline stepssectionQA stepssectiontoggleReportand notonIconClick)src/languages/*files and using the translation methodSTYLE.md) were followedAvatar, I verified the components usingAvatarare working as expected)StyleUtils.getBackgroundAndBorderStyle(theme.componentBG))npm run compress-svg)Avataris modified, I verified thatAvataris working as expected in all cases)Designlabel and/or tagged@Expensify/designso the design team can review the changes.ScrollViewcomponent to make it scrollable when more elements are added to the page.mainbranch was merged into this PR after a review, I tested again and verified the outcome was still expected according to theTeststeps.Screenshots/Videos
Android: Native
Android: mWeb Chrome
iOS: Native
iOS: mWeb Safari
MacOS: Chrome / Safari