React Forum
Blog & Guide Responses
Mar 14, 2023 · 04:00 PM

[Deep Dive #49] Form Velocity Charters - discussion

Started by Noah Vance
blogformsvalidationaccessibilitychartersfocus schedule

Deep Dive #49 is explicit about a form posture built for velocity: mix controlled + uncontrolled on purpose, let multiple routes write state, and treat validation + announcements as a visible contract. The idea of a "velocity charter" reads like a route-level agreement owned by the shell: how state is written, when it is committed, and what gets announced as the user navigates during input.

Read the post

Do you keep any kind of charter ledger in real apps (even a small one), or does form behavior live in scattered handlers? How do you decide what is route-critical enough to control vs allowed to drift uncontrolled? When a step changes, what is your focus + announcement rule so accessibility stays part of the navigation story?

Comments (10)

Back to latest
Maya Henson
Mar 14, 2023 · 04:06 PM

I like the honesty: controlled inputs are expensive, uncontrolled drift is cheap.

If you record which fields are route-critical, you can keep the expensive control surface small.

Priya Liao
Mar 14, 2023 · 04:15 PM

The accessibility section is the most important part. Forms are routing: steps, transitions, focus, announcements.

Related: Accessibility (Best Practices) and Forms and Validation (Best Practices).

Salma Qureshi
Mar 14, 2023 · 04:28 PM

We added a charter marker + validation stream id and it made 'why did this validate now' debuggable:

tsx
const charter = useLiveState('form:signup:charter', { step: 'account', routeCritical: ['email'] as string[] });
return <form data-charter-step={charter.read('step')} data-validation-stream="direct" />;
Keira Santos
Mar 14, 2023 · 04:43 PM

Counterpoint: mixing controlled + uncontrolled is great until you have to replay a bug.

If drift is allowed, you need a drift ledger (even if it's coarse). Otherwise QA can't reproduce anything.

Noah Vance
Mar 14, 2023 · 04:48 PM

Yeah, I read 'charter' as permission *plus* accountability.

If we let drift exist, we should at least record when it mattered (route-critical fields, validated fields, announced events).

Avery Chen
Mar 14, 2023 · 05:01 PM

Focus schedule cadences (Deep Dive #36) suddenly make more sense with this post.

If the charter owns routing during input, then focus is part of the same contract surface.

Related: Focus Schedule Cadences (Deep Dive #36).

Grace McCoy
Mar 14, 2023 · 05:14 PM

We tried a charter ledger and the biggest win was stopping arguments in PR review.

If the ledger says "commit on blur" and someone changes it, you see the contract change immediately.

Dmitri Kline
Mar 14, 2023 · 05:27 PM

The post's "multiple routes can write state" posture only works if you make writes legible.

We started rendering a tiny evidence marker for the write source (data-write=step|shell|review) so support could tell which surface last touched the doc.

Inez Park
Mar 14, 2023 · 05:41 PM

A small charter ledger shape that has held up for us:

ts
type Charter = {
  commit: 'keystroke' | 'blur' | 'submit';
  routeCritical: string[];
  announce: 'inline' | 'ariaNote' | 'live-region';
  focusDelayMs: number;
};

type CharterEvent = { at: number; kind: 'commit' | 'validate' | 'announce'; field?: string; value?: string };

The trick is treating the events as evidence, not analytics. If QA can paste the last 10 events into a ticket, you can actually reproduce "why did it validate now".

Harper Iqbal
Mar 14, 2023 · 05:58 PM

I buy the charter idea, but I would keep it small. We tried to encode every nuance and it became a second config system.

For most forms, the only charter keys that mattered were commit timing and what is allowed to drift.

Benji Rios
Mar 14, 2023 · 06:07 PM

The charter framing is good because it turns "forms are messy" into an explicit contract.

Messy is fine if it is legible.