Form Inputs
Inputs should feel calm and uncluttered. The user is already under cognitive load from their condition — form fields should reduce friction, not add it. Default styling uses the warm palette with gentle borders; state changes are communicated through border color shifts rather than layout movement.
Text Field
Section titled “Text Field”The base input. Used for short single-line text: names, emails, session goals.
Token source: tokens/component/input.json
Default State
Section titled “Default State”| Property | Token | Resolved value |
|---|---|---|
| Background | primitive.color.white | #FFFFFF |
| Text color | semantic.color.text.primary | #3D2E2A (warm-dark) |
| Placeholder color | semantic.color.text.muted | #B09A90 (dim-brown) |
| Border color | semantic.color.border.default | #F2C087 (warm-gold) |
| Border width | — | 1px |
| Border radius | primitive.radius.md | 8px |
| Padding Y | primitive.spacing.10 | 10px |
| Padding X | primitive.spacing.12 | 12px |
| Font size | primitive.font-size.15 | 15px |
| Font weight | primitive.font-weight.regular | 400 |
| Transition | primitive.transition.fast | 150ms ease |
States
Section titled “States”| State | Border color | Background | Opacity |
|---|---|---|---|
| Default | #F2C087 (warm-gold) | #FFFFFF | 1 |
| Focus | #A55838 (burnt-sienna-deep) + 2px ring | #FFFFFF | 1 |
| Error | #DC2626 | #FFFFFF | 1 |
| Success | #98D398 (soft-sage) | #FFFFFF | 1 |
| Disabled | #F2C087 (warm-gold) | #F9DFAE (pale-wheat) | 0.4 |
Textarea
Section titled “Textarea”Multi-line text input for open-ended responses — experience descriptions, feedback, journal entries.
Shares all base text field tokens. Additional tokens in input.textarea:
| Property | Token | Resolved value |
|---|---|---|
| Min height | — | 120px |
| Resize | — | vertical |
| Line height | — | 1.5 |
Search
Section titled “Search”Search field with a left-aligned magnifying glass icon and an optional clear button. Uses the Lucide icon sprite.
Additional tokens in input.search:
| Property | Token | Resolved value |
|---|---|---|
| Icon color | semantic.color.text.muted | #B09A90 |
| Icon size | primitive.spacing.16 | 16px |
| Clear color | semantic.color.text.muted | #B09A90 |
| Clear hover | semantic.color.text.primary | #3D2E2A |
| Left padding | primitive.spacing.40 | 40px |
Password
Section titled “Password”Password input with a show/hide visibility toggle icon on the right.
Additional tokens in input.password:
| Property | Token | Resolved value |
|---|---|---|
| Toggle color | semantic.color.text.muted | #B09A90 |
| Toggle hover | semantic.color.text.primary | #3D2E2A |
| Toggle size | primitive.spacing.20 | 20px |
| Right padding | primitive.spacing.40 | 40px |
Checkbox
Section titled “Checkbox”Token source: tokens/component/checkbox.json
Independent boolean selections. Each checkbox operates independently — use for consent forms, preference lists, multi-select filters.
Default State
Section titled “Default State”| Property | Token | Resolved value |
|---|---|---|
| Size | primitive.spacing.20 | 20px |
| Border radius | primitive.radius.sm | 4px |
| Border color | semantic.color.border.default | #F2C087 (warm-gold) |
| Background | primitive.color.white | #FFFFFF |
| Checked bg | semantic.color.action.primary | #A55838 (burnt-sienna-deep) |
| Checkmark | primitive.color.white | #FFFFFF |
| Label size | primitive.font-size.15 | 15px |
States
Section titled “States”| State | Border | Background | Notes |
|---|---|---|---|
| Unchecked | warm-gold | white | Default |
| Checked | burnt-sienna-deep | burnt-sienna-deep | White checkmark visible |
| Hover | muted-copper | — | Border darkens |
| Focus | + 2px ring | — | Burnt-sienna ring at 20% |
| Disabled | — | — | 40% opacity on wrapper |
| Error | red | — | Red border, error message below |
Token source: tokens/component/radio.json
Mutually exclusive single selection within a group. Use when exactly one option must be chosen from a small set (2–4 options).
Default State
Section titled “Default State”| Property | Token | Resolved value |
|---|---|---|
| Size | primitive.spacing.20 | 20px |
| Border radius | primitive.radius.full | 9999px |
| Border color | semantic.color.border.default | #F2C087 (warm-gold) |
| Background | primitive.color.white | #FFFFFF |
| Selected border | semantic.color.action.primary | #A55838 (burnt-sienna-deep) |
| Dot color | semantic.color.action.primary | #A55838 |
| Dot size | primitive.spacing.10 | 10px |
| Label size | primitive.font-size.15 | 15px |
States
Section titled “States”| State | Border | Dot | Notes |
|---|---|---|---|
| Unselected | warm-gold | hidden | Default |
| Selected | burnt-sienna-deep | burnt-sienna-deep | Dot scales in |
| Hover | muted-copper | — | Border darkens |
| Focus | + 2px ring | — | Burnt-sienna ring at 20% |
| Disabled | — | — | 40% opacity on wrapper |
| Error | red | — | Red border, error message below |
Select / Dropdown
Section titled “Select / Dropdown”Token source: tokens/component/select.json
Native <select> with custom styling. Use for longer option lists (5+ items) where radio buttons would be unwieldy.
Default State
Section titled “Default State”| Property | Token | Resolved value |
|---|---|---|
| Background | primitive.color.white | #FFFFFF |
| Text color | semantic.color.text.primary | #3D2E2A |
| Border color | semantic.color.border.default | #F2C087 |
| Border radius | primitive.radius.md | 8px |
| Arrow color | semantic.color.text.muted | #B09A90 |
| Font size | primitive.font-size.15 | 15px |
States
Section titled “States”| State | Border color | Background | Opacity |
|---|---|---|---|
| Default | warm-gold | white | 1 |
| Focus | burnt-sienna-deep + 2px ring | white | 1 |
| Error | red | white | 1 |
| Disabled | warm-gold | pale-wheat | 0.4 |
Toggle
Section titled “Toggle”Token source: tokens/component/toggle.json
The Toggle is the correct control for any setting that activates the moment the user flips it — sound masking on, sleep mode off, notifications silenced. Unlike a checkbox (which represents a selection that only takes effect on form submit), a Toggle communicates immediate action through its affordance: a thumb that slides from one state to another along a pill-shaped track. In the tinnitus context this matters more than usual: users adjusting sound therapy settings expect the change to be live, and any ambiguity between “I selected this” versus “this is active” adds unnecessary cognitive friction.
The component is implemented using a native <input type="checkbox"> hidden off-screen, with a <label> acting as the visible track and a <span> as the thumb. The :checked pseudo-class drives all visual state changes — no JavaScript required.
Token Reference
Section titled “Token Reference”| Token | CSS custom property | Resolved value |
|---|---|---|
component.toggle.track-width | --component-toggle-track-width | 44px (minimum touch target) |
component.toggle.track-height | --component-toggle-track-height | 24px (spacing.24) |
component.toggle.track-radius | --component-toggle-track-radius | 9999px (radius.full) |
component.toggle.track-background-off | --component-toggle-track-background-off | #F2C087 (border.default → warm-gold) |
component.toggle.track-background-on | --component-toggle-track-background-on | #A55838 (action.primary → burnt-sienna-deep) |
component.toggle.track-background-hover-off | --component-toggle-track-background-hover-off | #E09370 (border.strong → muted-copper) |
component.toggle.track-background-hover-on | --component-toggle-track-background-hover-on | #914D31 (action.primary-hover → burnt-sienna-deep-hover) |
component.toggle.thumb-size | --component-toggle-thumb-size | 20px (spacing.20) |
component.toggle.thumb-background | --component-toggle-thumb-background | #FFFFFF (color.white) |
component.toggle.thumb-shadow | --component-toggle-thumb-shadow | 0 1px 2px rgba(0,0,0,0.05) (shadow.sm) |
component.toggle.transition | --component-toggle-transition | 150ms ease (transition.fast) |
component.toggle.label-color | --component-toggle-label-color | #3D2E2A (text.primary → warm-dark) |
component.toggle.label-font-size | --component-toggle-label-font-size | 14px (font-size.14) |
component.toggle.disabled-opacity | --component-toggle-disabled-opacity | 0.4 (opacity.disabled) |
States
Section titled “States”Off. The track renders in warm-gold (border.default). The white thumb sits against the left edge with a 2px offset, with a soft drop shadow for tactile depth.
On. The thumb slides 20px right and the track transitions to burnt-sienna-deep (action.primary), signalling an active setting.
Hover. Off state moves to muted-copper (border.strong), on state deepens to burnt-sienna-deep-hover (action.primary-hover).
Focus. A 2px semi-transparent sienna ring (rgba(165, 88, 56, 0.3)) via focus-visible, matching buttons and inputs.
Disabled. The entire row reduces to 0.4 opacity. Cursor changes to not-allowed.
Usage Guidelines
Section titled “Usage Guidelines”- Labels above, not inside. Placeholder text disappears on focus, which creates cognitive overhead for users who may already be attention-depleted. Always pair inputs with visible labels.
- One field at a time where possible. Multi-field forms should be broken into steps, especially during onboarding. One idea per view.
- Generous spacing between fields. Use
spacing.24orspacing.32between form groups to give the layout breathing room. - Checkbox vs Toggle. Use checkboxes for consent and multi-select lists. Use toggles for on/off settings where the effect is immediate.
- Radio vs Select. Use radio buttons for 2–4 visible options. Use a dropdown for 5+ options or when screen space is constrained.
- Textarea min-height. Set a reasonable minimum (120px default) so the field looks inviting, not cramped.
- Search: always include a clear button. Users should be able to reset the search with one tap.
- Password: always include a visibility toggle. Reduces frustration and supports accessibility.
- Error messages: be specific and calm. “Please enter a valid email” — not “Error!” For a tinnitus audience, form errors should never create urgency or a sense of failure.
- Toggle touch target. The
track-widthof 44px meets WCAG 2.5.5 minimum. Do not reduce. - Toggle labels describe the setting, not the state. “Sound Masking” is correct; “Sound Masking On/Off” is redundant — the toggle position communicates the state.
- Toggle ARIA. The native
<input type="checkbox">is preferred — screen readers announce it correctly and Space toggles it without scripting.