CoreUI is in beta - APIs and components may change.
CoreUI
Base Components

Horizontal Stepper

A versatile horizontal step indicator providing a clear visual guide through sequential processes.


Installation

Install the following dependencies:

npm install @radix-ui/react-slot

Create a horizontal-stepper.tsx file and paste the following code into it.

components/ui/horizontal-stepper.tsx
// CoreUI HorizontalStepper 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 HORIZONTAL_STEPPER_ROOT_NAME = 'HorizontalStepperRoot';const HORIZONTAL_STEPPER_SEPARATOR_NAME = 'HorizontalStepperSeparator';const HORIZONTAL_STEPPER_ITEM_NAME = 'HorizontalStepperItem';const HORIZONTAL_STEPPER_ITEM_INDICATOR_NAME = 'HorizontalStepperItemIndicator';function HorizontalStepperRoot({  asChild,  children,  className,  ...rest}: React.HTMLAttributes<HTMLDivElement> & {  asChild?: boolean;}) {  const Component = asChild ? Slot : 'div';  return (    <Component      className={cn('flex flex-wrap justify-center gap-4', className)}      {...rest}    >      {children}    </Component>  );}HorizontalStepperRoot.displayName = HORIZONTAL_STEPPER_ROOT_NAME;function HorizontalStepperSeparatorIcon<T extends React.ElementType>({  className,  as,  ...rest}: PolymorphicComponentProps<T>) {  const Component = as || RiArrowRightSLine;  return (    <Component      className={cn('size-5 shrink-0 text-text-soft-400', className)}      {...rest}    />  );}HorizontalStepperSeparatorIcon.displayName = HORIZONTAL_STEPPER_SEPARATOR_NAME;const horizontalStepperItemVariants = tv({  slots: {    root: [      // base      'flex items-center gap-2 text-paragraph-sm',    ],    indicator: [      // base      'flex size-5 shrink-0 items-center justify-center rounded-full text-label-xs',    ],  },  variants: {    state: {      completed: {        root: 'text-text-strong-950',        indicator: 'bg-success-base text-static-white',      },      active: {        root: 'text-text-strong-950',        indicator: 'bg-primary-base text-static-white',      },      default: {        root: 'text-text-sub-600',        indicator:          'bg-bg-white-0 text-text-sub-600 ring-1 ring-inset ring-stroke-soft-200',      },    },  },  defaultVariants: {    state: 'default',  },});type HorizontalStepperItemSharedProps = VariantProps<  typeof horizontalStepperItemVariants>;type HorizontalStepperItemProps =  React.ButtonHTMLAttributes<HTMLButtonElement> &    VariantProps<typeof horizontalStepperItemVariants> & {      asChild?: boolean;    };const HorizontalStepperItem = React.forwardRef<  HTMLButtonElement,  HorizontalStepperItemProps>(({ asChild, children, state, className, ...rest }, forwardedRef) => {  const uniqueId = React.useId();  const Component = asChild ? Slot : 'button';  const { root } = horizontalStepperItemVariants({ state });  const sharedProps: HorizontalStepperItemSharedProps = {    state,  };  const extendedChildren = recursiveCloneChildren(    children as React.ReactElement[],    sharedProps,    [HORIZONTAL_STEPPER_ITEM_INDICATOR_NAME],    uniqueId,    asChild,  );  return (    <Component      ref={forwardedRef}      className={root({ class: className })}      {...rest}    >      {extendedChildren}    </Component>  );});HorizontalStepperItem.displayName = HORIZONTAL_STEPPER_ITEM_NAME;function HorizontalStepperItemIndicator({  state,  className,  children,  ...rest}: React.HTMLAttributes<HTMLDivElement> & HorizontalStepperItemSharedProps) {  const { indicator } = horizontalStepperItemVariants({ 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>  );}HorizontalStepperItemIndicator.displayName =  HORIZONTAL_STEPPER_ITEM_INDICATOR_NAME;export {  HorizontalStepperRoot as Root,  HorizontalStepperSeparatorIcon as SeparatorIcon,  HorizontalStepperItem as Item,  HorizontalStepperItemIndicator as ItemIndicator,};

Examples

With Radix Tabs

API Reference

HorizontalStepper.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:

PropTypeDefault
asChildboolean

HorizontalStepper.SeparatorIcon

Icon used as a separator between stepper items. A polymorphic component, allowing you to change the underlying HTML element using the as prop.

PropTypeDefault
asReact.ElementTypeRiArrowRightSLine

HorizontalStepper.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:

PropTypeDefault
asChildboolean
state"default" | "active" | "completed""default"

HorizontalStepper.ItemIndicator

This component is based on the <div> element and supports all of its props.

On this page