Skip to content

fix(a11y): auto-generate id/name em Input+Textarea + componente LabeledField#435

Merged
adm01-debug merged 1 commit into
mainfrom
fix/a11y-form-id-label
May 26, 2026
Merged

fix(a11y): auto-generate id/name em Input+Textarea + componente LabeledField#435
adm01-debug merged 1 commit into
mainfrom
fix/a11y-form-id-label

Conversation

@adm01-debug
Copy link
Copy Markdown
Owner

@adm01-debug adm01-debug commented May 26, 2026

🐛 Bugs (DevTools → Aba Problemas)

Bug 1 — 19 resources: A form field element should have an id or name attribute

Campos <Input> e <Textarea> sem id ou name → o navegador não consegue fazer autocomplete corretamente e ferramentas de acessibilidade não identificam o campo.

Bug 2 — 6 resources: No label associated with a form field

<Label> sem htmlFor apontando para um campo existente → leitores de tela não conseguem associar o rótulo ao input.


✅ Correção

Bug 1 — Primitivos com useId() fallback (zero mudanças nos call sites)

input.tsx e textarea.tsx agora resolvem id e name automaticamente:

id   → prop explícita → derivado de `name` → React.useId() estável
name → prop explícita → mesmo valor do id resolvido

Exemplos de comportamento:

Props passadas id resolvido name resolvido
id="x" name="y" "x" "y"
name="email" "email" "email"
id="my-field" "my-field" "my-field"
(nenhum) :r0: (useId) :r0: (useId)

Zero breaking changes — callers que já passam id/name não são afetados.

Bug 2 — Novo LabeledField.tsx (fix arquitetural)

Componente compound que auto-vincula <Label htmlFor> ao <Input id> via useId() compartilhado.

// ANTES (Label desconectado)
<Label>Email</Label>
<Input type="email" />

// DEPOIS (Label vinculado)
<LabeledField
  label="Email"
  name="email"
  type="email"
  required
  error={errors.email?.message}
/>

Também exporta LabeledTextarea para textareas.

As 6 labels desconectadas devem ser migradas para <LabeledField> nos call sites.


Arquivos alterados

Arquivo Tipo O que muda
src/components/ui/input.tsx Modificado useId() fallback para id e name
src/components/ui/textarea.tsx Modificado Mesmo padrão do input
src/components/ui/LabeledField.tsx Novo Compound LabeledField + LabeledTextarea

Como testar

Após deploy, abrir DevTools → Aba Problemas no dashboard:

  • Bug 1: contador de "form field should have id or name" deve zerar (era 19)
  • Bug 2: contador de "label not associated" deve zerar após migrar os 6 call sites para <LabeledField>

Summary by cubic

Auto‑gera id e name nos Input e Textarea e adiciona LabeledField/LabeledTextarea para vincular Label ao campo. Resolve os avisos de acessibilidade no DevTools sobre campos sem id/name e labels sem associação.

  • Bug Fixes

    • Input/Textarea: resolvem id/name automaticamente (id: prop → derivado de name → React.useId(); name: prop → igual ao id). Sem breaking changes.
    • LabeledField.tsx: novo componente que liga Label htmlFor ao id do campo com useId compartilhado; expõe LabeledTextarea e suporta description/error via aria-describedby.
  • Migration

    • Migrar os 6 usos de Label + Input/Textarea não vinculados para LabeledField/LabeledTextarea.
    • Verificar DevTools → Problemas: avisos de “form field should have id or name” e “no label associated” devem zerar após a migração.

Written for commit 7f64a58. Summary will update on new commits. Review in cubic

…eld compound component

Fixes two DevTools accessibility warnings:
  1. 'A form field element should have an id or name attribute' (19 resources)
  2. 'No label associated with a form field' (6 resources)

Changes:
- input.tsx: auto-resolves id from name, or falls back to React.useId()
  also ensures name attribute is always present (derived from id if not supplied)
