Skip to content

fix: aria-label nos dois X de SplitSection.tsx (closes #129) - #145

Draft
Guiroos wants to merge 1 commit into
mainfrom
claude/issue-129-split-section-x-buttons
Draft

fix: aria-label nos dois X de SplitSection.tsx (closes #129)#145
Guiroos wants to merge 1 commit into
mainfrom
claude/issue-129-split-section-x-buttons

Conversation

@Guiroos

@Guiroos Guiroos commented Sep 3, 2026

Copy link
Copy Markdown
Owner

O que mudou

components/forms/transaction/SplitSection.tsx tem dois <Button size="icon" variant="ghost"> cujo único filho é um <X> do lucide-react, sem aria-label, aria-labelledby, title ou sr-only:

  • linha 146-155 (após a mudança) — fecha a divisão de despesa inteira (onClick={handleClose})
  • linha 185-193 — remove uma pessoa da divisão (onClick={() => removePerson(entry.uid)})

lucide-react está pinado em 1.8.0 (package.json), cujo Icon marca o <svg> como aria-hidden="true" quando não recebe filho nem prop de a11y — o role=button chega ao leitor de tela sem nenhuma fonte de nome (WCAG 4.1.2 Name, Role, Value, nível A). Os dois botões são visualmente e semanticamente idênticos (mesmo ícone, mesmo variant/size), o que torna a ambiguidade especialmente perigosa: handleClose descarta todas as linhas já preenchidas, enquanto removePerson afeta só uma linha.

   <Button type="button" variant="ghost" size="icon" onClick={handleClose}
+    aria-label="Fechar divisão"
     className="text-text-tertiary hover:text-text-primary">
     <X className="h-4 w-4" />
   </Button>

   <Button type="button" variant="ghost" size="icon" onClick={() => removePerson(entry.uid)}
+    aria-label="Remover pessoa da divisão"
     className="mb-0.5 flex-shrink-0 text-text-tertiary hover:text-negative">
     <X className="h-4 w-4" />
   </Button>

Mudança puramente aditiva, sem alteração visual ou de comportamento.

Por que dessa forma

Segui a proposta da issue #129 à risca:

  • Rotular o botão, não o ícone. <X aria-label="..."> também desligaria o aria-hidden (via hasA11yProp do lucide), mas poria o nome no <svg> em vez do controle clicável — divergindo dos outros 20 controles do repo que rotulam o <Button>.
  • Rótulo estático, não interpolado com o nome da pessoa. personId pode estar vazio (linha recém-adicionada) e o nome vem do Combobox — montar a string dinamicamente acoplaria o rótulo ao estado de seleção sem ganho real, e um nome vazio produziria texto truncado.

Como testei

  • npm ci && npm run lint && npm run format:check && npm run typecheck && npm test && npm run build — todos verdes (35 arquivos, 529 testes).
  • Agente ds-reviewer rodado sobre o diff: aprovado sem violações — a mudança é só a prop aria-label, ortogonal às 6 regras do DS.
  • npm run test:integration não foi executado (exige credenciais Neon e só roda em push para main) — não se aplica, a mudança não toca camada de dados.

Novo arquivo __tests__/unit/a11y-nomes-acessiveis.test.ts (não existia em main no momento deste PR — issues irmãs como #126/#127/#128 também o criam em paralelo; conflito esperado, resolvido somando os describe blocks). A asserção não usa toMatch(/aria-label/) genérico: SplitSection.tsx tem 5 <Button> no total e os dois sites em risco usam o mesmo ícone <X>, então o gate precisa (1) achar exatamente os dois <Button> que envolvem um <X>, (2) exigir aria-label em ambos, e (3) exigir rótulos distintos — o defeito original é a ambiguidade entre os dois estados, não a ausência isolada de nome.

Risco e o que NÃO foi coberto

Risco baixo: mudança aditiva de um atributo ARIA, sem impacto em lógica, estilo ou dados. Não cobre os outros 7 sites do achado original (#106) — issues próprias: #126 (DeleteButton do DS), #127 e #128 (lápis de editar).

Conflito de arquivo esperado. __tests__/unit/a11y-nomes-acessiveis.test.ts está sendo criado do zero em paralelo por #126, #127, #128 e esta PR. Quem mergear por último resolve somando os describe blocks, não descartando um lado.

Arquivos tocados

  • components/forms/transaction/SplitSection.tsx
  • __tests__/unit/a11y-nomes-acessiveis.test.ts (novo)

🤖 Generated with Claude Code

https://claude.ai/code/session_01L2ttiXUNPukdqzYnnmuzw7


Generated by Claude Code

Os botões de fechar divisão e remover pessoa usam o mesmo ícone X do
lucide-react sem nome acessível — role=button sem fonte de nome, já que
lucide-react@1.8.0 marca o svg como aria-hidden quando não recebe filho
nem prop de a11y. Rótulos distintos para os dois estados.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L2ttiXUNPukdqzYnnmuzw7
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants