Step Navigation

Navigation controls for stepping through multi-page content.

Report a bug

Preview

Switch between light and dark to inspect the embedded Storybook preview.

Installation

pnpm dlx shadcn@latest add https://ui.vllnt.ai/r/step-navigation.json

Storybook

Explore all variants, controls, and accessibility checks in the interactive Storybook playground.

View in Storybook

Code

"use client"; import { memo } from "react"; import type { ReactNode } from "react"; import { cn } from "../../lib/utils"; export type StepNavigationProps = { canNext: boolean; canPrev: boolean; className?: string; currentStep: number; nextIcon?: ReactNode; nextLabel?: string; onNext: () => void; onPrev: () => void; previousIcon?: ReactNode; previousLabel?: string; stepLabel?: string; totalSteps: number; }; // eslint-disable-next-line max-lines-per-function -- Complex navigation with icons function StepNavigationImpl({ canNext, canPrev, className, currentStep, nextIcon, nextLabel = "Next", onNext, onPrev, previousIcon, previousLabel = "Prev", stepLabel = "Section", totalSteps, }: StepNavigationProps): React.ReactNode { return ( <nav aria-label="Step navigation" className={cn( "fixed bottom-0 left-0 right-0 z-50", "border-t border-neutral-200 bg-white", "dark:border-neutral-800 dark:bg-black", className, )} > <div className="mx-auto flex max-w-3xl items-center justify-between px-4 py-3"> {/* Previous Button */} <button aria-label="Previous step" className={cn( "flex min-h-[44px] min-w-[44px] items-center justify-center", "rounded-md px-3 py-2 text-sm font-medium transition-colors", "hover:bg-neutral-100 dark:hover:bg-neutral-900", "disabled:pointer-events-none disabled:opacity-40", )} disabled={!canPrev} onClick={onPrev} type="button" > {previousIcon ?? ( <svg aria-hidden="true" className="size-5" fill="none" stroke="currentColor" viewBox="0 0 24 24" > <path d="m15 19-7-7 7-7" strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} /> </svg> )} <span className="ml-1">{previousLabel}</span> </button> {/* Center: Step Counter */} <div className="text-sm tabular-nums text-neutral-600 dark:text-neutral-400"> {stepLabel} {currentStep} / {totalSteps} </div> {/* Next Button */} <button aria-label="Next step" className={cn( "flex min-h-[44px] min-w-[44px] items-center justify-center", "rounded-md px-3 py-2 text-sm font-medium transition-colors", "hover:bg-neutral-100 dark:hover:bg-neutral-900", "disabled:pointer-events-none disabled:opacity-40", )} disabled={!canNext} onClick={onNext} type="button" > <span className="mr-1">{nextLabel}</span> {nextIcon ?? ( <svg aria-hidden="true" className="size-5" fill="none" stroke="currentColor" viewBox="0 0 24 24" > <path d="m9 5 7 7-7 7" strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} /> </svg> )} </button> </div> </nav> ); } export const StepNavigation = memo(StepNavigationImpl); StepNavigation.displayName = "StepNavigation";

Dependencies

  • @vllnt/ui@^0.2.1