Skip to content

Latest commit

 

History

History
589 lines (497 loc) · 16.2 KB

stepper.mdx

File metadata and controls

589 lines (497 loc) · 16.2 KB
title description component
Stepper
Display content divided into a steps sequence.
true

Installation

CLI Manual

Run the following command:

npx shadcn-ui@latest add stepper

Update tailwind.config.js

Add the following animations to your tailwind.config.js file:

/** @type {import('tailwindcss').Config} */
module.exports = {
  theme: {
    extend: {
      keyframes: {
        "collapsible-down": {
          from: { height: "0" },
          to: { height: "var(--radix-collapsible-content-height)" },
        },
        "collapsible-up": {
          from: { height: "var(--radix-collapsible-content-height)" },
          to: { height: "0" },
        },
      },
      animation: {
        "collapsible-down": "collapsible-down 0.2s ease-out",
        "collapsible-up": "collapsible-up 0.2s ease-out",
      },
    },
  },
}

Copy and paste the following code into your project.

Update the import paths to match your project setup.

Usage

import {
  Step,
  Stepper,
  useStepper,
  type StepItem,
} from "@/components/ui/stepper"
const steps = [
  { label: "Step 1" },
  { label: "Step 2" },
  { label: "Step 3" },
] satisfies StepItem[]

export default function StepperDemo() {
  return (
    <div className="flex w-full flex-col gap-4">
      <Stepper initialStep={0} steps={steps}>
        {steps.map(({ label }, index) => {
          return (
            <Step key={label} label={label}>
              <div className="h-40 flex items-center justify-center my-4 border bg-secondary text-primary rounded-md">
                <h1 className="text-xl">Step {index + 1}</h1>
              </div>
            </Step>
          )
        })}
        <Footer />
      </Stepper>
    </div>
  )
}

const Footer = () => {
  const {
    nextStep,
    prevStep,
    resetSteps,
    isDisabledStep,
    hasCompletedAllSteps,
    isLastStep,
    isOptionalStep,
  } = useStepper()
  return (
    <>
      {hasCompletedAllSteps && (
        <div className="h-40 flex items-center justify-center my-4 border bg-secondary text-primary rounded-md">
          <h1 className="text-xl">Woohoo! All steps completed! 🎉</h1>
        </div>
      )}
      <div className="w-full flex justify-end gap-2">
        {hasCompletedAllSteps ? (
          <Button size="sm" onClick={resetSteps}>
            Reset
          </Button>
        ) : (
          <>
            <Button
              disabled={isDisabledStep}
              onClick={prevStep}
              size="sm"
              variant="secondary"
            >
              Prev
            </Button>
            <Button size="sm" onClick={nextStep}>
              {isLastStep ? "Finish" : isOptionalStep ? "Skip" : "Next"}
            </Button>
          </>
        )}
      </div>
    </>
  )
}

Examples

Default

Orientation

We can pass the orientation prop to the Stepper component to set the orientation as "vertical" or "horizontal".

Description

We can add a description to the array of steps

Optional steps

If you want to make a step optional, you can add optional: true in the array of steps.

In this example, the second step is optional. You can visualize the change of the label in the Next button

Variants

There are 3 design variants for the Stepper component:

  • circle: allows you to display each step in a circular shape. The label and description are positioned horizontally next to the button of each step.
  • circle-alt: same circular design as the circle variant but the label and description are positioned vertically below the button of each step.
  • line: this variant features a line layout with the title and description positioned below the line.

Sizes

The Stepper component has 3 sizes: sm, md, and lg which can be set using the size prop.

Responsive

By using the orientation prop you are able to switch between horizontal (default) and vertical orientations. By default, when in mobile view the Steps component will switch to vertical orientation. You are also able to customize the breakpoint at which the component switches to vertical orientation by using the mobileBreakpoint prop.

State

Sometimes it is useful to display visual feedback to the user depending on some asynchronous logic. In this case we can use the state prop to display a loading or error indicator with the values of loading | error.

This prop can be used globally within the Stepper component or locally in the Step component affected by this state.

