[{"data":1,"prerenderedAt":1851},["ShallowReactive",2],{"content-\u002Fdocs\u002Fmultistep\u002Fpatterns":3},{"id":4,"title":5,"body":6,"description":1831,"extension":1832,"meta":1833,"metaRows":1834,"navigation":95,"path":1846,"seo":1847,"source":1848,"stem":1849,"__hash__":1850},"docs\u002Fdocs\u002Fmultistep\u002Fpatterns.md","Patterns",{"type":7,"value":8,"toc":1820},"minimark",[9,13,29,32,37,48,276,281,340,343,389,398,402,409,730,753,759,824,842,846,853,1046,1075,1085,1092,1098,1141,1154,1158,1168,1316,1342,1363,1367,1374,1488,1491,1672,1682,1693,1697,1708,1743,1757,1761,1816],[10,11,5],"h1",{"id":12},"patterns",[14,15,16],"blockquote",{},[17,18,19,20,24,25,28],"p",{},"Each step of a wizard is a regular ",[21,22,23],"code",{},"useForm"," call. The wizard is a thin orchestrator over the ",[21,26,27],{},"steps"," array. Linear flows, branching graphs, dynamic terminals, per-step persistence, and per-step undo all compose through the same primitives the rest of Attaform exposes, without special wizard knobs.",[30,31],"docs-meta-table",{},[33,34,36],"h2",{"id":35},"linear-wizards","Linear wizards",[17,38,39,40,43,44,47],{},"The default shape: a list of forms in reading order. ",[21,41,42],{},"wizard.next()"," validates the active step before advancing; ",[21,45,46],{},"wizard.back()"," retreats. Out-of-bounds calls dev-warn and no-op.",[49,50,55],"pre",{"className":51,"code":52,"language":53,"meta":54,"style":54},"language-ts shiki shiki-themes github-light github-dark","import { useForm, useWizard } from 'attaform\u002Fzod'\nimport { z } from 'zod'\n\nconst accountSchema = z.object({ email: z.email() })\nconst profileSchema = z.object({ name: z.string().min(1) })\nconst reviewSchema = z.object({ tos: z.literal(true) })\n\nconst account = useForm({ schema: accountSchema, key: 'signup-account' })\nconst profile = useForm({ schema: profileSchema, key: 'signup-profile' })\nconst review = useForm({ schema: reviewSchema, key: 'signup-review' })\n\nconst wizard = useWizard({ steps: [account, profile, review] })\n","ts","",[21,56,57,77,90,97,126,161,188,193,215,235,255,260],{"__ignoreMap":54},[58,59,62,66,70,73],"span",{"class":60,"line":61},"line",1,[58,63,65],{"class":64},"szBVR","import",[58,67,69],{"class":68},"sVt8B"," { useForm, useWizard } ",[58,71,72],{"class":64},"from",[58,74,76],{"class":75},"sZZnC"," 'attaform\u002Fzod'\n",[58,78,80,82,85,87],{"class":60,"line":79},2,[58,81,65],{"class":64},[58,83,84],{"class":68}," { z } ",[58,86,72],{"class":64},[58,88,89],{"class":75}," 'zod'\n",[58,91,93],{"class":60,"line":92},3,[58,94,96],{"emptyLinePlaceholder":95},true,"\n",[58,98,100,103,107,110,113,117,120,123],{"class":60,"line":99},4,[58,101,102],{"class":64},"const",[58,104,106],{"class":105},"sj4cs"," accountSchema",[58,108,109],{"class":64}," =",[58,111,112],{"class":68}," z.",[58,114,116],{"class":115},"sScJk","object",[58,118,119],{"class":68},"({ email: z.",[58,121,122],{"class":115},"email",[58,124,125],{"class":68},"() })\n",[58,127,129,131,134,136,138,140,143,146,149,152,155,158],{"class":60,"line":128},5,[58,130,102],{"class":64},[58,132,133],{"class":105}," profileSchema",[58,135,109],{"class":64},[58,137,112],{"class":68},[58,139,116],{"class":115},[58,141,142],{"class":68},"({ name: z.",[58,144,145],{"class":115},"string",[58,147,148],{"class":68},"().",[58,150,151],{"class":115},"min",[58,153,154],{"class":68},"(",[58,156,157],{"class":105},"1",[58,159,160],{"class":68},") })\n",[58,162,164,166,169,171,173,175,178,181,183,186],{"class":60,"line":163},6,[58,165,102],{"class":64},[58,167,168],{"class":105}," reviewSchema",[58,170,109],{"class":64},[58,172,112],{"class":68},[58,174,116],{"class":115},[58,176,177],{"class":68},"({ tos: z.",[58,179,180],{"class":115},"literal",[58,182,154],{"class":68},[58,184,185],{"class":105},"true",[58,187,160],{"class":68},[58,189,191],{"class":60,"line":190},7,[58,192,96],{"emptyLinePlaceholder":95},[58,194,196,198,201,203,206,209,212],{"class":60,"line":195},8,[58,197,102],{"class":64},[58,199,200],{"class":105}," account",[58,202,109],{"class":64},[58,204,205],{"class":115}," useForm",[58,207,208],{"class":68},"({ schema: accountSchema, key: ",[58,210,211],{"class":75},"'signup-account'",[58,213,214],{"class":68}," })\n",[58,216,218,220,223,225,227,230,233],{"class":60,"line":217},9,[58,219,102],{"class":64},[58,221,222],{"class":105}," profile",[58,224,109],{"class":64},[58,226,205],{"class":115},[58,228,229],{"class":68},"({ schema: profileSchema, key: ",[58,231,232],{"class":75},"'signup-profile'",[58,234,214],{"class":68},[58,236,238,240,243,245,247,250,253],{"class":60,"line":237},10,[58,239,102],{"class":64},[58,241,242],{"class":105}," review",[58,244,109],{"class":64},[58,246,205],{"class":115},[58,248,249],{"class":68},"({ schema: reviewSchema, key: ",[58,251,252],{"class":75},"'signup-review'",[58,254,214],{"class":68},[58,256,258],{"class":60,"line":257},11,[58,259,96],{"emptyLinePlaceholder":95},[58,261,263,265,268,270,273],{"class":60,"line":262},12,[58,264,102],{"class":64},[58,266,267],{"class":105}," wizard",[58,269,109],{"class":64},[58,271,272],{"class":115}," useWizard",[58,274,275],{"class":68},"({ steps: [account, profile, review] })\n",[17,277,278,280],{},[21,279,42],{}," validates the active form for you, so the template wires straight to it:",[49,282,286],{"className":283,"code":284,"language":285,"meta":54,"style":54},"language-vue shiki shiki-themes github-light github-dark","\u003Cbutton v-if=\"wizard.canAdvance\" @click=\"wizard.next()\">Next\u003C\u002Fbutton>\n","vue",[21,287,288],{"__ignoreMap":54},[58,289,290,293,297,300,303,306,309,311,314,317,319,321,324,327,330,332,335,337],{"class":60,"line":61},[58,291,292],{"class":68},"\u003C",[58,294,296],{"class":295},"s9eBZ","button",[58,298,299],{"class":64}," v-if",[58,301,302],{"class":68},"=",[58,304,305],{"class":75},"\"",[58,307,308],{"class":68},"wizard.canAdvance",[58,310,305],{"class":75},[58,312,313],{"class":68}," @",[58,315,316],{"class":115},"click",[58,318,302],{"class":68},[58,320,305],{"class":75},[58,322,323],{"class":68},"wizard.",[58,325,326],{"class":115},"next",[58,328,329],{"class":68},"()",[58,331,305],{"class":75},[58,333,334],{"class":68},">Next\u003C\u002F",[58,336,296],{"class":295},[58,338,339],{"class":68},">\n",[17,341,342],{},"Mix in affordance steps (bare strings) wherever the flow benefits from a screen that presents rather than collects:",[49,344,346],{"className":51,"code":345,"language":53,"meta":54,"style":54},"const wizard = useWizard({\n  steps: ['welcome', account, profile, 'review-summary', review, 'congrats'],\n})\n",[21,347,348,361,384],{"__ignoreMap":54},[58,349,350,352,354,356,358],{"class":60,"line":61},[58,351,102],{"class":64},[58,353,267],{"class":105},[58,355,109],{"class":64},[58,357,272],{"class":115},[58,359,360],{"class":68},"({\n",[58,362,363,366,369,372,375,378,381],{"class":60,"line":79},[58,364,365],{"class":68},"  steps: [",[58,367,368],{"class":75},"'welcome'",[58,370,371],{"class":68},", account, profile, ",[58,373,374],{"class":75},"'review-summary'",[58,376,377],{"class":68},", review, ",[58,379,380],{"class":75},"'congrats'",[58,382,383],{"class":68},"],\n",[58,385,386],{"class":60,"line":92},[58,387,388],{"class":68},"})\n",[17,390,391,392,397],{},"See ",[393,394,396],"a",{"href":395},"\u002Fdocs\u002Fmultistep\u002Fstep-slots","Step slots"," for the affordance-slot story.",[33,399,401],{"id":400},"branching-wizards","Branching wizards",[17,403,404,405,408],{},"When the next step depends on a live value on an earlier form, use a function slot. The slot is a ",[21,406,407],{},"(ctx) => Form | string | undefined"," callback that re-evaluates reactively as its tracked reads change:",[49,410,412],{"className":51,"code":411,"language":53,"meta":54,"style":54},"import { useForm, useWizard } from 'attaform\u002Fzod'\nimport { z } from 'zod'\n\nconst accountSchema = z.object({ kind: z.enum(['user', 'organization']) })\nconst userProfileSchema = z.object({ name: z.string().min(1) })\nconst orgSchema = z.object({ orgName: z.string().min(1), seats: z.number().int().positive() })\nconst reviewSchema = z.object({ tos: z.literal(true) })\n\nconst account = useForm({ schema: accountSchema, key: 'signup-account' })\nconst userProfile = useForm({ schema: userProfileSchema, key: 'signup-user' })\nconst orgProfile = useForm({ schema: orgSchema, key: 'signup-org' })\nconst review = useForm({ schema: reviewSchema, key: 'signup-review' })\n\nconst wizard = useWizard({\n  steps: [\n    account,\n    (ctx) =>\n      ctx.forms['signup-account'].values.kind === 'organization' ? orgProfile : userProfile,\n    review,\n  ],\n})\n",[21,413,414,424,434,438,471,498,542,564,568,584,603,622,638,643,656,662,668,684,713,719,725],{"__ignoreMap":54},[58,415,416,418,420,422],{"class":60,"line":61},[58,417,65],{"class":64},[58,419,69],{"class":68},[58,421,72],{"class":64},[58,423,76],{"class":75},[58,425,426,428,430,432],{"class":60,"line":79},[58,427,65],{"class":64},[58,429,84],{"class":68},[58,431,72],{"class":64},[58,433,89],{"class":75},[58,435,436],{"class":60,"line":92},[58,437,96],{"emptyLinePlaceholder":95},[58,439,440,442,444,446,448,450,453,456,459,462,465,468],{"class":60,"line":99},[58,441,102],{"class":64},[58,443,106],{"class":105},[58,445,109],{"class":64},[58,447,112],{"class":68},[58,449,116],{"class":115},[58,451,452],{"class":68},"({ kind: z.",[58,454,455],{"class":115},"enum",[58,457,458],{"class":68},"([",[58,460,461],{"class":75},"'user'",[58,463,464],{"class":68},", ",[58,466,467],{"class":75},"'organization'",[58,469,470],{"class":68},"]) })\n",[58,472,473,475,478,480,482,484,486,488,490,492,494,496],{"class":60,"line":128},[58,474,102],{"class":64},[58,476,477],{"class":105}," userProfileSchema",[58,479,109],{"class":64},[58,481,112],{"class":68},[58,483,116],{"class":115},[58,485,142],{"class":68},[58,487,145],{"class":115},[58,489,148],{"class":68},[58,491,151],{"class":115},[58,493,154],{"class":68},[58,495,157],{"class":105},[58,497,160],{"class":68},[58,499,500,502,505,507,509,511,514,516,518,520,522,524,527,530,532,535,537,540],{"class":60,"line":163},[58,501,102],{"class":64},[58,503,504],{"class":105}," orgSchema",[58,506,109],{"class":64},[58,508,112],{"class":68},[58,510,116],{"class":115},[58,512,513],{"class":68},"({ orgName: z.",[58,515,145],{"class":115},[58,517,148],{"class":68},[58,519,151],{"class":115},[58,521,154],{"class":68},[58,523,157],{"class":105},[58,525,526],{"class":68},"), seats: z.",[58,528,529],{"class":115},"number",[58,531,148],{"class":68},[58,533,534],{"class":115},"int",[58,536,148],{"class":68},[58,538,539],{"class":115},"positive",[58,541,125],{"class":68},[58,543,544,546,548,550,552,554,556,558,560,562],{"class":60,"line":190},[58,545,102],{"class":64},[58,547,168],{"class":105},[58,549,109],{"class":64},[58,551,112],{"class":68},[58,553,116],{"class":115},[58,555,177],{"class":68},[58,557,180],{"class":115},[58,559,154],{"class":68},[58,561,185],{"class":105},[58,563,160],{"class":68},[58,565,566],{"class":60,"line":195},[58,567,96],{"emptyLinePlaceholder":95},[58,569,570,572,574,576,578,580,582],{"class":60,"line":217},[58,571,102],{"class":64},[58,573,200],{"class":105},[58,575,109],{"class":64},[58,577,205],{"class":115},[58,579,208],{"class":68},[58,581,211],{"class":75},[58,583,214],{"class":68},[58,585,586,588,591,593,595,598,601],{"class":60,"line":237},[58,587,102],{"class":64},[58,589,590],{"class":105}," userProfile",[58,592,109],{"class":64},[58,594,205],{"class":115},[58,596,597],{"class":68},"({ schema: userProfileSchema, key: ",[58,599,600],{"class":75},"'signup-user'",[58,602,214],{"class":68},[58,604,605,607,610,612,614,617,620],{"class":60,"line":257},[58,606,102],{"class":64},[58,608,609],{"class":105}," orgProfile",[58,611,109],{"class":64},[58,613,205],{"class":115},[58,615,616],{"class":68},"({ schema: orgSchema, key: ",[58,618,619],{"class":75},"'signup-org'",[58,621,214],{"class":68},[58,623,624,626,628,630,632,634,636],{"class":60,"line":262},[58,625,102],{"class":64},[58,627,242],{"class":105},[58,629,109],{"class":64},[58,631,205],{"class":115},[58,633,249],{"class":68},[58,635,252],{"class":75},[58,637,214],{"class":68},[58,639,641],{"class":60,"line":640},13,[58,642,96],{"emptyLinePlaceholder":95},[58,644,646,648,650,652,654],{"class":60,"line":645},14,[58,647,102],{"class":64},[58,649,267],{"class":105},[58,651,109],{"class":64},[58,653,272],{"class":115},[58,655,360],{"class":68},[58,657,659],{"class":60,"line":658},15,[58,660,661],{"class":68},"  steps: [\n",[58,663,665],{"class":60,"line":664},16,[58,666,667],{"class":68},"    account,\n",[58,669,671,674,678,681],{"class":60,"line":670},17,[58,672,673],{"class":68},"    (",[58,675,677],{"class":676},"s4XuR","ctx",[58,679,680],{"class":68},") ",[58,682,683],{"class":64},"=>\n",[58,685,687,690,692,695,698,701,704,707,710],{"class":60,"line":686},18,[58,688,689],{"class":68},"      ctx.forms[",[58,691,211],{"class":75},[58,693,694],{"class":68},"].values.kind ",[58,696,697],{"class":64},"===",[58,699,700],{"class":75}," 'organization'",[58,702,703],{"class":64}," ?",[58,705,706],{"class":68}," orgProfile ",[58,708,709],{"class":64},":",[58,711,712],{"class":68}," userProfile,\n",[58,714,716],{"class":60,"line":715},19,[58,717,718],{"class":68},"    review,\n",[58,720,722],{"class":60,"line":721},20,[58,723,724],{"class":68},"  ],\n",[58,726,728],{"class":60,"line":727},21,[58,729,388],{"class":68},[17,731,732,733,735,736,739,740,742,743,464,746,464,749,752],{},"When the user picks ",[21,734,467],{}," on the account step, the function slot resolves to ",[21,737,738],{},"orgProfile",". Toggling back to ",[21,741,461],{}," swaps the resolved form. ",[21,744,745],{},"wizard.steps",[21,747,748],{},"wizard.forms",[21,750,751],{},"wizard.statuses",", and the progress rail all follow along.",[17,754,755,756,709],{},"For typed reads, close over the original form ref instead of routing through ",[21,757,758],{},"ctx.forms",[49,760,762],{"className":51,"code":761,"language":53,"meta":54,"style":54},"const wizard = useWizard({\n  steps: [\n    account,\n    () => (account.values.kind === 'organization' ? orgProfile : userProfile), \u002F\u002F typed!\n    review,\n  ],\n})\n",[21,763,764,776,780,784,812,816,820],{"__ignoreMap":54},[58,765,766,768,770,772,774],{"class":60,"line":61},[58,767,102],{"class":64},[58,769,267],{"class":105},[58,771,109],{"class":64},[58,773,272],{"class":115},[58,775,360],{"class":68},[58,777,778],{"class":60,"line":79},[58,779,661],{"class":68},[58,781,782],{"class":60,"line":92},[58,783,667],{"class":68},[58,785,786,789,792,795,797,799,801,803,805,808],{"class":60,"line":99},[58,787,788],{"class":68},"    () ",[58,790,791],{"class":64},"=>",[58,793,794],{"class":68}," (account.values.kind ",[58,796,697],{"class":64},[58,798,700],{"class":75},[58,800,703],{"class":64},[58,802,706],{"class":68},[58,804,709],{"class":64},[58,806,807],{"class":68}," userProfile), ",[58,809,811],{"class":810},"sJ8bj","\u002F\u002F typed!\n",[58,813,814],{"class":60,"line":128},[58,815,718],{"class":68},[58,817,818],{"class":60,"line":163},[58,819,724],{"class":68},[58,821,822],{"class":60,"line":190},[58,823,388],{"class":68},[17,825,826,829,830,833,834,837,838,841],{},[21,827,828],{},"account.values.kind"," carries the Zod-derived ",[21,831,832],{},"'user' | 'organization'"," type through the predicate, where ",[21,835,836],{},"ctx.forms['signup-account'].values.kind"," reads as ",[21,839,840],{},"unknown",". Both work at runtime; the closed-over ref keeps the IDE happy.",[33,843,845],{"id":844},"dynamic-terminals","Dynamic terminals",[17,847,848,849,852],{},"A function slot that returns ",[21,850,851],{},"undefined"," drops its position from the compiled list. Combine that with a terminal step to get a wizard that ends early on a live condition:",[49,854,856],{"className":51,"code":855,"language":53,"meta":54,"style":54},"const accountSchema = z.object({ email: z.email(), willTakeSurvey: z.boolean() })\nconst surveySchema = z.object({ rating: z.number().int().min(1).max(5) })\nconst reviewSchema = z.object({ tos: z.literal(true) })\n\nconst account = useForm({ schema: accountSchema, key: 'signup-account' })\nconst survey = useForm({ schema: surveySchema, key: 'signup-survey' })\nconst review = useForm({ schema: reviewSchema, key: 'signup-review' })\n\nconst wizard = useWizard({\n  steps: [account, () => (account.values.willTakeSurvey ? survey : undefined), review],\n})\n",[21,857,858,882,925,947,951,967,986,1002,1006,1018,1042],{"__ignoreMap":54},[58,859,860,862,864,866,868,870,872,874,877,880],{"class":60,"line":61},[58,861,102],{"class":64},[58,863,106],{"class":105},[58,865,109],{"class":64},[58,867,112],{"class":68},[58,869,116],{"class":115},[58,871,119],{"class":68},[58,873,122],{"class":115},[58,875,876],{"class":68},"(), willTakeSurvey: z.",[58,878,879],{"class":115},"boolean",[58,881,125],{"class":68},[58,883,884,886,889,891,893,895,898,900,902,904,906,908,910,912,915,918,920,923],{"class":60,"line":79},[58,885,102],{"class":64},[58,887,888],{"class":105}," surveySchema",[58,890,109],{"class":64},[58,892,112],{"class":68},[58,894,116],{"class":115},[58,896,897],{"class":68},"({ rating: z.",[58,899,529],{"class":115},[58,901,148],{"class":68},[58,903,534],{"class":115},[58,905,148],{"class":68},[58,907,151],{"class":115},[58,909,154],{"class":68},[58,911,157],{"class":105},[58,913,914],{"class":68},").",[58,916,917],{"class":115},"max",[58,919,154],{"class":68},[58,921,922],{"class":105},"5",[58,924,160],{"class":68},[58,926,927,929,931,933,935,937,939,941,943,945],{"class":60,"line":92},[58,928,102],{"class":64},[58,930,168],{"class":105},[58,932,109],{"class":64},[58,934,112],{"class":68},[58,936,116],{"class":115},[58,938,177],{"class":68},[58,940,180],{"class":115},[58,942,154],{"class":68},[58,944,185],{"class":105},[58,946,160],{"class":68},[58,948,949],{"class":60,"line":99},[58,950,96],{"emptyLinePlaceholder":95},[58,952,953,955,957,959,961,963,965],{"class":60,"line":128},[58,954,102],{"class":64},[58,956,200],{"class":105},[58,958,109],{"class":64},[58,960,205],{"class":115},[58,962,208],{"class":68},[58,964,211],{"class":75},[58,966,214],{"class":68},[58,968,969,971,974,976,978,981,984],{"class":60,"line":163},[58,970,102],{"class":64},[58,972,973],{"class":105}," survey",[58,975,109],{"class":64},[58,977,205],{"class":115},[58,979,980],{"class":68},"({ schema: surveySchema, key: ",[58,982,983],{"class":75},"'signup-survey'",[58,985,214],{"class":68},[58,987,988,990,992,994,996,998,1000],{"class":60,"line":190},[58,989,102],{"class":64},[58,991,242],{"class":105},[58,993,109],{"class":64},[58,995,205],{"class":115},[58,997,249],{"class":68},[58,999,252],{"class":75},[58,1001,214],{"class":68},[58,1003,1004],{"class":60,"line":195},[58,1005,96],{"emptyLinePlaceholder":95},[58,1007,1008,1010,1012,1014,1016],{"class":60,"line":217},[58,1009,102],{"class":64},[58,1011,267],{"class":105},[58,1013,109],{"class":64},[58,1015,272],{"class":115},[58,1017,360],{"class":68},[58,1019,1020,1023,1025,1028,1031,1034,1036,1039],{"class":60,"line":237},[58,1021,1022],{"class":68},"  steps: [account, () ",[58,1024,791],{"class":64},[58,1026,1027],{"class":68}," (account.values.willTakeSurvey ",[58,1029,1030],{"class":64},"?",[58,1032,1033],{"class":68}," survey ",[58,1035,709],{"class":64},[58,1037,1038],{"class":105}," undefined",[58,1040,1041],{"class":68},"), review],\n",[58,1043,1044],{"class":60,"line":257},[58,1045,388],{"class":68},[17,1047,1048,1049,464,1052,1055,1056,464,1058,464,1061,1063,1064,1067,1068,464,1071,1074],{},"Users who decline the survey see two steps (",[21,1050,1051],{},"account",[21,1053,1054],{},"review","); users who opt in see three (",[21,1057,1051],{},[21,1059,1060],{},"survey",[21,1062,1054],{},"). The function slot re-evaluates whenever ",[21,1065,1066],{},"willTakeSurvey"," flips, so a late toggle is respected on the next navigation. ",[21,1069,1070],{},"wizard.count",[21,1072,1073],{},"wizard.isFinalStep",", and the progress rail recompute against the live list.",[17,1076,1077,1078,1084],{},"For heavier branching (a slot whose resolver is expensive enough that re-evaluating on every wizard mutation produces visible thrash), reach for ",[393,1079,1081],{"href":1080},"\u002Fdocs\u002Fmultistep\u002Fstep-slots#lazy-slots-lazy",[21,1082,1083],{},"lazy()"," instead.",[33,1086,1088,1089],{"id":1087},"manual-jumps-with-goto","Manual jumps with ",[21,1090,1091],{},"goTo",[17,1093,1094,1097],{},[21,1095,1096],{},"wizard.goTo(key)"," skips the validation gate. Use it when the user explicitly clicked a rail item:",[49,1099,1101],{"className":283,"code":1100,"language":285,"meta":54,"style":54},"\u003Cbutton type=\"button\" @click=\"wizard.goTo(step.key)\">Jump to {{ step.key }}\u003C\u002Fbutton>\n",[21,1102,1103],{"__ignoreMap":54},[58,1104,1105,1107,1109,1112,1114,1117,1119,1121,1123,1125,1127,1129,1132,1134,1137,1139],{"class":60,"line":61},[58,1106,292],{"class":68},[58,1108,296],{"class":295},[58,1110,1111],{"class":115}," type",[58,1113,302],{"class":68},[58,1115,1116],{"class":75},"\"button\"",[58,1118,313],{"class":68},[58,1120,316],{"class":115},[58,1122,302],{"class":68},[58,1124,305],{"class":75},[58,1126,323],{"class":68},[58,1128,1091],{"class":115},[58,1130,1131],{"class":68},"(step.key)",[58,1133,305],{"class":75},[58,1135,1136],{"class":68},">Jump to {{ step.key }}\u003C\u002F",[58,1138,296],{"class":295},[58,1140,339],{"class":68},[17,1142,1143,1146,1147,1149,1150,1153],{},[21,1144,1145],{},"wizard.handleSubmit"," catches the upstream validation gaps that ",[21,1148,1091],{}," lets through. Clicking Finish on a step the user jumped to without filling earlier forms validates everything, surfaces every error, and (with ",[21,1151,1152],{},"focusFirstError: true",", the default) jumps the wizard back to the first failing step.",[33,1155,1157],{"id":1156},"per-step-persistence","Per-step persistence",[17,1159,1160,1161,1163,1164,1167],{},"Each step is its own ",[21,1162,23],{}," call, so each step gets its own ",[21,1165,1166],{},"persist"," config. The wizard composes naturally with whatever you wire on each form:",[49,1169,1171],{"className":51,"code":1170,"language":53,"meta":54,"style":54},"const account = useForm({\n  schema: accountSchema,\n  key: 'signup-account',\n  persist: 'local', \u002F\u002F localStorage, namespaced by key\n})\n\nconst profile = useForm({\n  schema: profileSchema,\n  key: 'signup-profile',\n  persist: 'session', \u002F\u002F sessionStorage, separate scope from account\n})\n\nconst review = useForm({\n  schema: reviewSchema,\n  key: 'signup-review',\n  \u002F\u002F No persist; consent stays in-memory only\n})\n\nconst wizard = useWizard({ steps: [account, profile, review] })\n",[21,1172,1173,1185,1190,1200,1213,1217,1221,1233,1238,1246,1258,1262,1266,1278,1283,1291,1296,1300,1304],{"__ignoreMap":54},[58,1174,1175,1177,1179,1181,1183],{"class":60,"line":61},[58,1176,102],{"class":64},[58,1178,200],{"class":105},[58,1180,109],{"class":64},[58,1182,205],{"class":115},[58,1184,360],{"class":68},[58,1186,1187],{"class":60,"line":79},[58,1188,1189],{"class":68},"  schema: accountSchema,\n",[58,1191,1192,1195,1197],{"class":60,"line":92},[58,1193,1194],{"class":68},"  key: ",[58,1196,211],{"class":75},[58,1198,1199],{"class":68},",\n",[58,1201,1202,1205,1208,1210],{"class":60,"line":99},[58,1203,1204],{"class":68},"  persist: ",[58,1206,1207],{"class":75},"'local'",[58,1209,464],{"class":68},[58,1211,1212],{"class":810},"\u002F\u002F localStorage, namespaced by key\n",[58,1214,1215],{"class":60,"line":128},[58,1216,388],{"class":68},[58,1218,1219],{"class":60,"line":163},[58,1220,96],{"emptyLinePlaceholder":95},[58,1222,1223,1225,1227,1229,1231],{"class":60,"line":190},[58,1224,102],{"class":64},[58,1226,222],{"class":105},[58,1228,109],{"class":64},[58,1230,205],{"class":115},[58,1232,360],{"class":68},[58,1234,1235],{"class":60,"line":195},[58,1236,1237],{"class":68},"  schema: profileSchema,\n",[58,1239,1240,1242,1244],{"class":60,"line":217},[58,1241,1194],{"class":68},[58,1243,232],{"class":75},[58,1245,1199],{"class":68},[58,1247,1248,1250,1253,1255],{"class":60,"line":237},[58,1249,1204],{"class":68},[58,1251,1252],{"class":75},"'session'",[58,1254,464],{"class":68},[58,1256,1257],{"class":810},"\u002F\u002F sessionStorage, separate scope from account\n",[58,1259,1260],{"class":60,"line":257},[58,1261,388],{"class":68},[58,1263,1264],{"class":60,"line":262},[58,1265,96],{"emptyLinePlaceholder":95},[58,1267,1268,1270,1272,1274,1276],{"class":60,"line":640},[58,1269,102],{"class":64},[58,1271,242],{"class":105},[58,1273,109],{"class":64},[58,1275,205],{"class":115},[58,1277,360],{"class":68},[58,1279,1280],{"class":60,"line":645},[58,1281,1282],{"class":68},"  schema: reviewSchema,\n",[58,1284,1285,1287,1289],{"class":60,"line":658},[58,1286,1194],{"class":68},[58,1288,252],{"class":75},[58,1290,1199],{"class":68},[58,1292,1293],{"class":60,"line":664},[58,1294,1295],{"class":810},"  \u002F\u002F No persist; consent stays in-memory only\n",[58,1297,1298],{"class":60,"line":670},[58,1299,388],{"class":68},[58,1301,1302],{"class":60,"line":686},[58,1303,96],{"emptyLinePlaceholder":95},[58,1305,1306,1308,1310,1312,1314],{"class":60,"line":715},[58,1307,102],{"class":64},[58,1309,267],{"class":105},[58,1311,109],{"class":64},[58,1313,272],{"class":115},[58,1315,275],{"class":68},[17,1317,1318,1320,1321,1324,1325,1327,1328,1332,1333,1336,1337,1341],{},[21,1319,1051],{}," and ",[21,1322,1323],{},"profile"," survive a refresh; ",[21,1326,1054],{}," doesn't. The wizard itself doesn't persist field state; what ",[1329,1330,1331],"em",{},"it"," persists is the active step, via ",[21,1334,1335],{},"?step=\u003Ckey>"," on the URL by default (see ",[393,1338,1340],{"href":1339},"\u002Fdocs\u002Fmultistep\u002Furl-sync","URL sync","). The two stories compose: per-form persistence keeps the field values, the wizard-level URL sync keeps the navigation cursor.",[17,1343,1344,1345,464,1348,464,1351,1354,1355,1357,1358,1362],{},"Sensitive-name protection applies per-form: ",[21,1346,1347],{},"password",[21,1349,1350],{},"creditCard",[21,1352,1353],{},"ssn",", and friends never persist regardless of the per-step ",[21,1356,1166],{}," setting. See ",[393,1359,1361],{"href":1360},"\u002Fdocs\u002Fpersistence\u002Fsensitive-names","Sensitive-name protection",".",[33,1364,1366],{"id":1365},"per-step-undo","Per-step undo",[17,1368,1369,1370,1373],{},"Same composition: each step gets its own ",[21,1371,1372],{},"history"," chain.",[49,1375,1377],{"className":51,"code":1376,"language":53,"meta":54,"style":54},"const cargo = useForm({\n  schema: cargoSchema,\n  key: 'cargo',\n  history: true, \u002F\u002F unlimited undo \u002F redo across the cargo step\n})\n\nconst billing = useForm({\n  schema: billingSchema,\n  key: 'billing',\n  history: { max: 25 }, \u002F\u002F capped chain\n})\n\nconst wizard = useWizard({ steps: [cargo, billing] })\n",[21,1378,1379,1392,1397,1406,1418,1422,1426,1439,1444,1453,1467,1471,1475],{"__ignoreMap":54},[58,1380,1381,1383,1386,1388,1390],{"class":60,"line":61},[58,1382,102],{"class":64},[58,1384,1385],{"class":105}," cargo",[58,1387,109],{"class":64},[58,1389,205],{"class":115},[58,1391,360],{"class":68},[58,1393,1394],{"class":60,"line":79},[58,1395,1396],{"class":68},"  schema: cargoSchema,\n",[58,1398,1399,1401,1404],{"class":60,"line":92},[58,1400,1194],{"class":68},[58,1402,1403],{"class":75},"'cargo'",[58,1405,1199],{"class":68},[58,1407,1408,1411,1413,1415],{"class":60,"line":99},[58,1409,1410],{"class":68},"  history: ",[58,1412,185],{"class":105},[58,1414,464],{"class":68},[58,1416,1417],{"class":810},"\u002F\u002F unlimited undo \u002F redo across the cargo step\n",[58,1419,1420],{"class":60,"line":128},[58,1421,388],{"class":68},[58,1423,1424],{"class":60,"line":163},[58,1425,96],{"emptyLinePlaceholder":95},[58,1427,1428,1430,1433,1435,1437],{"class":60,"line":190},[58,1429,102],{"class":64},[58,1431,1432],{"class":105}," billing",[58,1434,109],{"class":64},[58,1436,205],{"class":115},[58,1438,360],{"class":68},[58,1440,1441],{"class":60,"line":195},[58,1442,1443],{"class":68},"  schema: billingSchema,\n",[58,1445,1446,1448,1451],{"class":60,"line":217},[58,1447,1194],{"class":68},[58,1449,1450],{"class":75},"'billing'",[58,1452,1199],{"class":68},[58,1454,1455,1458,1461,1464],{"class":60,"line":237},[58,1456,1457],{"class":68},"  history: { max: ",[58,1459,1460],{"class":105},"25",[58,1462,1463],{"class":68}," }, ",[58,1465,1466],{"class":810},"\u002F\u002F capped chain\n",[58,1468,1469],{"class":60,"line":257},[58,1470,388],{"class":68},[58,1472,1473],{"class":60,"line":262},[58,1474,96],{"emptyLinePlaceholder":95},[58,1476,1477,1479,1481,1483,1485],{"class":60,"line":640},[58,1478,102],{"class":64},[58,1480,267],{"class":105},[58,1482,109],{"class":64},[58,1484,272],{"class":115},[58,1486,1487],{"class":68},"({ steps: [cargo, billing] })\n",[17,1489,1490],{},"A keyboard shortcut bound to the active step:",[49,1492,1494],{"className":283,"code":1493,"language":285,"meta":54,"style":54},"\u003Cscript setup lang=\"ts\">\n  function onKeydown(event: KeyboardEvent) {\n    if (!wizard.activeForm) return\n    if (event.metaKey && event.key === 'z') {\n      event.shiftKey ? wizard.activeForm.history.redo() : wizard.activeForm.history.undo()\n    }\n  }\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n  \u003Cdiv @keydown=\"onKeydown\">\n    \u003C!-- step content -->\n  \u003C\u002Fdiv>\n\u003C\u002Ftemplate>\n",[21,1495,1496,1516,1537,1554,1574,1600,1605,1610,1619,1623,1632,1650,1655,1664],{"__ignoreMap":54},[58,1497,1498,1500,1503,1506,1509,1511,1514],{"class":60,"line":61},[58,1499,292],{"class":68},[58,1501,1502],{"class":295},"script",[58,1504,1505],{"class":115}," setup",[58,1507,1508],{"class":115}," lang",[58,1510,302],{"class":68},[58,1512,1513],{"class":75},"\"ts\"",[58,1515,339],{"class":68},[58,1517,1518,1521,1524,1526,1529,1531,1534],{"class":60,"line":79},[58,1519,1520],{"class":64},"  function",[58,1522,1523],{"class":115}," onKeydown",[58,1525,154],{"class":68},[58,1527,1528],{"class":676},"event",[58,1530,709],{"class":64},[58,1532,1533],{"class":115}," KeyboardEvent",[58,1535,1536],{"class":68},") {\n",[58,1538,1539,1542,1545,1548,1551],{"class":60,"line":92},[58,1540,1541],{"class":64},"    if",[58,1543,1544],{"class":68}," (",[58,1546,1547],{"class":64},"!",[58,1549,1550],{"class":68},"wizard.activeForm) ",[58,1552,1553],{"class":64},"return\n",[58,1555,1556,1558,1561,1564,1567,1569,1572],{"class":60,"line":99},[58,1557,1541],{"class":64},[58,1559,1560],{"class":68}," (event.metaKey ",[58,1562,1563],{"class":64},"&&",[58,1565,1566],{"class":68}," event.key ",[58,1568,697],{"class":64},[58,1570,1571],{"class":75}," 'z'",[58,1573,1536],{"class":68},[58,1575,1576,1579,1581,1584,1587,1590,1592,1594,1597],{"class":60,"line":128},[58,1577,1578],{"class":68},"      event.shiftKey ",[58,1580,1030],{"class":64},[58,1582,1583],{"class":68}," wizard.activeForm.history.",[58,1585,1586],{"class":115},"redo",[58,1588,1589],{"class":68},"() ",[58,1591,709],{"class":64},[58,1593,1583],{"class":68},[58,1595,1596],{"class":115},"undo",[58,1598,1599],{"class":68},"()\n",[58,1601,1602],{"class":60,"line":163},[58,1603,1604],{"class":68},"    }\n",[58,1606,1607],{"class":60,"line":190},[58,1608,1609],{"class":68},"  }\n",[58,1611,1612,1615,1617],{"class":60,"line":195},[58,1613,1614],{"class":68},"\u003C\u002F",[58,1616,1502],{"class":295},[58,1618,339],{"class":68},[58,1620,1621],{"class":60,"line":217},[58,1622,96],{"emptyLinePlaceholder":95},[58,1624,1625,1627,1630],{"class":60,"line":237},[58,1626,292],{"class":68},[58,1628,1629],{"class":295},"template",[58,1631,339],{"class":68},[58,1633,1634,1637,1640,1643,1645,1648],{"class":60,"line":257},[58,1635,1636],{"class":68},"  \u003C",[58,1638,1639],{"class":295},"div",[58,1641,1642],{"class":115}," @keydown",[58,1644,302],{"class":68},[58,1646,1647],{"class":75},"\"onKeydown\"",[58,1649,339],{"class":68},[58,1651,1652],{"class":60,"line":262},[58,1653,1654],{"class":810},"    \u003C!-- step content -->\n",[58,1656,1657,1660,1662],{"class":60,"line":640},[58,1658,1659],{"class":68},"  \u003C\u002F",[58,1661,1639],{"class":295},[58,1663,339],{"class":68},[58,1665,1666,1668,1670],{"class":60,"line":645},[58,1667,1614],{"class":68},[58,1669,1629],{"class":295},[58,1671,339],{"class":68},[17,1673,1674,1677,1678,1681],{},[21,1675,1676],{},"wizard.activeForm"," is identity-equal to the form in ",[21,1679,1680],{},"wizard.forms[wizard.currentStep]",", so undo \u002F redo dispatches to the right chain.",[17,1683,1684,1685,1688,1689,1692],{},"Each step's history is independent: undoing on the ",[21,1686,1687],{},"cargo"," step doesn't retreat changes the user made on ",[21,1690,1691],{},"billing",". That matches the user's mental model: \"undo what I just typed here,\" not \"undo the entire flow.\"",[33,1694,1696],{"id":1695},"cross-component-access","Cross-component access",[17,1698,1699,1700,1703,1704,1707],{},"Pass a ",[21,1701,1702],{},"key"," to ",[21,1705,1706],{},"useWizard"," so a deep-tree component (a floating finish button, a sticky progress rail) can reach the same wizard without prop-threading:",[49,1709,1711],{"className":51,"code":1710,"language":53,"meta":54,"style":54},"const wizard = useWizard({\n  steps: [account, profile, review],\n  key: 'signup',\n})\n",[21,1712,1713,1725,1730,1739],{"__ignoreMap":54},[58,1714,1715,1717,1719,1721,1723],{"class":60,"line":61},[58,1716,102],{"class":64},[58,1718,267],{"class":105},[58,1720,109],{"class":64},[58,1722,272],{"class":115},[58,1724,360],{"class":68},[58,1726,1727],{"class":60,"line":79},[58,1728,1729],{"class":68},"  steps: [account, profile, review],\n",[58,1731,1732,1734,1737],{"class":60,"line":92},[58,1733,1194],{"class":68},[58,1735,1736],{"class":75},"'signup'",[58,1738,1199],{"class":68},[58,1740,1741],{"class":60,"line":99},[58,1742,388],{"class":68},[17,1744,1745,1746,1749,1750,1756],{},"A descendant component reaches it via ",[21,1747,1748],{},"injectWizard('signup')",". See ",[393,1751,1753],{"href":1752},"\u002Fdocs\u002Fmultistep\u002Finject-wizard",[21,1754,1755],{},"injectWizard"," for the cross-component story (ambient resolution, keyed lookup, null-on-miss).",[33,1758,1760],{"id":1759},"where-to-next","Where to next",[1762,1763,1764,1780,1787,1794,1802,1809],"ul",{},[1765,1766,1767,1772,1773,1776,1777,1362],"li",{},[393,1768,1770],{"href":1769},"\u002Fdocs\u002Fmultistep\u002Fuse-wizard",[21,1771,1706],{}," for the navigation surface, ",[21,1774,1775],{},"activeForm",", and ",[21,1778,1779],{},"handleSubmit",[1765,1781,1782,1784,1785,914],{},[393,1783,396],{"href":395}," for the four slot kinds (form, string, function, ",[21,1786,1083],{},[1765,1788,1789,1793],{},[393,1790,1791],{"href":1752},[21,1792,1755],{}," for cross-component access to the wizard handle.",[1765,1795,1796,1798,1799,1801],{},[393,1797,1340],{"href":1339}," for wizard-level ",[21,1800,1335],{}," round-tripping.",[1765,1803,1804,1808],{},[393,1805,1807],{"href":1806},"\u002Fdocs\u002Fpersistence\u002Fper-field-opt-in","Per-field opt-in"," for the per-form persistence story.",[1765,1810,1811,1815],{},[393,1812,1814],{"href":1813},"\u002Fdocs\u002Fcross-cutting-state\u002Fundo-redo","Undo & redo"," for the per-form history chain.",[1817,1818,1819],"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}html pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}",{"title":54,"searchDepth":79,"depth":79,"links":1821},[1822,1823,1824,1825,1827,1828,1829,1830],{"id":35,"depth":79,"text":36},{"id":400,"depth":79,"text":401},{"id":844,"depth":79,"text":845},{"id":1087,"depth":79,"text":1826},"Manual jumps with goTo",{"id":1156,"depth":79,"text":1157},{"id":1365,"depth":79,"text":1366},{"id":1695,"depth":79,"text":1696},{"id":1759,"depth":79,"text":1760},"Idiomatic wizard patterns. Linear flows, branching with function slots, dynamic terminals, per-step persistence, per-step undo. Small primitives composed through the steps array without library-side magic.","md",{},[1835,1837,1840,1843],{"label":1836,"value":5},"Category",{"label":1838,"value":1839,"kind":21},"Linear","steps: [a, b, c]",{"label":1841,"value":1842,"kind":21},"Branching","(ctx) => pickedForm | string | undefined",{"label":1844,"value":1845},"Per-step","persistence + undo follow each form","\u002Fdocs\u002Fmultistep\u002Fpatterns",{"title":5,"description":1831},null,"docs\u002Fmultistep\u002Fpatterns","DmcFYDfgjvrybtyzePJ8yOnpI2DPyDYdq0W4v3ncv3I",1780949761959]