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
Notes
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:
Acceptance criteria
Notes