Custom Icons

If you want to show custom icons instead of the default numerical indicators, you can do so by using the icon prop on the Step component.

To change the general check and error icons, we can use the `checkIcon` and `errorIcon` prop inside the Stepper component

Clickable steps

If you need the step buttons to be clickable and to be able to set a custom logic for the onClick event, we can use the onClickStep prop in the Stepper component globally or in Step locally.

In this example we have placed a global alert when any step is clicked. Try clicking on any step to see the result.

The `onClickStep` function has as parameters the index of the clicked step and the setter that allows to change to that step index. The setter is useful when we want to send an onClick event globally and we don't have access to the useStepper hook.

Footer inside the step

When using the vertical orientation, we may want to have the footer buttons inside each step and not located at the end. To achieve this, we can simply move our footer below all the steps inside the Stepper component

Scroll tracking

If you would like the stepper to scroll to the active step when it is not in view you can do so using the scrollTracking prop on the Stepper component.

With Forms

If you want to use the stepper with forms, you can do so by using the useStepper hook to control the component.

This example uses the Form component of shadcn and the react-hook-form hooks to create a form with zod for validations.

You can also use the component with server actions.

Custom styles

In this example we will change the style of the steps and the separator. In addition, we will also change the variables that define the size and gap of the icon for each step.

...
  <Stepper
    initialStep={0}
    steps={steps}
    styles={{
      "step-button-container": cn(
        "text-purple-700 rounded-none",
        "data-[current=true]:border-purple-500 data-[current=true]:bg-purple-50",
        "data-[active=true]:bg-purple-500 data-[active=true]:border-purple-500"
      ),
      "horizontal-step":
        "data-[completed=true]:[&:not(:last-child)]:after:bg-purple-500",
    }}
    variables={{
      "--step-icon-size": "60px",
      "--step-gap": "20px",
    }}
  >
  // Rest of the code
  </Stepper>
...

To customize the styles of the Steps component, Stepper provides a list of css classes for each part of the component. You can use these classes to override the default styles. Below is a list of the classes that are available.

  • main-container: The main container of the stepper.
  • horizontal-step: Outer wrapper for each step in horizontal layout
  • horizontal-step-container: Inner wrapper for each step in horizontal layout
  • vertical-step: Outer wrapper for each step in vertical layout
  • vertical-step-container: Inner wrapper for each step in vertical layout
  • vertical-step-content: Content wrapper for each step in vertical layout
  • step-button-container: Wrapper for the step button
  • step-label-container: Wrapper for the label and description
  • step-label: The label of the step
  • step-description: The description of the step

In some cases you may want to customize the styles of a step based on its state. For example, you may want to change the color of a step when it is active. To do this, you can use the data attributes defined below.

  • data-active: The active step
  • data-invalid: The step with an error
  • data-loading: The step in loading state
  • data-clickable: The step that is clickable
  • data-completed: The step that is completed
  • data-optional: The step that is optional

Finally, we also have the variables prop that allows you to set values for the css variables that calculate the position of the separator lines. These variables can be useful when we need to set custom elements that have a different size than those offered by the component.

  • --step-icon-size: defines the width of the step icon. It is important to define this value in pixels. By default it has the values of the width of a shadcn/ui button depending if the style is default (36px, 40px, 44px) or new york (32px, 36px, 40px).
  • --step-gap: defines the gap between the separator and the elements that follow it. The default value is 8px.

API

Stepper

