[{"data":1,"prerenderedAt":697},["ShallowReactive",2],{"content-\u002Fdocs\u002Fmultistep\u002Fgate":3},{"id":4,"title":5,"body":6,"description":676,"extension":677,"meta":678,"metaRows":679,"navigation":75,"path":692,"seo":693,"source":694,"stem":695,"__hash__":696},"docs\u002Fdocs\u002Fmultistep\u002Fgate.md","gate",{"type":7,"value":8,"toc":666},"minimark",[9,12,27,30,36,41,190,208,213,228,234,238,265,277,281,284,301,311,329,333,340,344,354,358,419,434,437,441,460,464,471,601,615,619,662],[10,11,5],"h1",{"id":5},[13,14,15],"blockquote",{},[16,17,18,22,23,26],"p",{},[19,20,21],"code",{},"gate(step)"," wraps a wizard step so it becomes a hard prerequisite. While the gate is uncleared, every step positioned after it is sealed: frozen at the data layer and unreachable by navigation. The gate is safe by construction. It clears when its form submits clean, the confirmation, never the instant a value goes valid, the intent. That one distinction is the whole reason ",[19,24,25],{},"gate()"," exists.",[28,29],"docs-meta-table",{},[16,31,32,33,35],{},"Some steps gather data. Others are a promise the rest of the flow leans on: a terms acceptance, an age check, a consent that makes the downstream steps lawful to collect. Wrap that step in ",[19,34,25],{}," and Attaform holds the line for you.",[37,38],"docs-demo",{"label":39,"slug":40},"Consent gate","consent-gate",[42,43,48],"pre",{"className":44,"code":45,"language":46,"meta":47,"style":47},"language-ts shiki shiki-themes github-light github-dark","import { useForm, useWizard, gate } from 'attaform'\n\nconst consentSchema = z.object({ accepted: z.literal(true) })\nconst consent = useForm({\n  schema: consentSchema,\n  defaultValues: { accepted: false },\n  key: 'consent',\n})\n\nconst wizard = useWizard({ steps: [gate(consent), shipping, payment] })\n","ts","",[19,49,50,70,77,112,128,134,146,158,164,169],{"__ignoreMap":47},[51,52,55,59,63,66],"span",{"class":53,"line":54},"line",1,[51,56,58],{"class":57},"szBVR","import",[51,60,62],{"class":61},"sVt8B"," { useForm, useWizard, gate } ",[51,64,65],{"class":57},"from",[51,67,69],{"class":68},"sZZnC"," 'attaform'\n",[51,71,73],{"class":53,"line":72},2,[51,74,76],{"emptyLinePlaceholder":75},true,"\n",[51,78,80,83,87,90,93,97,100,103,106,109],{"class":53,"line":79},3,[51,81,82],{"class":57},"const",[51,84,86],{"class":85},"sj4cs"," consentSchema",[51,88,89],{"class":57}," =",[51,91,92],{"class":61}," z.",[51,94,96],{"class":95},"sScJk","object",[51,98,99],{"class":61},"({ accepted: z.",[51,101,102],{"class":95},"literal",[51,104,105],{"class":61},"(",[51,107,108],{"class":85},"true",[51,110,111],{"class":61},") })\n",[51,113,115,117,120,122,125],{"class":53,"line":114},4,[51,116,82],{"class":57},[51,118,119],{"class":85}," consent",[51,121,89],{"class":57},[51,123,124],{"class":95}," useForm",[51,126,127],{"class":61},"({\n",[51,129,131],{"class":53,"line":130},5,[51,132,133],{"class":61},"  schema: consentSchema,\n",[51,135,137,140,143],{"class":53,"line":136},6,[51,138,139],{"class":61},"  defaultValues: { accepted: ",[51,141,142],{"class":85},"false",[51,144,145],{"class":61}," },\n",[51,147,149,152,155],{"class":53,"line":148},7,[51,150,151],{"class":61},"  key: ",[51,153,154],{"class":68},"'consent'",[51,156,157],{"class":61},",\n",[51,159,161],{"class":53,"line":160},8,[51,162,163],{"class":61},"})\n",[51,165,167],{"class":53,"line":166},9,[51,168,76],{"emptyLinePlaceholder":75},[51,170,172,174,177,179,182,185,187],{"class":53,"line":171},10,[51,173,82],{"class":57},[51,175,176],{"class":85}," wizard",[51,178,89],{"class":57},[51,180,181],{"class":95}," useWizard",[51,183,184],{"class":61},"({ steps: [",[51,186,5],{"class":95},[51,188,189],{"class":61},"(consent), shipping, payment] })\n",[16,191,192,195,196,199,200,203,204,207],{},[19,193,194],{},"gate(consent)"," seals ",[19,197,198],{},"shipping"," and ",[19,201,202],{},"payment"," until ",[19,205,206],{},"consent"," is confirmed. Ordering places the gate: put it immediately before what it guards, and everything to its right is gated.",[209,210,212],"h2",{"id":211},"confirmation-not-intent","Confirmation, not intent",[16,214,215,216,220,221,223,224,227],{},"A gate clears on a member form's ",[217,218,219],"strong",{},"clean submit",", not the moment a value passes validation. Checking the consent box makes ",[19,222,206],{}," valid, but the rail stays sealed. Pressing Next (or calling ",[19,225,226],{},"wizard.tryNext()",") submits the form, and that submit is the confirmation that opens the gate.",[16,229,230,231,233],{},"This is deliberate, and it is load-bearing. Keying a gate on a leading value signal reads intuitive and is quietly unsafe: a downstream step could start collecting data the instant a box is ticked, before the user has actually committed. So the box can be checked and unchecked all day; the gate does not move until a real submission lands. There is no predicate to get wrong, because ",[19,232,25],{}," never hands you one.",[209,235,237],{"id":236},"the-freeze-runs-at-the-data-layer","The freeze runs at the data layer",[16,239,240,241,248,249,252,253,256,257,260,261,264],{},"A sealed downstream step is frozen through the same ",[242,243,245],"a",{"href":244},"\u002Fdocs\u002Fcross-cutting-state\u002Fdisabled",[19,246,247],{},"disabled"," channel a ",[19,250,251],{},"useForm({ disabled })"," uses, so every value write no-ops regardless of origin: ",[19,254,255],{},"setValue",", a ",[19,258,259],{},"v-register"," directive, a host component. The wizard also refuses to seat the active step on anything past an uncleared gate, so a deep link, the browser back button, or a stray ",[19,262,263],{},"wizard.goTo(key)"," all redirect to the gate.",[16,266,267,268,272,273,276],{},"Both halves matter. The navigation refusal keeps the user from ",[269,270,271],"em",{},"seeing"," a gated step; the data freeze keeps a gated form from ",[269,274,275],{},"storing"," anything even if some other code reaches it. The guarantee lives in the data, not in a UI guard, so nothing routes around it.",[209,278,280],{"id":279},"clearing-a-gate","Clearing a gate",[16,282,283],{},"How a gate clears depends on what it wraps.",[16,285,286,287,290,291,293,294,296,297,300],{},"A ",[217,288,289],{},"form gate"," (",[19,292,194],{},") clears when its form submits clean. Wire the step's Next button to ",[19,295,226],{},", which submits the active step and advances once that submit settles, so a gate on the active step clears and advances in a single click. A bare ",[19,298,299],{},"wizard.next()"," on a gate step does the same thing: it cannot skip the confirmation.",[16,302,303,304,290,307,310],{},"An ",[217,305,306],{},"affordance gate",[19,308,309],{},"gate('terms')",", a bare string) clears when the user acknowledges it by advancing. Because that acknowledgment is ephemeral, an affordance gate re-prompts every session, which is what you want for a \"you have read this\" screen.",[16,312,313,314,317,318,320,321,324,325,328],{},"A form gate also recognizes a ",[217,315,316],{},"seeded-valid"," member form. If ",[19,319,206],{}," rehydrates already valid, say a returning customer whose acceptance you loaded into ",[19,322,323],{},"defaultValues",", the gate is treated as pre-cleared at mount, so the flow renders open from the first frame and a deep link into a downstream step is honored. Seeding a gate's form valid is an explicit assertion that the prerequisite is already satisfied, so reach for a base-schema value (a ",[19,326,327],{},"z.literal(true)",") rather than a value only a refinement can judge.",[209,330,332],{"id":331},"freeze-after-clear","Freeze after clear",[16,334,335,336,339],{},"Once a gate clears, its own form freezes too. Navigating back to a cleared consent step is a read-only review: the checkbox is there, but it cannot be unchecked, so there is no withdrawal path and no re-lock dance to reason about. ",[19,337,338],{},"wizard.reset()"," reboots the flow and re-gates from scratch.",[209,341,343],{"id":342},"conditional-gates","Conditional gates",[16,345,346,348,349,353],{},[19,347,25],{}," takes no options. A gate that only applies sometimes comes from a function slot, the same slot kind that powers ",[242,350,352],{"href":351},"\u002Fdocs\u002Fmultistep\u002Fpatterns#branching-wizards","branching wizards",":",[37,355],{"label":356,"slug":357},"Conditional gate","kyc-gate",[42,359,361],{"className":44,"code":360,"language":46,"meta":47,"style":47},"const wizard = useWizard({\n  steps: [transfer, () => (transfer.values.amount > 10_000 ? gate(kyc) : undefined), 'review'],\n})\n",[19,362,363,375,415],{"__ignoreMap":47},[51,364,365,367,369,371,373],{"class":53,"line":54},[51,366,82],{"class":57},[51,368,176],{"class":85},[51,370,89],{"class":57},[51,372,181],{"class":95},[51,374,127],{"class":61},[51,376,377,380,383,386,389,392,395,398,401,403,406,409,412],{"class":53,"line":72},[51,378,379],{"class":61},"  steps: [transfer, () ",[51,381,382],{"class":57},"=>",[51,384,385],{"class":61}," (transfer.values.amount ",[51,387,388],{"class":57},">",[51,390,391],{"class":85}," 10_000",[51,393,394],{"class":57}," ?",[51,396,397],{"class":95}," gate",[51,399,400],{"class":61},"(kyc) ",[51,402,353],{"class":57},[51,404,405],{"class":85}," undefined",[51,407,408],{"class":61},"), ",[51,410,411],{"class":68},"'review'",[51,413,414],{"class":61},"],\n",[51,416,417],{"class":53,"line":79},[51,418,163],{"class":61},[16,420,421,422,425,426,429,430,433],{},"A small transfer drops the middle slot entirely (the ",[19,423,424],{},"undefined"," arm), so the flow is two steps with no verification. A large transfer resolves the slot to ",[19,427,428],{},"gate(kyc)",", and ",[19,431,432],{},"review"," seals until the KYC form is submitted. The threshold is live: raise the amount and the gate appears, lower it and the gate drops, all from one expression.",[16,435,436],{},"Because the gate wraps the slot rather than living in a separate policy, the condition and the consequence sit in one place, and there is nothing to keep in sync.",[209,438,440],{"id":439},"composing-with-lazy","Composing with lazy",[16,442,443,199,445,451,452,455,456,459],{},[19,444,25],{},[242,446,448],{"href":447},"\u002Fdocs\u002Fmultistep\u002Fstep-slots#lazy-slots-lazy",[19,449,450],{},"lazy()"," compose in either order. ",[19,453,454],{},"gate(lazy(resolve))"," gates a memoized slot; ",[19,457,458],{},"lazy((ctx) => gate(form))"," memoizes a resolver that produces a gate. Both resolve identically, so you can reach for whichever reads clearer at the call site without a second thought about ordering.",[209,461,463],{"id":462},"rendering-the-lock","Rendering the lock",[16,465,466,467,470],{},"Every gated step reports ",[19,468,469],{},"wizard.statuses[key].locked === true",", so a progress rail can render a sealed step with a lock icon and a disabled button:",[42,472,476],{"className":473,"code":474,"language":475,"meta":47,"style":47},"language-vue shiki shiki-themes github-light github-dark","\u003Cbutton\n  v-for=\"step in wizard.steps\"\n  :key=\"step.key\"\n  type=\"button\"\n  :disabled=\"wizard.statuses[step.key]?.locked === true\"\n  @click=\"wizard.goTo(step.key)\"\n>\n  {{ step.key }}\n\u003C\u002Fbutton>\n","vue",[19,477,478,487,510,527,537,558,581,586,591],{"__ignoreMap":47},[51,479,480,483],{"class":53,"line":54},[51,481,482],{"class":61},"\u003C",[51,484,486],{"class":485},"s9eBZ","button\n",[51,488,489,492,495,498,501,504,507],{"class":53,"line":72},[51,490,491],{"class":57},"  v-for",[51,493,494],{"class":61},"=",[51,496,497],{"class":68},"\"",[51,499,500],{"class":61},"step ",[51,502,503],{"class":57},"in",[51,505,506],{"class":61}," wizard.steps",[51,508,509],{"class":68},"\"\n",[51,511,512,515,518,520,522,525],{"class":53,"line":79},[51,513,514],{"class":61},"  :",[51,516,517],{"class":95},"key",[51,519,494],{"class":61},[51,521,497],{"class":68},[51,523,524],{"class":61},"step.key",[51,526,509],{"class":68},[51,528,529,532,534],{"class":53,"line":114},[51,530,531],{"class":95},"  type",[51,533,494],{"class":61},[51,535,536],{"class":68},"\"button\"\n",[51,538,539,541,543,545,547,550,553,556],{"class":53,"line":130},[51,540,514],{"class":61},[51,542,247],{"class":95},[51,544,494],{"class":61},[51,546,497],{"class":68},[51,548,549],{"class":61},"wizard.statuses[step.key]?.locked ",[51,551,552],{"class":57},"===",[51,554,555],{"class":85}," true",[51,557,509],{"class":68},[51,559,560,563,566,568,570,573,576,579],{"class":53,"line":136},[51,561,562],{"class":61},"  @",[51,564,565],{"class":95},"click",[51,567,494],{"class":61},[51,569,497],{"class":68},[51,571,572],{"class":61},"wizard.",[51,574,575],{"class":95},"goTo",[51,577,578],{"class":61},"(step.key)",[51,580,509],{"class":68},[51,582,583],{"class":53,"line":148},[51,584,585],{"class":61},">\n",[51,587,588],{"class":53,"line":160},[51,589,590],{"class":61},"  {{ step.key }}\n",[51,592,593,596,599],{"class":53,"line":166},[51,594,595],{"class":61},"\u003C\u002F",[51,597,598],{"class":485},"button",[51,600,585],{"class":61},[16,602,603,604,610,611,614],{},"The whole-wizard ",[242,605,607],{"href":606},"\u002Fdocs\u002Fmultistep\u002Fuse-wizard",[19,608,609],{},"wizard.handleSubmit"," honors the same guarantee: it refuses to complete while any gate is uncleared, even if every form happens to validate, and routes the attempt to the gate through its ",[19,612,613],{},"onError"," callback.",[209,616,618],{"id":617},"where-to-next","Where to next",[620,621,622,633,640,647],"ul",{},[623,624,625,629,630,632],"li",{},[242,626,628],{"href":627},"\u002Fdocs\u002Fmultistep\u002Fstep-slots","Step slots"," for the four slot kinds ",[19,631,25],{}," wraps.",[623,634,635,639],{},[242,636,638],{"href":637},"\u002Fdocs\u002Fmultistep\u002Fpatterns","Patterns"," for the hard-prerequisite pattern in context.",[623,641,642,646],{},[242,643,644],{"href":244},[19,645,247],{}," for the data-freeze channel the gate drives.",[623,648,649,654,655,429,658,661],{},[242,650,651],{"href":606},[19,652,653],{},"useWizard"," for navigation, ",[19,656,657],{},"tryNext",[19,659,660],{},"handleSubmit",".",[663,664,665],"style",{},"html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .s9eBZ, html code.shiki .s9eBZ{--shiki-default:#22863A;--shiki-dark:#85E89D}",{"title":47,"searchDepth":72,"depth":72,"links":667},[668,669,670,671,672,673,674,675],{"id":211,"depth":72,"text":212},{"id":236,"depth":72,"text":237},{"id":279,"depth":72,"text":280},{"id":331,"depth":72,"text":332},{"id":342,"depth":72,"text":343},{"id":439,"depth":72,"text":440},{"id":462,"depth":72,"text":463},{"id":617,"depth":72,"text":618},"gate(step) turns a wizard step into a hard prerequisite. Everything after it stays sealed and frozen until the gate clears on the wrapped form's clean submit, never on intent.","md",{},[680,683,686,689],{"label":681,"value":682},"Category","Concept",{"label":684,"value":685},"Clears on","a member form's clean submit",{"label":687,"value":688},"Downstream","frozen + unreachable",{"label":690,"value":691,"kind":19},"Composes","gate(lazy(s)) === lazy(gate(s))","\u002Fdocs\u002Fmultistep\u002Fgate",{"title":5,"description":676},null,"docs\u002Fmultistep\u002Fgate","IlYEJkokMmPi8PmkaU--Ah82mNF3b6tLQHMp0VsqyAY",1784252105906]