輸入元件

Multi-step Form

多步驟表單容器,方向感知的滑動過場搭配可切換款式的進度指示。

這是 WebberUI Pro 元件

下方的安裝指令需要授權才能執行,未授權時會回 401。

怎麼安裝 Pro 元件 →查看方案 →

把這個效果裝進你的專案

在專案資料夾執行指令,CLI 會加入原始碼與依賴套件,再依下方使用範例引用元件。

npx webberui@latest add "https://webberui.com/r/multi-step-form.json?t=<安裝 token>"

還沒有專案,或安裝遇到問題?看快速開始 →

Multi-step Form 是用 React、Motion(motion/react)與 Tailwind CSS 做的多步驟表單(multi-step form/表單精靈 wizard)容器:用 MultiStepFormStep 宣告步驟,切換時依前進或後退方向滑動,進度指示可選進度條、圓點或編號步驟三種款式。元件只負責步驟流程與過場,欄位與驗證由你自己的表單邏輯決定。

Playground

即時調整 props、程式碼片段同步更新——直接試出你要的樣子再複製。

編輯中的呼叫預設屬性
<MultiStepForm/>

完整範例包含預覽的排版與資料。先安裝元件,再把範例檔貼入你的專案。

適用情境

  • SaaS 註冊流程:帳號、公司資料、方案偏好分成幾步填寫
  • 結帳流程:收件資訊、配送方式、付款確認
  • 新使用者的 onboarding 問卷,每頁只問一兩個問題
  • 報名表、申請表這類欄位很多、需要分段降低填寫壓力的表單
  • 不適合:只需要顯示流程進度、內容不在同一個容器裡切換時,改用 Stepper

安裝

npx webberui@latest add "https://webberui.com/r/multi-step-form.json?t=<安裝 token>"

或在 components.json 設定 registries 後,改用 @webberui/multi-step-form 安裝。

使用

以 <MultiStepFormStep> 宣告每一個步驟,容器會依目前步驟做方向感知的滑動過場,並自動渲染進度指示與上一步/下一步控制列。

import {
  MultiStepForm,
  MultiStepFormStep,
} from "@/components/ui/multi-step-form";

<MultiStepForm variant="steps" onComplete={() => console.log("done")}>
  <MultiStepFormStep label="帳號">{/* ...欄位 */}</MultiStepFormStep>
  <MultiStepFormStep label="個人資料">{/* ...欄位 */}</MultiStepFormStep>
  <MultiStepFormStep label="確認">{/* ...摘要 */}</MultiStepFormStep>
</MultiStepForm>;

自訂控制列

將 controls 設為 false,改用 useMultiStepForm() hook 在任意位置自組導覽(可加入驗證後才 next()):

import { useMultiStepForm } from "@/components/ui/multi-step-form";

function Footer() {
  const { back, next, isFirst, isLast } = useMultiStepForm();
  return (
    <div>
      <button onClick={back} disabled={isFirst}>
        上一步
      </button>
      <button onClick={next}>{isLast ? "送出" : "下一步"}</button>
    </div>
  );
}

受控模式

傳入 step 與 onStepChange 即可完全掌控目前步驟,適合搭配表單驗證做步驟守門。

const [step, setStep] = React.useState(0);

<MultiStepForm step={step} onStepChange={(next) => setStep(next)}>
  {/* ...steps */}
</MultiStepForm>;

範例

驗證通過才能前進

受控模式下,onStepChange 會帶入方向;往前走(direction === 1)時先檢查目前步驟,不通過就不更新 step。最後一步按「完成」走的是 onComplete,不會經過 onStepChange。

"use client";

import * as React from "react";
import {
  MultiStepForm,
  MultiStepFormStep,
} from "@/components/ui/multi-step-form";

export function SignupForm() {
  const [step, setStep] = React.useState(0);
  const [email, setEmail] = React.useState("");
  const [error, setError] = React.useState("");

  return (
    <MultiStepForm
      step={step}
      onStepChange={(next, direction) => {
        if (direction === 1 && step === 0 && !email.includes("@")) {
          setError("請輸入有效的電子郵件");
          return;
        }
        setError("");
        setStep(next);
      }}
      onComplete={() => console.log("送出", { email })}
    >
      <MultiStepFormStep label="帳號">
        <input
          type="email"
          value={email}
          onChange={(e) => setEmail(e.target.value)}
          placeholder="you@company.com"
          className="w-full rounded-lg border px-3 py-2 text-sm"
        />
        {error ? <p className="mt-2 text-sm text-red-600">{error}</p> : null}
      </MultiStepFormStep>
      <MultiStepFormStep label="公司資料">{/* 公司名稱、人數 */}</MultiStepFormStep>
      <MultiStepFormStep label="確認">{/* 摘要 */}</MultiStepFormStep>
    </MultiStepForm>
  );
}

