authorgravatar for 55418374+Leos-Khai@users.noreply.github.comKhai Shern, Toh <55418374+Leos-Khai@users.noreply.github.com> 2026-09-21 05:46:08+08:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-09-20 18:46:08-03:00
log45e06f48a03354b600782259010ae6222241aff0
treebe1cde3cca5f79db5c0caf0aeae44c36264be370
parent2d8f9a8f55d2195ac820c91f0f1e8f66a055f841
signature Signed by PGP key B5690EEEBB952194

fix: composer hero reads project name to screen readers (#12397)


2 files changed, 20 insertions(+), 6 deletions(-)

apps/mobile/src/features/threads/NewTaskDraftScreen.tsx+1-1
...@@ -1455,7 +1455,7 @@ export function NewTaskDraftScreen(props: {...@@ -1455,7 +1455,7 @@ export function NewTaskDraftScreen(props: {
1455 <Text className="text-2xl font-t3-medium tracking-tight text-foreground">in </Text>1455 <Text className="text-2xl font-t3-medium tracking-tight text-foreground">in </Text>
1456 <Pressable1456 <Pressable
1457 accessibilityHint="Opens the project picker"1457 accessibilityHint="Opens the project picker"
1458 accessibilityLabel={`Change project from ${selectedProject.title}`}1458 accessibilityLabel={selectedProject.title}
1459 accessibilityRole="button"1459 accessibilityRole="button"
1460 disabled={isComposerInteractionLocked}1460 disabled={isComposerInteractionLocked}
1461 onPress={chooseProject}1461 onPress={chooseProject}
apps/web/src/components/chat/DraftHeroHeadline.tsx+19-5
...@@ -136,10 +136,11 @@ export function DraftHeroHeadline({...@@ -136,10 +136,11 @@ export function DraftHeroHeadline({
136 <Tooltip>136 <Tooltip>
137 <TooltipTrigger137 <TooltipTrigger
138 render={138 render={
139 <MenuTrigger139 // The trigger's accessible name comes from its visible text (the
140 aria-label={hasResolvedProject ? "Change project" : "Choose a project"}140 // project title) so the hero sentence reads naturally: an
141 className="pointer-events-auto inline-block max-w-64 truncate border-foreground/60 border-b border-dotted align-baseline text-foreground transition-colors hover:border-foreground/80 focus-visible:rounded-sm focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring"141 // aria-label here would replace the title with an action phrase
142 />142 // mid-sentence and baffle screen-reader users.
143 <MenuTrigger className="pointer-events-auto inline-block max-w-64 truncate border-foreground/60 border-b border-dotted align-baseline text-foreground transition-colors hover:border-foreground/80 focus-visible:rounded-sm focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring" />
143 }144 }
144 >145 >
145 {activeProjectDisplayName ?? "Choose a project"}146 {activeProjectDisplayName ?? "Choose a project"}
...@@ -233,8 +234,21 @@ export function DraftHeroHeadline({...@@ -233,8 +234,21 @@ export function DraftHeroHeadline({
233 </button>234 </button>
234 );235 );
235236
237 // The composer hero is a sentence, so the heading's accessible name must be
238 // a complete sentence too. The project picker is a control rendered inline
239 // in the h1; without an explicit label its widget state bleeds into the
240 // announced phrase.
241 const headingLabel = hasResolvedProject
242 ? `What should we build in ${activeProjectDisplayName}?`
243 : canChooseProject
244 ? `${activeProjectDisplayName ?? "Choose a project"} to start`
245 : "Add a project to start";
246
236 return (247 return (
237 <h1 className="mx-auto w-full max-w-5xl text-center font-normal text-2xl text-foreground tracking-tight sm:text-3xl">248 <h1
249 aria-label={headingLabel}
250 className="mx-auto w-full max-w-5xl text-center font-normal text-2xl text-foreground tracking-tight sm:text-3xl"
251 >
238 {hasResolvedProject ? (252 {hasResolvedProject ? (
239 <>What should we build in {projectSelector}?</>253 <>What should we build in {projectSelector}?</>
240 ) : canChooseProject ? (254 ) : canChooseProject ? (