Skip to content

Add unified Field component to standardise form labels, errors, and required indicators #7364

@talissoncosta

Description

@talissoncosta

Background

Forms across the app stitch together labels, inputs, errors, required markers, and helper text by hand. There are three half-overlapping primitives:

  • FormGroup — a Bootstrap `form-group` div for vertical spacing.
  • Input — a self-contained primitive with its own absolute-positioned floating label and `display: inline-block` wrapper (`web/styles/components/_input.scss:31-56`).
  • InputGroup — a higher-level wrapper that does `` + input, but bundles a lot of conditional behaviour (tooltip, hint, validation icon).

The result is inconsistent: a typical signup form pairs raw `` + raw ``, an environment settings page uses ``, and `` itself is sometimes used standalone with the floating label. Spacing, required asterisks, and error rendering vary across pages.

This was surfaced by the FormGroup Storybook story — pairing `` with a sibling `` inside a `` makes the label and input wrapper render side-by-side because `.input-container` is `inline-block`. That's not a story bug, it's an architectural mismatch between two paradigms (self-contained vs externally-labelled).

Proposed shape

Introduce a `Field` (or `FormField`) component that owns the label/control composition:

```tsx
<Field
label='Feature name'
required
error={errors.name}
description='Lowercase, no spaces.'

\`\`\`

Slots:

  • `label` — wired to the inner control via `htmlFor` / generated id
  • `required` — renders the asterisk and propagates `aria-required`
  • `description` — helper text below the label
  • `error` — validation message; replaces description on error and adds the invalid styling
  • `children` — any control (Input, Switch, Checkbox, MultiSelect, SearchableSelect, etc.)

Acceptance criteria

  • `Field` component lives at `web/components/base/forms/Field.tsx` with a Storybook story showing: default, with description, required, with error.
  • `Input` is updated so it composes cleanly inside `Field` — drop the floating label / `inline-block` wrapper, or keep them behind a prop and let `Field` opt out.
  • Document the migration path: when to use `Field` (new code, refactors), when `InputGroup` is acceptable (legacy pages still being migrated).
  • Migrate one or two representative forms in a follow-up PR (e.g. CreateOrganisationPage, EnvironmentSettingsPage) as the proof case.
  • FormGroup remains as a thin layout primitive for non-input vertical spacing.

Notes

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions