From 7c397306164d6fae0e85435e5098f4bed56b42e8 Mon Sep 17 00:00:00 2001
From: Linus Rath <139418639+rathlinus@users.noreply.github.com>
Date: Sat, 31 Oct 2026 19:58:47 +0100
Subject: [PATCH] fix: name the recipient on the composer chip remove button
---
.../compose/RecipientSuggestionList.tsx | 3 ++-
src/lib/__tests__/recipient-chip-a11y.test.ts | 27 +++++++++++++++++++
src/lib/recipient-chip-a11y.ts | 16 +++++++++++
3 files changed, 45 insertions(+), 1 deletion(-)
create mode 100644 src/lib/__tests__/recipient-chip-a11y.test.ts
create mode 100644 src/lib/recipient-chip-a11y.ts
diff --git a/src/components/compose/RecipientSuggestionList.tsx b/src/components/compose/RecipientSuggestionList.tsx
index 972fc93b..5d0931dc 100644
--- a/src/components/compose/RecipientSuggestionList.tsx
+++ b/src/components/compose/RecipientSuggestionList.tsx
@@ -6,6 +6,7 @@ import { radius, spacing, typography, type ThemePalette } from '../../theme/toke
import { useLocaleStore } from '../../stores/locale-store';
import { getEmailInitials } from '../../lib/avatar-utils';
import { SUGGESTION_ROW_HEIGHT } from '../../lib/recipient-picker';
+import { recipientRemoveAccessibilityLabel } from '../../lib/recipient-chip-a11y';
import type { RecipientSuggestion } from '../../stores/contacts-store';
/** A recipient chip's data. Group chips carry their resolved members and no email. */
@@ -43,7 +44,7 @@ export function RecipientChip({
// A 12 px icon: the touch area reaches about 40 dp around it.
hitSlop={14}
accessibilityRole="button"
- accessibilityLabel={t('email_composer.recipient_remove', 'Remove')}
+ accessibilityLabel={recipientRemoveAccessibilityLabel(recipient, t)}
>
diff --git a/src/lib/__tests__/recipient-chip-a11y.test.ts b/src/lib/__tests__/recipient-chip-a11y.test.ts
new file mode 100644
index 00000000..8c3678f6
--- /dev/null
+++ b/src/lib/__tests__/recipient-chip-a11y.test.ts
@@ -0,0 +1,27 @@
+// The cross on a composer recipient chip said only "Remove".
+import { describe, expect, it, vi } from 'vitest';
+import fs from 'node:fs';
+import path from 'node:path';
+
+vi.mock('expo-localization', () => ({ getLocales: () => [] }));
+
+import { translate } from '../../i18n';
+import { recipientRemoveAccessibilityLabel } from '../recipient-chip-a11y';
+
+const t = (key: string, fallback?: string, params?: Record) =>
+ translate('en', key, fallback ?? key, params);
+
+describe('recipient chip remove label', () => {
+ it('names the recipient', () => {
+ expect(recipientRemoveAccessibilityLabel({ name: 'Anna Berg', email: 'anna@example.org' }, t)).toBe('Remove Anna Berg');
+ });
+
+ it('falls back to the address when the chip has no name', () => {
+ expect(recipientRemoveAccessibilityLabel({ name: ' ', email: 'anna@example.org' }, t)).toBe('Remove anna@example.org');
+ });
+
+ it('is what the chip cross uses', () => {
+ const source = fs.readFileSync(path.resolve(__dirname, '../../components/compose/RecipientSuggestionList.tsx'), 'utf8');
+ expect(source).toMatch(/accessibilityLabel=\{recipientRemoveAccessibilityLabel\(/);
+ });
+});
diff --git a/src/lib/recipient-chip-a11y.ts b/src/lib/recipient-chip-a11y.ts
new file mode 100644
index 00000000..266a94b4
--- /dev/null
+++ b/src/lib/recipient-chip-a11y.ts
@@ -0,0 +1,16 @@
+// What a screen reader says for the cross on a To/Cc/Bcc chip. It said only
+// "Remove", so with several chips nobody could tell which one a cross took
+// away. The webmail names the recipient (email-composer.tsx,
+// recipient_remove_named), as the event editor here already does.
+
+type Translate = (key: string, fallback?: string, params?: Record) => string;
+
+/** "Remove Anna Berg", or "Remove anna@example.org" when the chip has no name. */
+export function recipientRemoveAccessibilityLabel(
+ recipient: { name?: string; email: string },
+ t: Translate,
+): string {
+ const name = recipient.name?.trim() || recipient.email.trim();
+ if (!name) return t('email_composer.recipient_remove', 'Remove');
+ return t('email_composer.recipient_remove_named', 'Remove {name}', { name });
+}