- textarea.tsx: same pattern as input.tsx
- LabeledField.tsx: new compound component that auto-links <Label htmlFor>
  to <Input id> using a shared React.useId(). Canonical fix for bug #2.
  Also exports LabeledTextarea for textarea fields.

Bug #2 (6 labels without associated field) is solved architecturally via
LabeledField. Existing call sites using bare <Label> + <Input> without
coordinated htmlFor/id should be migrated to <LabeledField label='...'>.
Bug #1 (19 inputs/textareas without id/name) is fixed at the primitive level."
Copilot AI review requested due to automatic review settings May 26, 2026 11:08
@vercel
Copy link
Copy Markdown

vercel Bot commented May 26, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
we-dream-big Ready Ready Preview, Comment May 26, 2026 11:09am

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 26, 2026

Warning

Review limit reached

@adm01-debug, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 36 minutes and 55 seconds. Learn how PR review limits work.

Your organization has run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: dfcdd3d1-42b3-44e5-b2e4-074f9b5aa8ca

📥 Commits

Reviewing files that changed from the base of the PR and between 37ba489 and 7f64a58.

📒 Files selected for processing (3)
  • src/components/ui/LabeledField.tsx
  • src/components/ui/input.tsx
  • src/components/ui/textarea.tsx
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/a11y-form-id-label

Comment @coderabbitai help to get the list of available commands and usage tips.

@supabase
Copy link
Copy Markdown

supabase Bot commented May 26, 2026

This pull request has been ignored for the connected project doufsxqlfjyuvxuezpln because there are no changes detected in supabase directory. You can change this behaviour in Project Integrations Settings ↗︎.


Preview Branches by Supabase.
Learn more about Supabase Branching ↗︎.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 7f64a58ec8

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

name={name}
aria-describedby={ariaDescribedBy}
aria-invalid={!!error}
aria-required={required}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Forward required to the underlying form control

The component consumes required for label styling but never passes the native required attribute to <Input> (and the textarea variant follows the same pattern), so <LabeledField required> does not trigger built-in browser constraint validation. aria-required only exposes semantics to assistive tech and does not enforce submission rules, which can let empty required fields through unless every caller adds separate validation.

Useful? React with 👍 / 👎.

Comment on lines +86 to +87
const ariaDescribedBy =
[descriptionId, errorId].filter(Boolean).join(' ') || undefined;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Build aria-describedby only from rendered helper text

When both description and error are set, aria-describedby includes both IDs, but the description element is not rendered (description && !error). That produces a broken ARIA ID reference at runtime and can trigger accessibility audit failures or inconsistent announcements in screen readers; the described-by list should match the elements actually present in the DOM.

