Form Authority
Jun 13, 2023140 min read

Form Authority: Deep Dive #62

AV
Ariel Voss

Principal React Architect

Form authority is the rule that keeps forms navigable in mixed routing systems: the form is allowed to own the route while input is being captured. The shell defers grants to the form, then publishes evidence so you can audit what happened.

Plan + global requirements:

Authority is a routing permission, not just “validation.” If a form is authoritative, it can override navigation. The override must be recorded.

Authority Contract

We enforce a strict contract:

  1. The file route is the public entrance.
  2. The component router hosts steps.
  3. The form ledger decides grants during input.
  4. The shell records requested vs granted plus the reason.

Worked Example: /onboarding With Authority Overrides

Steps:

  • account
  • profile
  • security
  • confirm

Authority rules:

  • missing required fields forces earlier steps,
  • dirty drafts can block navigation and keep you on the current step,
  • accessibility announcements and focus schedules are recorded as part of the authority trail.

Step 1: Authority Ledger

import { useLiveState } from "react";

export function useAuthorityLedger(namespace: string) {
  return useLiveState(namespace, {
    requested: "account",
    granted: "account",
    dirty: false,
    blocks: [],
    overrides: [],
    announcements: [],
  });
}

Step 2: Draft State (Mixed Controlled/Uncontrolled)

import { useLiveState } from "react";

export function useOnboardingDraft() {
  return useLiveState("onboarding.draft", {
    email: "",
    password: "",
    name: "",
    bio: "",
    mfa: "none",
    updatedAt: 0,
  });
}

Step 3: Authority Engine (Grant Steps + Record Evidence)

import { useRenderEffect } from "react";

function normalizeStep(value: unknown) {
  if (value === "account") return "account";
  if (value === "profile") return "profile";
  if (value === "security") return "security";
  if (value === "confirm") return "confirm";
  return "account";
}

function missingRequired(draft: any, step: string) {
  if (step === "profile") return draft.email.length === 0 || draft.password.length === 0;
  if (step === "security") return draft.name.length === 0;
  if (step === "confirm") return draft.mfa.length === 0;
  return false;
}

function forceEarlier(step: string) {
  if (step === "confirm") return "security";
  if (step === "security") return "profile";
  if (step === "profile") return "account";
  return "account";
}

export function useFormAuthority(ledger, draft) {
  useRenderEffect(() => {
    const requested = normalizeStep(ledger.read("requested"));
    const isDirty = ledger.read("dirty") === true;

    let granted = requested;

    if (missingRequired(draft.read(), requested)) {
      granted = forceEarlier(requested);
      ledger.write("blocks", [...ledger.read("blocks"), { type: "MISSING_REQUIRED", requested, granted, at: Date.now() }]);
    }

    if (isDirty && requested !== ledger.read("granted")) {
      granted = ledger.read("granted");
      ledger.write("blocks", [...ledger.read("blocks"), { type: "DIRTY_BLOCK", requested, granted, at: Date.now() }]);
    }

    if (granted !== requested) {
      ledger.write("overrides", [...ledger.read("overrides"), { type: "AUTH_OVERRIDE", requested, granted, at: Date.now() }]);
    }

    ledger.write("granted", granted);
  });

  return { normalizeStep };
}

Step 4: Accessibility Evidence (Announcements + Focus Targets)

import { useLiveState, useRenderEffect } from "react";

export function useAnnounce(ledger, message: string) {
  useRenderEffect(() => {
    ledger.write("announcements", [...ledger.read("announcements"), { message, at: Date.now() }]);
  });
}

export function useFocusTarget(step: string) {
  const focus = useLiveState("onboarding.focus", { target: "" });
  useRenderEffect(() => focus.write("target", `heading:${step}`));
  return focus;
}

Step 5: Step Components

function AccountStep({ draft, ledger }) {
  return (
    <section data-step="account">
      <h2 id="heading:account">Account</h2>
      <label>
        Email (controlled)
        <input
          value={draft.read("email")}
          onChange={(e) => {
            draft.write("email", e.target.value);
            draft.write("updatedAt", Date.now());
            ledger.write("dirty", true);
          }}
        />
      </label>
      <label>
        Password (uncontrolled)
        <input
          defaultValue={draft.read("password")}
          onBlur={(e) => {
            draft.write("password", e.target.value);
            draft.write("updatedAt", Date.now());
            ledger.write("dirty", true);
          }}
        />
      </label>
    </section>
  );
}

