[{"data":1,"prerenderedAt":1723},["ShallowReactive",2],{"content-\u002Fdocs\u002Fmultistep\u002Fpatterns":3},{"id":4,"title":5,"body":6,"description":1703,"extension":1704,"meta":1705,"metaRows":1706,"navigation":95,"path":1718,"seo":1719,"source":1720,"stem":1721,"__hash__":1722},"docs\u002Fdocs\u002Fmultistep\u002Fpatterns.md","Patterns",{"type":7,"value":8,"toc":1692},"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,1170,1239,1243,1250,1366,1369,1550,1560,1571,1575,1586,1621,1635,1639,1688],[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, active-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},"persisting-the-active-step","Persisting the active step",[17,1159,1160,1161,1164,1165,1169],{},"What the wizard persists is the active step, via ",[21,1162,1163],{},"?step=\u003Ckey>"," on the URL by default (see ",[393,1166,1168],{"href":1167},"\u002Fdocs\u002Fmultistep\u002Furl-sync","URL sync","). A refresh lands the user back on the step they were on, with the navigation cursor intact.",[49,1171,1173],{"className":51,"code":1172,"language":53,"meta":54,"style":54},"const 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",[21,1174,1175,1191,1207,1223,1227],{"__ignoreMap":54},[58,1176,1177,1179,1181,1183,1185,1187,1189],{"class":60,"line":61},[58,1178,102],{"class":64},[58,1180,200],{"class":105},[58,1182,109],{"class":64},[58,1184,205],{"class":115},[58,1186,208],{"class":68},[58,1188,211],{"class":75},[58,1190,214],{"class":68},[58,1192,1193,1195,1197,1199,1201,1203,1205],{"class":60,"line":79},[58,1194,102],{"class":64},[58,1196,222],{"class":105},[58,1198,109],{"class":64},[58,1200,205],{"class":115},[58,1202,229],{"class":68},[58,1204,232],{"class":75},[58,1206,214],{"class":68},[58,1208,1209,1211,1213,1215,1217,1219,1221],{"class":60,"line":92},[58,1210,102],{"class":64},[58,1212,242],{"class":105},[58,1214,109],{"class":64},[58,1216,205],{"class":115},[58,1218,249],{"class":68},[58,1220,252],{"class":75},[58,1222,214],{"class":68},[58,1224,1225],{"class":60,"line":99},[58,1226,96],{"emptyLinePlaceholder":95},[58,1228,1229,1231,1233,1235,1237],{"class":60,"line":128},[58,1230,102],{"class":64},[58,1232,267],{"class":105},[58,1234,109],{"class":64},[58,1236,272],{"class":115},[58,1238,275],{"class":68},[33,1240,1242],{"id":1241},"per-step-undo","Per-step undo",[17,1244,1245,1246,1249],{},"Same composition: each step gets its own ",[21,1247,1248],{},"history"," chain.",[49,1251,1253],{"className":51,"code":1252,"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,1254,1255,1268,1273,1284,1296,1300,1304,1317,1322,1331,1345,1349,1353],{"__ignoreMap":54},[58,1256,1257,1259,1262,1264,1266],{"class":60,"line":61},[58,1258,102],{"class":64},[58,1260,1261],{"class":105}," cargo",[58,1263,109],{"class":64},[58,1265,205],{"class":115},[58,1267,360],{"class":68},[58,1269,1270],{"class":60,"line":79},[58,1271,1272],{"class":68},"  schema: cargoSchema,\n",[58,1274,1275,1278,1281],{"class":60,"line":92},[58,1276,1277],{"class":68},"  key: ",[58,1279,1280],{"class":75},"'cargo'",[58,1282,1283],{"class":68},",\n",[58,1285,1286,1289,1291,1293],{"class":60,"line":99},[58,1287,1288],{"class":68},"  history: ",[58,1290,185],{"class":105},[58,1292,464],{"class":68},[58,1294,1295],{"class":810},"\u002F\u002F unlimited undo \u002F redo across the cargo step\n",[58,1297,1298],{"class":60,"line":128},[58,1299,388],{"class":68},[58,1301,1302],{"class":60,"line":163},[58,1303,96],{"emptyLinePlaceholder":95},[58,1305,1306,1308,1311,1313,1315],{"class":60,"line":190},[58,1307,102],{"class":64},[58,1309,1310],{"class":105}," billing",[58,1312,109],{"class":64},[58,1314,205],{"class":115},[58,1316,360],{"class":68},[58,1318,1319],{"class":60,"line":195},[58,1320,1321],{"class":68},"  schema: billingSchema,\n",[58,1323,1324,1326,1329],{"class":60,"line":217},[58,1325,1277],{"class":68},[58,1327,1328],{"class":75},"'billing'",[58,1330,1283],{"class":68},[58,1332,1333,1336,1339,1342],{"class":60,"line":237},[58,1334,1335],{"class":68},"  history: { max: ",[58,1337,1338],{"class":105},"25",[58,1340,1341],{"class":68}," }, ",[58,1343,1344],{"class":810},"\u002F\u002F capped chain\n",[58,1346,1347],{"class":60,"line":257},[58,1348,388],{"class":68},[58,1350,1351],{"class":60,"line":262},[58,1352,96],{"emptyLinePlaceholder":95},[58,1354,1355,1357,1359,1361,1363],{"class":60,"line":640},[58,1356,102],{"class":64},[58,1358,267],{"class":105},[58,1360,109],{"class":64},[58,1362,272],{"class":115},[58,1364,1365],{"class":68},"({ steps: [cargo, billing] })\n",[17,1367,1368],{},"A keyboard shortcut bound to the active step:",[49,1370,1372],{"className":283,"code":1371,"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,1373,1374,1394,1415,1432,1452,1478,1483,1488,1497,1501,1510,1528,1533,1542],{"__ignoreMap":54},[58,1375,1376,1378,1381,1384,1387,1389,1392],{"class":60,"line":61},[58,1377,292],{"class":68},[58,1379,1380],{"class":295},"script",[58,1382,1383],{"class":115}," setup",[58,1385,1386],{"class":115}," lang",[58,1388,302],{"class":68},[58,1390,1391],{"class":75},"\"ts\"",[58,1393,339],{"class":68},[58,1395,1396,1399,1402,1404,1407,1409,1412],{"class":60,"line":79},[58,1397,1398],{"class":64},"  function",[58,1400,1401],{"class":115}," onKeydown",[58,1403,154],{"class":68},[58,1405,1406],{"class":676},"event",[58,1408,709],{"class":64},[58,1410,1411],{"class":115}," KeyboardEvent",[58,1413,1414],{"class":68},") {\n",[58,1416,1417,1420,1423,1426,1429],{"class":60,"line":92},[58,1418,1419],{"class":64},"    if",[58,1421,1422],{"class":68}," (",[58,1424,1425],{"class":64},"!",[58,1427,1428],{"class":68},"wizard.activeForm) ",[58,1430,1431],{"class":64},"return\n",[58,1433,1434,1436,1439,1442,1445,1447,1450],{"class":60,"line":99},[58,1435,1419],{"class":64},[58,1437,1438],{"class":68}," (event.metaKey ",[58,1440,1441],{"class":64},"&&",[58,1443,1444],{"class":68}," event.key ",[58,1446,697],{"class":64},[58,1448,1449],{"class":75}," 'z'",[58,1451,1414],{"class":68},[58,1453,1454,1457,1459,1462,1465,1468,1470,1472,1475],{"class":60,"line":128},[58,1455,1456],{"class":68},"      event.shiftKey ",[58,1458,1030],{"class":64},[58,1460,1461],{"class":68}," wizard.activeForm.history.",[58,1463,1464],{"class":115},"redo",[58,1466,1467],{"class":68},"() ",[58,1469,709],{"class":64},[58,1471,1461],{"class":68},[58,1473,1474],{"class":115},"undo",[58,1476,1477],{"class":68},"()\n",[58,1479,1480],{"class":60,"line":163},[58,1481,1482],{"class":68},"    }\n",[58,1484,1485],{"class":60,"line":190},[58,1486,1487],{"class":68},"  }\n",[58,1489,1490,1493,1495],{"class":60,"line":195},[58,1491,1492],{"class":68},"\u003C\u002F",[58,1494,1380],{"class":295},[58,1496,339],{"class":68},[58,1498,1499],{"class":60,"line":217},[58,1500,96],{"emptyLinePlaceholder":95},[58,1502,1503,1505,1508],{"class":60,"line":237},[58,1504,292],{"class":68},[58,1506,1507],{"class":295},"template",[58,1509,339],{"class":68},[58,1511,1512,1515,1518,1521,1523,1526],{"class":60,"line":257},[58,1513,1514],{"class":68},"  \u003C",[58,1516,1517],{"class":295},"div",[58,1519,1520],{"class":115}," @keydown",[58,1522,302],{"class":68},[58,1524,1525],{"class":75},"\"onKeydown\"",[58,1527,339],{"class":68},[58,1529,1530],{"class":60,"line":262},[58,1531,1532],{"class":810},"    \u003C!-- step content -->\n",[58,1534,1535,1538,1540],{"class":60,"line":640},[58,1536,1537],{"class":68},"  \u003C\u002F",[58,1539,1517],{"class":295},[58,1541,339],{"class":68},[58,1543,1544,1546,1548],{"class":60,"line":645},[58,1545,1492],{"class":68},[58,1547,1507],{"class":295},[58,1549,339],{"class":68},[17,1551,1552,1555,1556,1559],{},[21,1553,1554],{},"wizard.activeForm"," is identity-equal to the form in ",[21,1557,1558],{},"wizard.forms[wizard.currentStep]",", so undo \u002F redo dispatches to the right chain.",[17,1561,1562,1563,1566,1567,1570],{},"Each step's history is independent: undoing on the ",[21,1564,1565],{},"cargo"," step doesn't retreat changes the user made on ",[21,1568,1569],{},"billing",". That matches the user's mental model: \"undo what I just typed here,\" not \"undo the entire flow.\"",[33,1572,1574],{"id":1573},"cross-component-access","Cross-component access",[17,1576,1577,1578,1581,1582,1585],{},"Pass a ",[21,1579,1580],{},"key"," to ",[21,1583,1584],{},"useWizard"," so a deep-tree component (a floating finish button, a sticky progress rail) can reach the same wizard without prop-threading:",[49,1587,1589],{"className":51,"code":1588,"language":53,"meta":54,"style":54},"const wizard = useWizard({\n  steps: [account, profile, review],\n  key: 'signup',\n})\n",[21,1590,1591,1603,1608,1617],{"__ignoreMap":54},[58,1592,1593,1595,1597,1599,1601],{"class":60,"line":61},[58,1594,102],{"class":64},[58,1596,267],{"class":105},[58,1598,109],{"class":64},[58,1600,272],{"class":115},[58,1602,360],{"class":68},[58,1604,1605],{"class":60,"line":79},[58,1606,1607],{"class":68},"  steps: [account, profile, review],\n",[58,1609,1610,1612,1615],{"class":60,"line":92},[58,1611,1277],{"class":68},[58,1613,1614],{"class":75},"'signup'",[58,1616,1283],{"class":68},[58,1618,1619],{"class":60,"line":99},[58,1620,388],{"class":68},[17,1622,1623,1624,1627,1628,1634],{},"A descendant component reaches it via ",[21,1625,1626],{},"injectWizard('signup')",". See ",[393,1629,1631],{"href":1630},"\u002Fdocs\u002Fmultistep\u002Finject-wizard",[21,1632,1633],{},"injectWizard"," for the cross-component story (ambient resolution, keyed lookup, null-on-miss).",[33,1636,1638],{"id":1637},"where-to-next","Where to next",[1640,1641,1642,1659,1666,1673,1681],"ul",{},[1643,1644,1645,1650,1651,1654,1655,1658],"li",{},[393,1646,1648],{"href":1647},"\u002Fdocs\u002Fmultistep\u002Fuse-wizard",[21,1649,1584],{}," for the navigation surface, ",[21,1652,1653],{},"activeForm",", and ",[21,1656,1657],{},"handleSubmit",".",[1643,1660,1661,1663,1664,914],{},[393,1662,396],{"href":395}," for the four slot kinds (form, string, function, ",[21,1665,1083],{},[1643,1667,1668,1672],{},[393,1669,1670],{"href":1630},[21,1671,1633],{}," for cross-component access to the wizard handle.",[1643,1674,1675,1677,1678,1680],{},[393,1676,1168],{"href":1167}," for wizard-level ",[21,1679,1163],{}," round-tripping.",[1643,1682,1683,1687],{},[393,1684,1686],{"href":1685},"\u002Fdocs\u002Fcross-cutting-state\u002Fundo-redo","Undo & redo"," for the per-form history chain.",[1689,1690,1691],"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":1693},[1694,1695,1696,1697,1699,1700,1701,1702],{"id":35,"depth":79,"text":36},{"id":400,"depth":79,"text":401},{"id":844,"depth":79,"text":845},{"id":1087,"depth":79,"text":1698},"Manual jumps with goTo",{"id":1156,"depth":79,"text":1157},{"id":1241,"depth":79,"text":1242},{"id":1573,"depth":79,"text":1574},{"id":1637,"depth":79,"text":1638},"Idiomatic wizard patterns. Linear flows, branching with function slots, dynamic terminals, active-step persistence, per-step undo. Small primitives composed through the steps array without library-side magic.","md",{},[1707,1709,1712,1715],{"label":1708,"value":5},"Category",{"label":1710,"value":1711,"kind":21},"Linear","steps: [a, b, c]",{"label":1713,"value":1714,"kind":21},"Branching","(ctx) => pickedForm | string | undefined",{"label":1716,"value":1717},"Per-step","undo follows each form","\u002Fdocs\u002Fmultistep\u002Fpatterns",{"title":5,"description":1703},null,"docs\u002Fmultistep\u002Fpatterns","MxIHUJAzdD9l6fTelRyMXDh_HTSZdQ7PrXntbpYhyOY",1781745874552]