Base Components
Vertical Stepper
An adaptable vertical step indicator offering an intuitive progression representation for diverse layouts.
Installation
Install the following dependencies:
npm install @radix-ui/react-slotCreate a vertical-stepper.tsx file and paste the following code into it.
// CoreUI VerticalStepper v0.0.0import * as React from 'react';import { Slot } from '@radix-ui/react-slot';import { RiArrowRightSLine } from '@remixicon/react';import { cn } from '@/utils/cn';import type { PolymorphicComponentProps } from '@/utils/polymorphic';import { recursiveCloneChildren } from '@/utils/recursive-clone-children';import { tv, type VariantProps } from '@/utils/tv';const VERTICAL_STEPPER_ROOT_NAME = 'VerticalStepperRoot';const VERTICAL_STEPPER_ARROW_NAME = 'VerticalStepperArrow';const VERTICAL_STEPPER_ITEM_NAME = 'VerticalStepperItem';const VERTICAL_STEPPER_ITEM_INDICATOR_NAME = 'VerticalStepperItemIndicator';function VerticalStepperRoot({ asChild, children, className, ...rest}: React.HTMLAttributes<HTMLDivElement> & { asChild?: boolean;}) { const Component = asChild ? Slot : 'div'; return ( <Component className={cn('w-full space-y-2', className)} {...rest}> {children} </Component> );}VerticalStepperRoot.displayName = VERTICAL_STEPPER_ROOT_NAME;function VerticalStepperArrow<T extends React.ElementType>({ className, as, ...rest}: PolymorphicComponentProps<T>) { const Component = as || RiArrowRightSLine; return ( <Component className={cn('size-5 shrink-0 text-text-sub-600', className)} {...rest} /> );}VerticalStepperArrow.displayName = VERTICAL_STEPPER_ARROW_NAME;const verticalStepperItemVariants = tv({ slots: { root: [ // base 'grid w-full auto-cols-auto grid-flow-col grid-cols-[auto_minmax(0,1fr)] items-center gap-2.5 rounded-10 p-2 text-left text-paragraph-sm', ], indicator: [ // base 'flex size-5 shrink-0 items-center justify-center rounded-full text-label-xs', ], }, variants: { state: { completed: { root: 'bg-bg-weak-50 text-text-sub-600', indicator: 'bg-success-base text-static-white', }, active: { root: 'bg-bg-white-0 text-text-strong-950 shadow-regular-xs', indicator: 'bg-primary-base text-static-white', }, default: { root: 'bg-bg-weak-50 text-text-sub-600', indicator: 'bg-bg-white-0 text-text-sub-600 shadow-regular-xs', }, }, }, defaultVariants: { state: 'default', },});type VerticalStepperItemSharedProps = VariantProps< typeof verticalStepperItemVariants>;type VerticalStepperItemProps = React.ButtonHTMLAttributes<HTMLButtonElement> & VariantProps<typeof verticalStepperItemVariants> & { asChild?: boolean; };const VerticalStepperItem = React.forwardRef< HTMLButtonElement, VerticalStepperItemProps>(({ asChild, children, state, className, ...rest }, forwardedRef) => { const uniqueId = React.useId(); const Component = asChild ? Slot : 'button'; const { root } = verticalStepperItemVariants({ state }); const sharedProps: VerticalStepperItemSharedProps = { state, }; const extendedChildren = recursiveCloneChildren( children as React.ReactElement[], sharedProps, [VERTICAL_STEPPER_ITEM_INDICATOR_NAME], uniqueId, asChild, ); return ( <Component ref={forwardedRef} className={root({ class: className })} {...rest} > {extendedChildren} </Component> );});VerticalStepperItem.displayName = VERTICAL_STEPPER_ITEM_NAME;function VerticalStepperItemIndicator({ state, className, children, ...rest}: React.HTMLAttributes<HTMLDivElement> & VerticalStepperItemSharedProps) { const { indicator } = verticalStepperItemVariants({ state }); if (state === 'completed') { return ( <div className={indicator({ class: className })} {...rest}> <svg viewBox='0 0 20 20' xmlns='http://www.w3.org/2000/svg' fill='none'> <path fill='currentColor' d='M15.1 7.453 8.726 13.82 4.9 10l1.275-1.274 2.55 2.548 5.1-5.094L15.1 7.453Z' /> </svg> </div> ); } return ( <div className={indicator({ class: className })} {...rest}> {children} </div> );}VerticalStepperItemIndicator.displayName = VERTICAL_STEPPER_ITEM_INDICATOR_NAME;export { VerticalStepperRoot as Root, VerticalStepperArrow as Arrow, VerticalStepperItem as Item, VerticalStepperItemIndicator as ItemIndicator,};Examples
With Radix Tabs
API Reference
VerticalStepper.Root
Outer container that holds the stepper items and separator icons. This component is based on the <div> element and supports all of its props. And adds:
| Prop | Type | Default |
|---|---|---|
asChild | boolean |
VerticalStepper.Arrow
Arrow icon used to indicate active step item. A polymorphic component, allowing you to change the underlying HTML element using the as prop.
| Prop | Type | Default |
|---|---|---|
as | React.ElementType | RiArrowRightSLine |
VerticalStepper.Item
Individual stepper item which can indicate the state of the step. This component is based on the <button> element and supports all of its props. And adds:
| Prop | Type | Default |
|---|---|---|
asChild | boolean | |
state | "default" | "active" | "completed" | "default" |
VerticalStepper.ItemIndicator
This component is based on the <div> element and supports all of its props.