Skip to content
Facade UI
Colour mode
GitHub (opens in a new tab)
blockmotion

Steps (motion)

Steps arrive in order, the one place a stagger carries meaning.

Wraps the static Steps, which renders identically with JavaScript disabled.

Preview

Open full width (opens in a new tab)
Preview width

Installation

Package manager
pnpm dlx shadcn@latest add https://facadeui.dev/r/steps-motion.json

Pulls in motion-primitives, steps. The CLI installs them for you.

Usage

The exact source of the preview above.

demos/steps-motion.tsx
"use client"

/**
 * The `steps` demo, with the motion variant swapped in.
 *
 * Identical props: the motion variant only changes which slot components
 * the static section renders through.
 *
 * `"use client"` is required here, not incidental: the motion variant is a
 * client component, and icon components cannot cross a server-to-client
 * boundary as props. See the note on `IconComponent`.
 */

import { StepsMotion } from "@registry/sections/steps-motion"

import { STEPS } from "./content"

export function Demo() {
  return (
    <StepsMotion
      eyebrow="Getting started"
      title="From nothing to a landing page"
      description="An ordered list, so a screen reader already announces 'step 2 of 3' without the numeral being read twice."
      items={STEPS}
    />
  )
}

Source

What the CLI copies into your project, byte for byte.

components/sections/steps-motion.tsx
"use client"

/**
 * StepsMotion — the numbered sequence with a staggered entrance.
 *
 * Steps arrive in order, which is the one place a stagger carries meaning rather than decoration.
 *
 * A genuine wrapper: it renders the static `Steps` and only swaps the list
 * slots, so no markup, styling or accessibility behaviour is duplicated.
 *
 * The list slot renders an `ol` rather than a `ul`, because the sequence is the
 * content — which is also why the stagger belongs here at all. The item slot
 * still renders an `li`: an `ol` may only contain list items, and a `div` there
 * is invalid markup that axe reports as `list`.
 *
 * Dependencies: react, @/components/motion/slots, @/components/motion/stagger,
 * @/components/sections/steps.
 */

import type { ReactNode } from "react"

import { StaggerListItem } from "@/components/motion/slots"
import { Stagger } from "@/components/motion/stagger"
import { Steps, type StepsProps } from "@/components/sections/steps"

function StaggerOl({
  className,
  children,
}: {
  className?: string
  children?: ReactNode
}) {
  return (
    <Stagger as="ol" className={className}>
      {children}
    </Stagger>
  )
}

export type StepsMotionProps = Omit<StepsProps, "listAs" | "itemAs">

export function StepsMotion(props: StepsMotionProps) {
  return <Steps {...props} listAs={StaggerOl} itemAs={StaggerListItem} />
}

Props

StepsMotionProps

Extends Omit<StepsProps, "listAs" | "itemAs">.