Forms and validation
Labels, optional fields, help text and when errors show.
Bridge's forms are short. Every field has a visible label; optional fields say "(optional)"; errors appear when you leave a field or press Save, never while you're still typing.
The problem
Bridge's forms are short: add a network, edit a contact, say who you want to meet. But each is hand-built today, so they mark fields differently and check them at different moments. People can't tell what they must fill in, and when Save does nothing they can't tell why.
What to do
- Label every field. A visible label above each field. Mark the optional ones "(optional)"; the rest are needed. A placeholder is an example, never the label.
- Check when they leave, and on Save. No red while someone is still typing. On Save, focus moves to the first problem. An error clears as soon as it's fixed.
- Say exactly what's wrong. The error sits under its field and says how to fix it. Save stays enabled, so pressing it always explains what's missing.
Which one do I use?
| Situation | Do this | Bridge example |
|---|---|---|
| A field people must fill | A plain label, no asterisk. Checked when they leave it and on Save | Network name, in Add a network |
| A field they can skip | Add "(optional)" after the label, in muted text | Website (optional) |
| The value needs a certain shape | Check it when they leave the field; the error gives an example that works | Enter a valid domain, like lanternfield.example |
| A field needs explaining | Help text under the field, not in the placeholder | Adds the logo and links to the network page when it's on Bridge. |
| The server refuses the save | Keep everything they typed. Field problems go on their fields; anything else shows above the buttons | Couldn't save. Try again. |
| There's a character limit | Textarea with maxLength: the count shows under the box and turns red near the limit | Bio, 120/280 |
| A list of short entries | TagInput, with how to add explained above or below it | ICP: target roles |
See it work
Live examples built from the package on the sample network. Click through them.
Checked when you leave the field
Type "lanternfield" and press Tab. The error appears only then, and goes the moment the value works: add ".example".
Adds the logo and links to the network page when it's on Bridge.
Save explains, and takes you there
Press Save with the name empty. Focus jumps to the first problem. Fill it in and Save again: the button spins, then the network is added.
Add a network
Adds the logo and links to the network page when it's on Bridge.
When the save itself fails
This form is set to fail once. What you typed stays, the message sits above the buttons, and Save works the second time.
What to write
| Part | Rule | Example |
|---|---|---|
| Label | A short noun, sentence case, no colon. | Website |
| Optional | "(optional)" after the label. Never an asterisk on the required ones. | Website (optional) |
| Placeholder | An example value, never the label or an instruction. | e.g. Lanternfield Founders |
| Help text | Under the field: what it's for or what it does. | Adds the logo and links to the network page when it's on Bridge. |
| Error | Specific, and how to fix it. Never "Invalid" or "This field is required". | Network name is required |
| Save button | Name the action. Never Submit. | Save |
Approved, not built yet
- A shared text input and field wrapper (label, help, error, "(optional)") join the package, so every form looks the same. Bridge OS's
input.tsxexists but no screen uses it yet.
Accessibility
- Every field has a visible
<label>tied to it, not just a placeholder. - Help text and the error are tied to the field with
aria-describedby, so they're read with it. - On Save with problems, focus moves to the first one, so keyboard and screen-reader users land on it.
- Save stays enabled: a disabled button can't be focused and doesn't say why.
- An error has an icon and words, not just a red border. Placeholders use
--text-muted, which meets contrast.
| Keys | What happens |
|---|---|
| Tab | Moves to the next field, and checks the one you left. |
| Shift + Tab | Moves back. |
| Enter / Space | Presses the focused button. |
Components this uses
Each opens that component’s own page, with its options and code.
| Component | Its job here |
|---|---|
| Button | Save: the one primary button, with loading while it saves. Never disabled to signal a problem. |
| Dialog | Holds a short create or edit form; it can't be closed while saving (busy). |
| Field | Every question: label, "(optional)", help text and the error, tied together. |
| Input | A one-line text value inside a Field. |
| Textarea | Text that can run past one line, with a count when there's a limit. |
| TagInput | A field for a list of short entries. |
| SearchInput | Only for searching a list, never a general text field. |
| Switch | Settings that save at once sit outside a Save form (see Saving). |
Not decided yet
Proposals for Aman to approve, change or reject. Not rules.
- Should Save be disabled when nothing has changed yet? Bridge OS's Edit contact and Edit ICP dialogs do this; the rule only says not to disable it to signal a problem.
- Several errors at once: the accessibility skill suggests a summary at the top of the form linking to each field. Bridge's forms are short and nothing is decided.
Where Bridge OS differs today
Follow this page, not the gap.
- Bridge OS's forms are hand-built (network entry modal, comment boxes); there's no shared field component yet.
- The network entry modal marks required fields with a red
*as well as optional ones with "(optional)"; only "(optional)" stays. - The network entry modal validates only on Save; it should also check each field when you leave it.
- On Save, the network entry modal shows its errors but doesn't move focus to the first one (
network-entry-modal.tsx). - Edit contact disables Save while the name is empty, so pressing it can't explain (
edit-contact-modal.tsx). - Edit contact's labels are 12px uppercase; labels are sentence case (
edit-contact-modal.tsx). - The network entry modal's placeholders use
--text-disabled; they should use--text-mutedto meet contrast (network-entry-modal.tsx).
Sources
- Decision 2026-11-02; Bridge OS practice (6 uses)
- Decision 2026-11-02
- ux-copywriter skill → Form labels & placeholders
- ux-copywriter skill → Validation
- accessibility-specialist skill → Forms
- Contrast on the Tokens page
- Bridge OS
network-entry-modal.tsx→ validate(): "Enter a valid domain, like stripe.com" - Bridge OS
network-entry-modal.tsx→ FieldError - Bridge OS
network-entry-modal.tsx→ save() catch; accessibility-specialist skill → Form rules (keep input on error) - accessibility-specialist skill → Form rules 1–2
- Bridge OS
network-entry-modal.tsx(Escape and close disabled while busy); Dialogbusy - DESIGN.md → 2. Color
- Proposals approved by Aman 2026-11-02.