goauthentik / goauthentik/authentik
web: triage and consolidate form input/label patterns
- Dominant language
- Python
- Stars
- 25.6k
- Forks
- 2k
- Avg merge
- 1d 1h
- Merged PRs (30d)
- 644
Description
## Background
Form-label accessibility has steadily improved in the web frontend — most recently #16119 (and the parent #15894 / #15876 / #15701) introduced `AKLabel` + `AKFieldError` so `` and `` finally pair across the same DOM tree. That migration is **partial**. The interior of our forms — the inputs themselves and the layer between them and `` — is a patchwork: a mix of native ``, deprecated wrapper props, ad-hoc `name=` selectors used by e2e tests, and several generations of `ak-*-input` custom elements that each handle labelling slightly differently.
We want to land the rest of this work, but **as several themed PRs**, not one unreviewable behemoth.
## Current state (recon, against `main` ~ c4fa613)
- `web/src/elements/forms/HorizontalFormElement.ts` still exposes `@property label` annotated `@deprecated Labels cannot associate with inputs across DOM roots. Use the slotted label element instead.` — 176 call sites still pass `label="..."` rather than the slotted `` pattern that #16119 set up.
- 149 files reference `` in some form. Migration is well-started but uneven.
- `AKLabel` is a small light-DOM functional component in `web/src/components/ak-label.ts` (32 lines). It sets `aria-required` on the label but does not propagate it to the wrapped input. `AKFieldError` (`web/src/components/ak-field-errors.ts`, 45 lines) does not yet wire `aria-describedby` from the input.
- `web/src/components/HorizontalLightComponent.ts` generates `field-help-${id}` / `field-label-${id}` via `IDGenerator.elementID()`. This is the canonical id-pairing pattern but is only used in ~40 places across the tree.
- 116 files reference the `ak-text-input` / `ak-textarea-input` / `ak-toggle-group` / `ak-search-select` family. These each made independent choices about where the label lives and how errors render. The e2e tests adapt to whichever one is in front of them; tightening label semantics tends to break those locator paths.
- Tests under `tests/e2e/` rely heavily on locators like `name="..."` (40+ occurrences) — selector-of-last-resort because no stable `for/id` or `aria-labelledby` pairing existed at the time. With the AKLabel pairing in place we can finally migrate these to `getByLabel` and similar role-based selectors.
## Goal
Drive the form-input/label surface to a single coherent pattern and let the e2e tests rely on label associations, not name-attribute archaeology.
## Proposed PR slices (initial — refine after triage)
Use this as a guide; **do not** bundle them. Each slice should be reviewable in isolation, ship behind its own backport label as needed, and not depend on a later slice landing.
1. **Sweep the deprecated `label="..."` prop on `ak-form-element-horizontal`** — convert remaining call sites to the slotted `` pattern from #16119. Delete the deprecated property when the count hits zero. Pure mechanical migration; should be the largest and most boring slice.
2. **`AKLabel` / `AKFieldError` follow-through on the input side** — propagate `aria-required` and `aria-invalid` to the wrapped control; wire `aria-describedby` to error and help blocks; ensure required indicator is conveyed by more than just visual styling (`data-required-label` is currently only on the ``).
3. **`ak-*-input` family consistency** — standardise the `(label, name, help, errors)` interface across `ak-text-input`, `ak-textarea-input`, `ak-toggle-group`, `ak-search-select`, etc. Likely a small base class or a shared `withLabel()` mixin. Storybook coverage for each so review can verify visually.
4. **`HorizontalLightComponent` id-generation as the single source of truth** — every input control should derive its `id`, `aria-labelledby`, and `aria-describedby` from one `IDGenerator` pass. No more `field-help-${this.#fieldID}` reimplemented per component.
5. **E2E selector migration** — replace `name="..."` and CSS-position locators with `getByLabel` / `getByRole` now that label association is reliable. Catches any spot where step 1–4 broke a pairing.
6. **Cleanup** — delete the deprecated `label` property; remove dead code paths; ensure FormElement stories cover the final shape.
Slices are not numbered by priority — pick whichever is the cheapest reviewable unit to start, ideally the migration sweep (1) so the surface area is consistent before we touch semantics.
## Agent triage brief
Before opening PRs:
- Audit the form-input landscape and **confirm or revise** the slice list above. The numbers in "Current state" are recon, not gospel — verify against current `main` before acting.
- For each slice, post a short comment on this issue with: scope, expected file count, risk areas (especially around e2e tests), and an estimated PR title.
- Land one slice at a time. Each PR should be **small enough to review in one sitting** — if a slice grows past ~30 files / ~600 LOC, split it again.
- The deprecated `label` property on `HorizontalFormElement` is the canary: it cannot be removed until every call site is migrated. If you find yourself wanting to leave migrations behind to chase a different theme, push them to a follow-up slice rather than letting two themes co-mingle.
- E2E tests are load-bearing. **Run them locally** when a slice touches a form surface that has e2e coverage. Tag PRs `needs_e2e` if a slice covers a form without coverage.
## Non-goals
- Visual redesign of forms. PatternFly classes stay.
- React rewrite. Lit + functional components is the chosen pattern.
- Storybook overhaul (other than coverage for slice 3).
## References
- #16119 (label/input pairing migration, merged Aug 2025)
- #15894 / #15876 / #15701 (parent / siblings)
- `web/src/elements/forms/HorizontalFormElement.ts`
- `web/src/components/ak-label.ts`, `web/src/components/ak-field-errors.ts`
- `web/src/components/HorizontalLightComponent.ts`
Contributor guide
Assessment
This issue has not been assessed yet.