Useful? React with 👍 / 👎.

) => {
const autoId = React.useId();
// Prefer name-based id for readability in DevTools; fall back to autoId.
const fieldId = name ? `field-${name}` : autoId;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Generate unique ids even when fields share the same name

Deriving fieldId directly from name makes every instance with the same name produce the same DOM id (e.g. repeated sections or same logical field in multiple forms). Duplicate ids break label targeting because htmlFor resolves to the first matching element, so later labels can focus/announce the wrong control; include a per-instance unique suffix or allow caller-provided ids to avoid collisions.

Useful? React with 👍 / 👎.

({ className, type, id, name, ...props }, ref) => {
const fallbackId = React.useId();
// Resolve id: explicit > derived from name > unique fallback
const resolvedId = id ?? name ?? fallbackId;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Avoid reusing name as fallback id in base input primitives

Using name as the default id means controls that intentionally share a name (most notably radio groups) will also share the same id when callers omit id. This creates duplicate ids and breaks explicit <label for=...> associations, since multiple options in one group need the same name but distinct ids.

Useful? React with 👍 / 👎.

Comment on lines +58 to +59
export type LabeledFieldProps = LabeledFieldBaseProps &
Omit<React.ComponentProps<'input'>, 'id'>;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Permit explicit id in LabeledField prop types

The prop types explicitly remove id (Omit<..., 'id'>), so callers cannot provide a deterministic unique id through normal typing even when they need to prevent collisions (for example repeated fields with the same name). This blocks a practical escape hatch and contradicts the component comment that the id is auto-generated only when not supplied.

Useful? React with 👍 / 👎.

({ className, ...props }, ref) => {
({ className, id, name, ...props }, ref) => {
const fallbackId = React.useId();
const resolvedId = id ?? name ?? fallbackId;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Avoid reusing name as fallback id in textarea primitive

Textarea now mirrors the input fallback id ?? name ?? useId, which causes duplicate DOM ids whenever multiple textareas intentionally share the same name (e.g. repeated groups or array-style fields). Duplicate ids break label targeting and ARIA references, so id generation should remain unique per element even when names repeat.

Useful? React with 👍 / 👎.

@adm01-debug adm01-debug merged commit df83f02 into main May 26, 2026
7 checks passed
@adm01-debug adm01-debug deleted the fix/a11y-form-id-label branch May 26, 2026 11:32
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Note

Copilot was unable to run its full agentic suite in this review.

Improves form-field accessibility by guaranteeing stable id/name attributes for base inputs and introducing a compound “label + field” component that links labels/descriptions/errors via shared IDs.

Changes:

  • Auto-resolve id/name for Input and Textarea using prop → name → React.useId() fallback.
  • Add LabeledField / LabeledTextarea compound components that bind <Label htmlFor> to the underlying field and wire up aria-* attributes.
  • Add documentation comments describing accessibility rules and resolution order.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 8 comments.

File Description
src/components/ui/textarea.tsx Auto-generates/derives id and name for <textarea> to satisfy a11y/devtools requirements.
src/components/ui/input.tsx Auto-generates/derives id and name for <input> with documented resolution rules.
src/components/ui/LabeledField.tsx New compound components that connect labels, descriptions, and errors to inputs/textareas via shared IDs.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +103 to +112
<Input
ref={ref}
id={fieldId}
name={name}
aria-describedby={ariaDescribedBy}
aria-invalid={!!error}
aria-required={required}
className={className}
{...inputProps}
/>
Comment on lines +180 to +189
<Textarea
ref={ref}
id={fieldId}
name={name}
aria-describedby={ariaDescribedBy}
aria-invalid={!!error}
aria-required={required}
className={className}
{...textareaProps}
/>
Comment on lines +83 to +87
const descriptionId = description ? `${fieldId}-description` : undefined;
const errorId = error ? `${fieldId}-error` : undefined;

const ariaDescribedBy =
[descriptionId, errorId].filter(Boolean).join(' ') || undefined;
Comment on lines +114 to +124
{description && !error && (
<p id={descriptionId} className="text-xs text-muted-foreground">
{description}
</p>
)}

{error && (
<p id={errorId} className="text-xs font-medium text-destructive" role="alert">
{error}
</p>
)}
Comment on lines +103 to +112
<Input
ref={ref}
id={fieldId}
name={name}
aria-describedby={ariaDescribedBy}
aria-invalid={!!error}
aria-required={required}
className={className}
{...inputProps}
/>
Comment on lines +180 to +189
<Textarea
ref={ref}
id={fieldId}
name={name}
aria-describedby={ariaDescribedBy}
aria-invalid={!!error}
aria-required={required}
className={className}
{...textareaProps}
/>
* Renders a <Label> + <Input> pair with a shared `id` so the browser
* correctly associates them. The id is auto-generated when not supplied.
*/
export const LabeledField = React.forwardRef<HTMLInputElement, LabeledFieldProps>(
/**
* Renders a <Label> + <Textarea> pair with a shared `id`.
*/
export const LabeledTextarea = React.forwardRef<
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