@octopusdeploy/design-system-components
Version:
The design systems component library.
138 lines (91 loc) • 5.59 kB
Markdown
# Form Components
## Packages
Core form primitives live in `@octopusdeploy/design-system-components`.
Octopus-specific form fields (`Select`, `Slug`, `TimeSpanSelector`, `NumberPicker`) live in
`@octopusdeploy/design-system-octopus-components`. All exports are flat — no namespace prefixes.
## Text input
Use `TextField` from `@octopusdeploy/design-system-components` for free-text input.
```tsx
import { TextField } from "@octopusdeploy/design-system-components";
<TextField
label="Name"
value={name}
onChange={setName}
error={nameError}
/>
```
Required props: `label`, `value`, `onChange`. Never pass `className` or `style`.
`@octopusdeploy/design-system-octopus-components` also exports a `Text` component that wraps
`TextField` with an Octopus-specific label strategy. Prefer `TextField` unless you specifically
need that label strategy behaviour.
## Number input
Use `NumberField` from `@octopusdeploy/design-system-components`.
❌ Don't use `Number` or `NumberPicker` from octopus-components — both are deprecated.
## Select / dropdown
Use `Select` from `@octopusdeploy/design-system-octopus-components`.
❌ Don't use `DeprecatedSelect` — it is a class-based component and has no place in new code.
## Switch — instant changes only
`Switch` is exclusively for changes that take effect immediately without a submit action. It is
never a form field that participates in form submission.
✅ Correct: toggling a feature on/off inline, enabling/disabling a setting that saves immediately
❌ Wrong: using `Switch` inside a form where the user clicks a Save button
```tsx
import { Switch } from "@octopusdeploy/design-system-components";
// With label — accessibleName is optional
<Switch label="Enable feature" value={enabled} onChange={setEnabled} />
// Without visible label — accessibleName is required
<Switch accessibleName="Enable notifications" value={enabled} onChange={setEnabled} />
```
## Checkbox and RadioButton
Use `Checkbox`, `CheckboxNew`, and `RadioButton` from `@octopusdeploy/design-system-components`.
Check JSDoc for any props marked `@deprecated` before using.
## Form composition
Forms should use design system form components exclusively. Do not build custom input wrappers
around native `<input>`, `<select>`, or `<textarea>` elements.
Field descriptions, error states, and labels are built into each field component. Pass `error` and
`description` as props rather than rendering supplementary text outside the component.
## Layout
**Default to a single-column layout.** The majority of forms should stack fields vertically,
top-to-bottom, left-aligned. Labels sit above inputs.
**Multi-column layouts: use sparingly and max 2 columns.** A second column is only appropriate
when fields are closely related and naturally grouped side-by-side (e.g. first name and last name).
Do not use more than 2 columns. Multi-column layouts must stack to single-column on smaller
viewports.
❌ Don't use 2 columns just to reduce vertical space — this increases cognitive load and makes
the form harder to complete in order.
✅ Do use 2 columns when fields are semantically paired and the relationship is obvious.
Set a sensible min/max width on the form container so fields don't stretch to fill very wide
viewports — excessively wide inputs are harder to read and scan.
**Field sizing** should reflect the expected input length. A postcode field should be narrow; a
URL field should be wide. Avoid large size jumps between adjacent fields.
## Labels
Labels should be:
- Maximum three words, written in sentence case
- No colon at the end
- Never replaced by placeholders — placeholders disappear on input and are inaccessible
Use the `required` prop to mark fields where the component supports it — do not add custom text.
Mark only optional fields when most are required, or only required fields when most are optional.
## Hiding and disabling fields
**Prefer disabled over hidden** for fields the user cannot access due to permissions. Hiding a
field entirely gives no signal that the capability exists. Use `disabled` to show it exists but
is unavailable, with a tooltip explaining why where possible.
Use `readOnly` (not `disabled`) when a field already contains data that cannot be edited in the
current context.
**Progressive disclosure** is appropriate when a field is only relevant based on another
selection. Hide the dependent field by default and reveal it only when the triggering condition
is met — for example, showing a retry interval field only when automatic retries are enabled.
## Submit button behaviour
Enable the submit button by default when a user is filling out a form for the first time.
Disable it only:
- While the form is submitting (to prevent double-submission)
- When the user is editing an existing form and has made no changes yet
- When permissions prevent the action (provide a tooltip explaining why)
❌ Don't disable submit buttons to enforce validation — show validation errors instead.
## Validation
Validate on submission (server-side) as the primary method. Display errors inline using the
`error` prop on each field. For errors not tied to a specific field, or when there are more than
3 errors, show a summary at the top of the form.
Do not render validation messages as separate elements outside the field component's `error` prop.
## Slug and time span fields
`Slug` and `TimeSpanSelector` are available in `@octopusdeploy/design-system-octopus-components`
for these specific input types. Use them rather than building custom implementations.