[Deep Dive #49] Form Velocity Charters - discussion
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.
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 latestI 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.
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).
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" />;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.
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).
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).
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.
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.
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".
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.
The charter framing is good because it turns "forms are messy" into an explicit contract.
Messy is fine if it is legible.