進度條款式與自訂按鈕文字

variant="bar" 顯示目前步驟標題與「x / y」計數;三個 label props 改內建控制列的文字。

<MultiStepForm
  variant="bar"
  backLabel="返回"
  nextLabel="繼續"
  completeLabel="送出訂單"
  onComplete={placeOrder}
>
  <MultiStepFormStep label="收件資訊">{/* ... */}</MultiStepFormStep>
  <MultiStepFormStep label="配送方式">{/* ... */}</MultiStepFormStep>
  <MultiStepFormStep label="付款確認">{/* ... */}</MultiStepFormStep>
</MultiStepForm>

從指定步驟開始

非受控模式下用 defaultStep 指定初始步驟,例如使用者回來繼續填寫草稿時。索引超出範圍會自動夾回有效值。

<MultiStepForm defaultStep={savedDraft.step} variant="dots">
  {/* ...steps */}
</MultiStepForm>

Props

MultiStepForm

Prop型別預設值說明
childrenReactNode—步驟宣告,皆為 <MultiStepFormStep>
stepnumber—受控目前步驟索引;不傳則為非受控
defaultStepnumber0非受控模式的初始步驟索引
onStepChange(step: number, direction: 1 | -1) => void—步驟變更時觸發,帶入新索引與方向
onComplete() => void—於最後一步按下「完成」時觸發
variant"bar" | "dots" | "steps" | false"steps"進度指示款式;false 不顯示
controlsbooleantrue是否渲染內建控制列
backLabelstring"上一步"上一步按鈕文字
nextLabelstring"下一步"下一步按鈕文字
completeLabelstring"完成"末步按鈕文字

MultiStepFormStep

Prop型別預設值說明
labelstring—進度指示顯示的步驟標題
childrenReactNode—步驟內容
classNamestring—追加到步驟容器的 className

useMultiStepForm()

回傳 { step, total, direction, isFirst, isLast, goTo, next, back, reducedMotion },供自訂控制列或進度指示使用。

細節

  • 三種進度款式:bar 進度條、dots 圓點(目前點延伸為膠囊)、steps 編號步驟(完成打勾、連接線依進度上色)。
  • 方向感知:前進時新步驟自右側滑入、舊步驟往左退場;後退則相反。方向於 render 期間依前次索引推導,受控與非受控皆正確。
  • 檢視區採 overflow-hidden 裁切水平滑動,並以 popLayout 讓退場步驟脫離流排、由進場步驟決定容器高度,避免過場時高度塌陷。

可及性

  • 進度指示中「目前與先前」的步驟可點擊回跳(goTo),未到達的步驟停用,維持填寫順序。
  • 目前步驟標上 aria-current="step";進度條提供 role="progressbar" 與 aria-valuenow。
  • 步驟切換透過 aria-live="polite" 區塊朗讀「步驟 x / y」。
  • 內建控制列為原生 button,可鍵盤操作並具 focus-visible 外框;第一步時上一步鈕停用。
  • 使用者系統開啟「減少動態效果」時,滑動退化為短暫淡入淡出,進度填充直接跳位。

常見問題

可以在 Next.js App Router 使用嗎?

可以。檔案開頭已有 "use client",在 Server Component 頁面裡照常 import;需要用 state 或事件處理的包裝元件,自己也要標 "use client"。

可以搭配 React Hook Form 之類的表單函式庫嗎?

可以。元件不管理欄位值,把整個 MultiStepForm 包在你的 <form> 或表單 provider 裡,再用受控模式或 useMultiStepForm() 的 next() 在驗證後切換步驟。內建控制列的按鈕是 type="button",不會意外送出表單。

為什麼我放在 MultiStepForm 裡的其他元素沒有顯示?

容器只會擷取直接子節點中的 MultiStepFormStep,其他節點會被忽略。標題、說明文字請放在步驟內容裡,或放在 MultiStepForm 外面。

螢幕閱讀器會知道步驟換了嗎?

會。容器內有 aria-live="polite" 區塊朗讀「步驟 x / y」與步驟標題,目前步驟標上 aria-current="step"。系統開啟「減少動態效果」時,滑動改為短暫淡入淡出。

需要哪些依賴?

motion、lucide-react、clsx 與 tailwind-merge,詳見上方安裝區塊。