<PropsTable data={[ { name: "initialStep", required: true, type: "number", description: "Currently active step", }, { name: "steps", required: true, type: "StepItem[]", description: "An array of objects with the label and description of each step.", }, { name: "orientation", type: '"horizontal" | "vertical"', default: "horizontal", description: "The orientation of the stepper.", }, { name: "size", type: '"sm" | "md" | "lg"', default: "md", description: "The size of the stepper.", }, { name: "state", type: '"loading" | "error"', description: "The state of the stepper (global).", }, { name: "icon", type: "LucideIcon | React.ComponentType", description: "The custom icon to be displayed in the step button.", }, { name: "checkIcon", type: "LucideIcon | React.ComponentType", description: "The custom icon to be displayed when the step is completed.", }, { name: "errorIcon", type: "LucideIcon | React.ComponentType", description: "The custom icon to be displayed when the step has an error.", }, { name: "responsive", type: "boolean", default: "true", description: "If the stepper should be responsive.", }, { name: "mobileBreakpoint", type: "number", default: "768px", description: "The breakpoint at which the stepper switches to vertical orientation.", }, { name: "scrollTracking", type: "boolean", default: "false", description: "Scroll to the active step when scrolling forward or backward.", }, { name: "styles", type: "{ [key: string]: string }", description: "Custom styles for the stepper.", }, { name: "variables", type: "{ [key: string]: string }", description: "Custom css variables values for the stepper.", }, { name: "onClickStep", type: "(index: number, setStep: (index: number) => void) => void", description: "The function to be executed when a step is clicked.", }, { name: "variant", type: '"circle" | "circle-alt" | "line"', default: "circle", description: "The design variant of the stepper.", }, { name: "expandVerticalSteps", type: "boolean", default: "false", description: "Control whether or not the vertical steps should collapse.", }, ]} />

Step

<PropsTable data={[ { name: "id", type: "string", description: "The id of the step.", }, { name: "label", type: "string", description: "The label of the step.", }, { name: "description", type: "string", description: "The description of the step.", }, { name: "optional", type: "boolean", description: "If the step is optional.", }, { name: "icon", type: "LucideIcon | React.ComponentType", description: "The custom icon to be displayed in the step button.", }, { name: "state", type: '"loading" | "error"', description: "The state of the step (local).", }, { name: "isCompletedStep", type: "boolean", description: "Individually control each step state, defaults to active step.", }, { name: "isKeepError", type: "boolean", description: "Individually control if each step should keep showing the error state.", }, { name: "checkIcon", type: "LucideIcon | React.ComponentType", description: "The custom icon to be displayed when the step is completed.", }, { name: "errorIcon", type: "LucideIcon | React.ComponentType", description: "The custom icon to be displayed when the step has an error.", }, { name: "onClickStep", type: "(index: number, setStep: (index: number) => void) => void", description: "The function to be executed when a step is clicked.", }, ]} />

useStepper

In order to use the hook, we simply have to import it and use it inside the <Stepper /> component as a wrapper.

import { useStepper } from "@/components/ui/stepper"

export funcion UseStepperDemo() {
  { ... } = useStepper();

  return (
    <div>
      { ... }
    </div>
  )
}

The values returned by the hook are the following:

<PropsTable data={[ { name: "activeStep", type: "number", description: "The current active step.", }, { name: "isLastStep", type: "boolean", description: "If the current step is the last step.", }, { name: "isOptionalStep", type: "boolean", description: "If the current step is optional.", }, { name: "isDisabledStep", type: "boolean", description: "If the current step is disabled.", }, { name: "isError", type: "boolean", description: "If the stepper has an error.", }, { name: "isLoading", type: "boolean", description: "If the stepper is loading.", }, { name: "isVertical", type: "boolean", description: "If the stepper is vertical.", }, { name: "expandVerticalSteps", type: "boolean", description: "If the vertical steps should be expanded.", }, { name: "nextStep", type: "() => void", description: "Function to go to the next step.", }, { name: "prevStep", type: "() => void", description: "Function to go to the previous step.", }, { name: "setStep", type: "(index: number) => void", description: "Function to set a specific step.", }, { name: "resetSteps", type: "() => void", description: "Function to reset the stepper.", }, { name: "stepCount", type: "number", description: "The total number of steps.", }, { name: "initialStep", type: "number", description: "The initial active step.", }, { name: "clickable", type: "boolean", description: "If the steps are clickable.", }, { name: "hasCompletedAllSteps", type: "boolean", description: "If all steps have been completed.", }, { name: "currentStep", type: "StepItem", description: "The current step object.", }, ]} />