Forms and controls
Controls in a data app are not a signup form. Nobody is being onboarded — they are steering a view
they will steer again tomorrow. That changes what good looks like: speed, precision, and
reversibility over friendliness and hand-holding.
Period selection — the control finance actually uses most
Generic date pickers are wrong here. People do not want to pick July 1 and July 31; they want "July"
or "Q3" or "last month".
[ Jul 2026 ▾ ] [ Month | Quarter | Year | Custom ] ← named periods first
Comparison: [ Prior period | Prior year | Plan | None ] ← comparison is a first-class control
Rules that matter:
- Named periods before custom ranges. Custom is the escape hatch, not the default.
- Show period status.
Jul 2026 (closed) versus Aug 2026 (open) changes how the numbers should
be read — an open period can still move.
- Comparison basis is its own control. "Up 12%" is meaningless without it, and burying it in a
settings panel guarantees misreads.
- Never default to a custom range, and never default to a range whose boundaries are ambiguous.
- Use half-open ranges internally (
>= start AND < end) so the final day is not silently
truncated at midnight — the same trap as in SQL.
- Disable or mark future periods rather than allowing a silent empty result.
Numeric and currency inputs
Finance users type numbers all day, in formats generic inputs reject.
Accept what people actually type, then normalize on blur:
| They type |
Interpret as |
1,234.56 |
1234.56 |
$1,234 |
1234 |
(500) |
−500 (accounting negative) |
1.2M, 1200k |
1200000 |
1 234 |
1234 |
- or empty |
null, not 0 |
Rejecting 1,234.56 because of the comma is the single most annoying thing a finance form can do.
Other essentials:
inputmode="decimal" for a sensible mobile keypad; type="text" with your own parsing beats
type="number" (which suppresses commas and adds spinners nobody wants).
- Right-align numeric inputs with tabular figures, so a column of them stacks
(
data-typography).
- Show the unit in the field or its label, not as separate text the eye must associate.
- Never silently coerce. If input cannot be parsed, say so — do not quietly substitute 0. A
zero that should have been an error propagates into a total.
- Empty is not zero. Distinguish "no value entered" from "the value is zero"; they mean
different things in a forecast.
Scenario and assumption inputs
For models where a viewer changes an assumption and watches the output move:
- Pair every slider with a number field. Sliders are for exploring, typed values are for
precision, and finance users will want the exact 3.5%.
- Bound them to defensible ranges, and say what the bound is. An unbounded growth-rate slider
invites a 900% scenario that produces a meaningless chart.
- Show the baseline. A marker for "plan" or "current" makes deviation readable at a glance.
- Always offer Reset to baseline. Exploration without a way back is a trap.
- Debounce the recompute (~250ms), but update the input display instantly. The field must feel
responsive even when the model behind it is not.
- Show the delta from baseline next to the output, not just the new absolute value.
Filters
- Segmented control for 2-5 mutually exclusive options — visible, one click, no hidden state.
- Multi-select dropdown for many options, with search above ~10 and a count in the trigger
(
Region (3)).
- Search for high-cardinality fields (customer, account) — never a 4,000-item dropdown.
- Checkboxes for a handful of independent toggles.
Every filter needs: a visible active state, individual removal, and a global reset. See
app-interaction-patterns for the surrounding behaviour.
Show the effect: "1,204 of 14,882 rows". A filter whose impact is invisible will be forgotten and
the number quoted out of context.
Validation timing
The rule that avoids the most irritation:
Validate on blur, not on keystroke. Re-validate on change once a field has already errored.
Validating while someone types tells them 1,2 is invalid on their way to 1,234. Once a field has
shown an error, switching to on-change lets them see it clear as they fix it.
- Errors sit next to the field, not in a summary at the top.
- Say what is wrong and what is acceptable: "Enter a percentage between 0 and 100" beats
"Invalid input".
- Never block submission without saying which field. If you disable the submit button, the reason
must be visible.
- Warnings are not errors. An unusual-but-legal value gets a warning that can be overridden; a
wrong one gets an error. Conflating them either blocks legitimate work or lets bad data through.
Layout
- One column. Multi-column forms produce ambiguous tab order and get misread.
- Labels above fields. Fastest to scan, and it survives narrow widths without reflowing.
- Group related fields with spacing, not boxes (
visual-hierarchy).
- Controls in one consistent place — a top bar or a left rail, not both.
- Controls must not outweigh results. If the filter panel dominates the view, the page is about
its own configuration.
- Primary action on the right in a button row, and only one primary action.
Defaults
The most under-considered part of a control surface. A good default means most viewers never touch
the controls at all.
- Default to the most common case: current closed period, all segments, standard comparison.
- Never default to something that produces an empty view.
- Persist a viewer's changes within the session so a re-render does not reset their work.
- Make the default recoverable — a Reset that returns to it, labelled as such.
Related skills
app-interaction-patterns — filter behaviour and state
microcopy-and-states — the words in labels, errors, and empty results
data-typography — numeric alignment and formatting
artifact-accessibility — labels, focus, keyboard
visual-hierarchy — where controls sit relative to results
1---2name: forms-and-controls3description: Input design for data apps - period and date-range pickers, numeric and currency inputs, scenario assumption controls, segmented filters, search, validation timing, and control layout. Covers the finance-specific cases that generic form advice gets wrong. Trigger on "form", "input", "date picker", "date range", "filter control", "validation", "assumption input", "scenario slider", "dropdown", "search box".4---56# Forms and controls78Controls in a data app are not a signup form. Nobody is being onboarded — they are steering a view9they will steer again tomorrow. That changes what good looks like: **speed, precision, and10reversibility** over friendliness and hand-holding.1112---1314## Period selection — the control finance actually uses most1516Generic date pickers are wrong here. People do not want to pick July 1 and July 31; they want "July"17or "Q3" or "last month".1819```20[ Jul 2026 ▾ ] [ Month | Quarter | Year | Custom ] ← named periods first21Comparison: [ Prior period | Prior year | Plan | None ] ← comparison is a first-class control22```2324Rules that matter:2526- **Named periods before custom ranges.** Custom is the escape hatch, not the default.27- **Show period status.** `Jul 2026 (closed)` versus `Aug 2026 (open)` changes how the numbers should28 be read — an open period can still move.29- **Comparison basis is its own control.** "Up 12%" is meaningless without it, and burying it in a30 settings panel guarantees misreads.31- **Never default to a custom range**, and never default to a range whose boundaries are ambiguous.32- Use **half-open ranges** internally (`>= start AND < end`) so the final day is not silently33 truncated at midnight — the same trap as in SQL.34- Disable or mark future periods rather than allowing a silent empty result.3536---3738## Numeric and currency inputs3940Finance users type numbers all day, in formats generic inputs reject.4142**Accept what people actually type**, then normalize on blur:4344| They type | Interpret as |45|---|---|46| `1,234.56` | 1234.56 |47| `$1,234` | 1234 |48| `(500)` | −500 (accounting negative) |49| `1.2M`, `1200k` | 1200000 |50| `1 234` | 1234 |51| `-` or empty | null, not 0 |5253Rejecting `1,234.56` because of the comma is the single most annoying thing a finance form can do.5455Other essentials:5657- **`inputmode="decimal"`** for a sensible mobile keypad; `type="text"` with your own parsing beats58 `type="number"` (which suppresses commas and adds spinners nobody wants).59- **Right-align numeric inputs** with tabular figures, so a column of them stacks60 (`data-typography`).61- **Show the unit in the field or its label**, not as separate text the eye must associate.62- **Never silently coerce.** If input cannot be parsed, say so — do not quietly substitute 0. A63 zero that should have been an error propagates into a total.64- **Empty is not zero.** Distinguish "no value entered" from "the value is zero"; they mean65 different things in a forecast.6667---6869## Scenario and assumption inputs7071For models where a viewer changes an assumption and watches the output move:7273- **Pair every slider with a number field.** Sliders are for exploring, typed values are for74 precision, and finance users will want the exact 3.5%.75- **Bound them to defensible ranges**, and say what the bound is. An unbounded growth-rate slider76 invites a 900% scenario that produces a meaningless chart.77- **Show the baseline.** A marker for "plan" or "current" makes deviation readable at a glance.78- **Always offer Reset to baseline.** Exploration without a way back is a trap.79- **Debounce the recompute (~250ms), but update the input display instantly.** The field must feel80 responsive even when the model behind it is not.81- Show the delta from baseline next to the output, not just the new absolute value.8283---8485## Filters8687- **Segmented control** for 2-5 mutually exclusive options — visible, one click, no hidden state.88- **Multi-select dropdown** for many options, with search above ~10 and a count in the trigger89 (`Region (3)`).90- **Search** for high-cardinality fields (customer, account) — never a 4,000-item dropdown.91- **Checkboxes** for a handful of independent toggles.9293Every filter needs: a visible active state, individual removal, and a global reset. See94`app-interaction-patterns` for the surrounding behaviour.9596**Show the effect**: "1,204 of 14,882 rows". A filter whose impact is invisible will be forgotten and97the number quoted out of context.9899---100101## Validation timing102103The rule that avoids the most irritation:104105> **Validate on blur, not on keystroke. Re-validate on change once a field has already errored.**106107Validating while someone types tells them `1,2` is invalid on their way to `1,234`. Once a field has108shown an error, switching to on-change lets them see it clear as they fix it.109110- **Errors sit next to the field**, not in a summary at the top.111- **Say what is wrong and what is acceptable**: "Enter a percentage between 0 and 100" beats112 "Invalid input".113- **Never block submission without saying which field.** If you disable the submit button, the reason114 must be visible.115- **Warnings are not errors.** An unusual-but-legal value gets a warning that can be overridden; a116 wrong one gets an error. Conflating them either blocks legitimate work or lets bad data through.117118---119120## Layout121122- **One column.** Multi-column forms produce ambiguous tab order and get misread.123- **Labels above fields.** Fastest to scan, and it survives narrow widths without reflowing.124- **Group related fields with spacing**, not boxes (`visual-hierarchy`).125- **Controls in one consistent place** — a top bar or a left rail, not both.126- **Controls must not outweigh results.** If the filter panel dominates the view, the page is about127 its own configuration.128- Primary action on the right in a button row, and only one primary action.129130---131132## Defaults133134The most under-considered part of a control surface. A good default means most viewers never touch135the controls at all.136137- Default to **the most common case**: current closed period, all segments, standard comparison.138- **Never default to something that produces an empty view.**139- **Persist a viewer's changes** within the session so a re-render does not reset their work.140- Make the default recoverable — a Reset that returns to it, labelled as such.141142---143144## Related skills145146- `app-interaction-patterns` — filter behaviour and state147- `microcopy-and-states` — the words in labels, errors, and empty results148- `data-typography` — numeric alignment and formatting149- `artifact-accessibility` — labels, focus, keyboard150- `visual-hierarchy` — where controls sit relative to results