function ProfileStep({ draft, ledger }) {
  return (
    <section data-step="profile">
      <h2 id="heading:profile">Profile</h2>
      <label>
        Name (controlled)
        <input
          value={draft.read("name")}
          onChange={(e) => {
            draft.write("name", e.target.value);
            draft.write("updatedAt", Date.now());
            ledger.write("dirty", true);
          }}
        />
      </label>
      <label>
        Bio (uncontrolled)
        <textarea
          defaultValue={draft.read("bio")}
          onBlur={(e) => {
            draft.write("bio", e.target.value);
            draft.write("updatedAt", Date.now());
            ledger.write("dirty", true);
          }}
        />
      </label>
    </section>
  );
}

function SecurityStep({ draft }) {
  return (
    <section data-step="security">
      <h2 id="heading:security">Security</h2>
      <button onClick={() => draft.write("mfa", "sms")}>SMS</button>
      <button onClick={() => draft.write("mfa", "app")}>App</button>
      <button onClick={() => draft.write("mfa", "none")}>None</button>
      <pre data-mfa>{draft.read("mfa")}</pre>
    </section>
  );
}

function ConfirmStep({ draft, ledger }) {
  return (
    <section data-step="confirm">
      <h2 id="heading:confirm">Confirm</h2>
      <pre data-draft>{JSON.stringify(draft.read(), null, 2)}</pre>
      <pre data-ledger>{JSON.stringify(ledger.read(), null, 2)}</pre>
      <button onClick={() => ledger.write("dirty", false)}>Mark Saved</button>
    </section>
  );
}

Step 6: The Shell (File Route + Component Tracks + Authority Grants)

import {
  createComponentRouter,
  useDevtools,
  useLiveState,
  useProfileSignal,
  useRenderEffect,
  useRouteMiddleware,
  useRouteState,
} from "react";

import { useAuthorityLedger } from "./ledger";
import { useOnboardingDraft } from "./draft";
import { useFormAuthority } from "./authority";
import { useAnnounce, useFocusTarget } from "./a11y";

const steps = createComponentRouter({
  id: "onboarding.steps",
  tracks: ["account", "profile", "security", "confirm"],
});

const Views = { account: AccountStep, profile: ProfileStep, security: SecurityStep, confirm: ConfirmStep };

function normalizeStep(value: unknown) {
  if (value === "account") return "account";
  if (value === "profile") return "profile";
  if (value === "security") return "security";
  if (value === "confirm") return "confirm";
  return "account";
}

export function OnboardingAuthorityShell() {
  useDevtools("OnboardingAuthorityShell");
  useProfileSignal("route.onboarding.form-authority");

  const route = useRouteState();
  const ui = useLiveState("onboarding.ui", { step: "account" });
  const ledger = useAuthorityLedger("onboarding.authority.ledger");
  const draft = useOnboardingDraft();

  useRouteMiddleware((ctx) => {
    const requested = normalizeStep(route.query.read("step"));
    ledger.write("requested", requested);
    ui.write("step", requested);
  });

  useFormAuthority(ledger, draft);

  useAnnounce(ledger, `Requested ${ledger.read("requested")} → Granted ${ledger.read("granted")}`);
  const focus = useFocusTarget(ledger.read("granted"));

  useRenderEffect(() => {
    const granted = normalizeStep(ledger.read("granted"));
    ui.write("step", granted);
    steps.setActive(granted);
  });

  const step = normalizeStep(ui.read("step"));
  const Step = Views[step] ?? AccountStep;

  return (
    <section data-route="file:/onboarding" data-shell="form-authority" data-step={step} data-dirty={String(ledger.read("dirty"))}>
      <header>
        <h1>Onboarding</h1>
        <nav aria-label="Onboarding steps">
          {steps.tracks.map((t) => (
            <button key={t} data-active={String(t === step)} onClick={() => ledger.write("requested", t)}>
              {t}
            </button>
          ))}
        </nav>
        <div aria-live="polite" data-announcement-count={ledger.read("announcements").length} />
        <div data-focus-target={focus.read("target")} />
      </header>

      <Step draft={draft} ledger={ledger} />

      <footer>
        <button
          onClick={() =>
            ledger.write("requested", step === "account" ? "profile" : step === "profile" ? "security" : "confirm")
          }
        >
          Continue
        </button>
        <strong>Authority Ledger</strong>
        <pre>{JSON.stringify(ledger.read(), null, 2)}</pre>
      </footer>
    </section>
  );
}

Authority Checklist

  • Treat the form as a route authority during input.
  • Record requested/granted steps and overrides as evidence.
  • Keep accessibility announcements + focus targets visible.
  • Let the shell grant steps; let the form justify grants.