shadimage
odysseyui
uicomponents-ai-stepssource: ok · hit

Steps

A collapsible AI tool-run steps component with animated shimmer trigger and a vertical progress bar.

npx shadcn add "https://www.odysseyui.com/r/components-ai-steps.json"
free

Where it goes

onboarding

Interaction

accordion

Tags auto-generated by rules on 9/24/2026 — empty means no evidence, not lack of support.

Dependencies

Bundle weight · light
iconslucide-react

Registry dependencies

collapsible@odysseyui/components-texts-text-shimmer

Files1 · source cached 10/9/2026, 6:46:32 PM

registry/components/ai/steps/index.tsx
'use client';

import {
  Collapsible,
  CollapsibleContent,
  CollapsibleTrigger,
} from '@/components/ui/collapsible';
import { cn } from '@/lib/utils';
import { ChevronDown } from 'lucide-react';
import { ShimmerText } from '@/components/odyssey/components/texts/text-shimmer';

export type StepsItemProps = React.ComponentProps<'div'>;

export const StepsItem = ({
  children,
  className,
  ...props
}: StepsItemProps) => (
  <div
    className={cn(
      'text-muted-foreground hover:text-foreground text-sm transition-colors duration-200 [&_strong]:text-blue-500',
      className,
    )}
    {...props}
  >
    {children}
  </div>
);

export type StepsTriggerProps = React.ComponentProps<
  typeof CollapsibleTrigger
> & {
  leftIcon?: React.ReactNode;
  swapIconOnHover?: boolean;
};

export const StepsTrigger = ({
  children,
  className,
  leftIcon,
  swapIconOnHover = true,
  ...props
}: StepsTriggerProps) => (
  <CollapsibleTrigger
    className={cn(
      'group text-muted-foreground hover:text-blue-500 flex w-full cursor-pointer items-center justify-start gap-1 text-sm transition-colors duration-200',
      className,
    )}
    {...props}
  >
    <div className="flex items-center gap-2">
      {leftIcon ? (
        <span className="relative inline-flex size-4 items-center justify-center">
          <span
            className={cn(
              'transition-opacity',
              swapIconOnHover && 'group-hover:opacity-0',
            )}
          >
            {leftIcon}
          </span>
          {swapIconOnHover && (
            <ChevronDown className="absolute size-4 opacity-0 transition-opacity group-hover:opacity-100 group-hover:text-blue-500 group-data-[state=open]:rotate-180" />
          )}
        </span>
      ) : null}
      <span>
        <ShimmerText text={children as string} />
      </span>
    </div>
    {!leftIcon && (
      <ChevronDown className="size-4 transition-transform group-hover:text-blue-500 group-data-[state=open]:rotate-180" />
    )}
  </CollapsibleTrigger>
);

export type StepsContentProps = React.ComponentProps<
  typeof CollapsibleContent
> & {
  bar?: React.ReactNode;
};

export const StepsContent = ({
  children,
  className,
  bar,
  ...props
}: StepsContentProps) => {
  return (
    <CollapsibleContent
      className={cn(
        'text-popover-foreground data-[state=closed]:animate-collapsible-up data-[state=open]:animate-collapsible-down overflow-hidden cursor-pointer',
        className,
      )}
      {...props}
    >
      <div className="group mt-3 grid max-w-full min-w-0 grid-cols-[min-content_minmax(0,1fr)] items-start gap-x-3">
        <div className="min-w-0 self-stretch">{bar ?? <StepsBar />}</div>
        <div className="min-w-0 space-y-2">{children}</div>
      </div>
    </CollapsibleContent>
  );
};

export type StepsBarProps = React.HTMLAttributes<HTMLDivElement>;

export const StepsBar = ({ className, ...props }: StepsBarProps) => (
  <div
    className={cn(
      'bg-muted h-full w-[2px] transition-colors duration-300 group-hover:bg-blue-500/40',
      className,
    )}
    aria-hidden
    {...props}
  />
);

export type StepsProps = React.ComponentProps<typeof Collapsible>;

export function Steps({ defaultOpen = true, className, ...props }: StepsProps) {
  return (
    <Collapsible
      className={cn(className)}
      defaultOpen={defaultOpen}
      {...props}
    />
  );
}

More from odysseyui

Browse all components in this library →