Skip to content

Form labels lose their association after hydration in production builds #16

Description

@TerrorSquad

Impact

Three visible form fields have no accessible name in production. Screen reader users get bare textboxes on two of the app's main write paths.

Page Field
/notes/new Title
/notes/new Body
/admin invite email

Root cause

Vue's useId() diverges between server and client. UFormField does const id = ref(useId()) and binds <Label :for="id">, passing the same id to the child input. After hydration the input re-generates its id while the label keeps the server value:

label for input id
SSR v-0-12-0 v-0-12-0
after hydration v-0-12-0 v-0-0-0

document.getElementById(label.htmlFor) returns null for all three.

/account is unaffected. Its ids are flat (v-0-7, v-0-8, …) while the broken ones are nested (v-0-12-0), which means useId() ran inside an async boundary on the broken pages and not on /account.

Only in production

Dev builds are fine. This is why pnpm test:e2e passes locally (dev server) while CI fails (production build). It also went unnoticed for weeks because the scheduled e2e workflow was blocked by an Actions budget.

Reproduce locally against the production build:

pnpm build
SCREENSHOTS=1 pnpm exec playwright test --project=app

Tried and rejected

Adding { lazy: true } to useNotes()'s useAsyncData on the theory that it created the async boundary. Rebuilt and retested: ids were still nested and still broken. Not the cause.

Options

  1. Interim: explicit aria-label on the three fields. Restores the accessible name and unblocks CI. A workaround, not a cure, and it will not help the next form someone adds.
  2. Real fix: find why useId() is unstable on these pages. UFormField exposes no id prop, so it cannot be overridden from the call site. Likely worth an upstream issue against Nuxt UI or Vue.

Any page combining a setup-time fetch with UFormField is exposed, so this is not limited to the three fields above.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions