Docs
Onboarding Steps

Onboarding Steps

A premium, interactive step-by-step guide component for SaaS onboarding flows. Features smooth progress tracking, code snippet display, and elegant animations.

Start Building in Minutes

Follow these three simple steps to integrate our powerful SDK into your application.

1

Install the Package

Start by installing the artifact-ui package using your preferred package manager. It's lightweight and fully typed.

2

Configure Theme

Add the tailwind plugin to your configuration file to enable the custom animations and utilities.

3

Import Components

You're all set! Import any component and start building your dream interface in minutes.

install.tsx
npm install artifact-ui
# or
pnpm add artifact-ui
# or
yarn add artifact-ui

Installation

Install dependencies

npm install framer-motion lucide-react

Copy and paste the following code into your project.

"use client";
 
import React, { useState } from "react";
import { motion, AnimatePresence } from "framer-motion";
import { cn } from "@/lib/utils";
import { Check, ChevronRight, Copy } from "lucide-react";
 
interface Step {
  id: string;
  title: string;
  description: string;
  code?: string;
  image?: string;
}
 
interface OnboardingStepsProps {
  steps: Step[];
  title?: string;
  description?: string;
  ctaText?: string;
  ctaLink?: string;
  className?: string;
}
 
export function OnboardingSteps({
  steps,
  title = "How it works",
  description = "Get up and running in minutes with our simple onboarding process.",
  ctaText = "Get Started",
  ctaLink = "#",
  className,
}: OnboardingStepsProps) {
  const [activeStep, setActiveStep] = useState(0);
  const [copied, setCopied] = useState(false);
 
  const handleCopy = (code: string) => {
    navigator.clipboard.writeText(code);
    setCopied(true);
    setTimeout(() => setCopied(false), 2000);
  };
 
  return (
    <div
      className={cn(
        "w-full max-w-6xl mx-auto p-6 md:p-12 font-sans",
        className,
      )}
    >
      <div className="text-center mb-20 space-y-4">
        <h2 className="text-4xl md:text-5xl font-bold tracking-tight text-foreground">
          {title}
        </h2>
        <p className="text-muted-foreground text-lg max-w-2xl mx-auto">
          {description}
        </p>
      </div>
 
      <div className="grid grid-cols-1 lg:grid-cols-2 gap-16 items-start">
        {/* Left Column: Steps List */}
        <div className="space-y-4 relative pl-4">
          {/* Continuous Vertical Line */}
          <div className="absolute left-[59px] top-0 bottom-0 w-[2px] bg-white/10 hidden md:block" />
 
          {/* Active Progress Line */}
          <motion.div
            className="absolute left-[59px] top-0 w-[2px] bg-white hidden md:block origin-top"
            initial={{ height: 0 }}
            animate={{ height: `${(activeStep / (steps.length - 1)) * 100}%` }}
            transition={{ duration: 0.5, ease: "easeInOut" }}
          />
 
          {steps.map((step, index) => (
            <div
              key={step.id}
              className={cn(
                "relative flex gap-8 p-6 rounded-3xl transition-all duration-300 cursor-pointer border border-transparent group",
                activeStep === index
                  ? "bg-white/5 border-white/10 shadow-[0_0_30px_-10px_rgba(255,255,255,0.1)] backdrop-blur-sm"
                  : "hover:bg-white/5 border-transparent",
              )}
              onClick={() => setActiveStep(index)}
            >
              {/* Step Indicator */}
              <div className="relative z-10 flex-shrink-0 mt-1">
                <div
                  className={cn(
                    "w-10 h-10 rounded-full flex items-center justify-center text-sm font-bold border-2 transition-all duration-300",
                    activeStep === index
                      ? "bg-white border-white text-black scale-110 shadow-[0_0_20px_-5px_rgba(255,255,255,0.5)]"
                      : index < activeStep
                        ? "bg-white border-white text-black"
                        : "bg-[#0f1117] border-white/20 text-white/40 group-hover:border-white/40 group-hover:text-white/60",
                  )}
                >
                  {index < activeStep ? (
                    <Check className="w-5 h-5" />
                  ) : (
                    <span>{index + 1}</span>
                  )}
                </div>
              </div>
 
              {/* Step Content */}
              <div className="space-y-3 pt-1">
                <h3
                  className={cn(
                    "text-xl font-bold transition-colors",
                    activeStep === index
                      ? "text-foreground"
                      : "text-muted-foreground group-hover:text-foreground/80",
                  )}
                >
                  {step.title}
                </h3>
                <p className="text-muted-foreground leading-relaxed text-base">
                  {step.description}
                </p>
              </div>
            </div>
          ))}
        </div>
 
        {/* Right Column: Interactive Preview / Code */}
        <div className="relative lg:sticky lg:top-8">
          <div className="relative rounded-2xl overflow-hidden border border-white/10 bg-[#0f1117] shadow-2xl min-h-[400px]">
            {/* Window Controls */}
            <div className="flex items-center justify-between px-6 py-4 border-b border-white/5 bg-white/[0.02]">
              <div className="flex items-center gap-2">
                <div className="w-3 h-3 rounded-full bg-[#FF5F56]" />
                <div className="w-3 h-3 rounded-full bg-[#FFBD2E]" />
                <div className="w-3 h-3 rounded-full bg-[#27C93F]" />
              </div>
              <div className="text-xs font-medium text-white/20 font-mono">
                {steps[activeStep].id}.tsx
              </div>
            </div>
 
            {/* Content Area */}
            <div className="relative p-6 h-full min-h-[340px] flex flex-col">
              <AnimatePresence mode="wait">
                <motion.div
                  key={activeStep}
                  initial={{ opacity: 0, y: 10, filter: "blur(4px)" }}
                  animate={{ opacity: 1, y: 0, filter: "blur(0px)" }}
                  exit={{ opacity: 0, y: -10, filter: "blur(4px)" }}
                  transition={{ duration: 0.3 }}
                  className="h-full"
                >
                  {steps[activeStep].code ? (
                    <div className="relative group h-full">
                      <button
                        onClick={() => handleCopy(steps[activeStep].code!)}
                        className="absolute right-0 top-0 p-2 rounded-lg bg-white/5 text-white/40 opacity-0 group-hover:opacity-100 transition-all hover:bg-white/10 hover:text-white"
                      >
                        {copied ? (
                          <Check className="w-4 h-4" />
                        ) : (
                          <Copy className="w-4 h-4" />
                        )}
                      </button>
                      <pre className="font-mono text-sm leading-relaxed text-blue-100/90 overflow-x-auto p-2">
                        <code>{steps[activeStep].code}</code>
                      </pre>
                    </div>
                  ) : (
                    <div className="h-full flex items-center justify-center">
                      {steps[activeStep].image ? (
                        // eslint-disable-next-line @next/next/no-img-element
                        <img
                          src={steps[activeStep].image}
                          alt={steps[activeStep].title}
                          className="w-full h-full object-cover rounded-lg"
                        />
                      ) : (
                        <div className="text-center p-8 text-white/20">
                          <div className="w-16 h-16 border-2 border-dashed border-white/10 rounded-xl flex items-center justify-center mx-auto mb-4">
                            <span className="text-2xl font-bold">
                              {activeStep + 1}
                            </span>
                          </div>
                          <p>No preview available</p>
                        </div>
                      )}
                    </div>
                  )}
                </motion.div>
              </AnimatePresence>
            </div>
          </div>
 
          {/* Decorative Glow */}
          <div className="absolute -inset-4 bg-primary/20 blur-3xl -z-10 rounded-[3rem] opacity-20" />
        </div>
      </div>
 
      {/* Bottom CTA */}
      <div className="mt-20 text-center">
        <a
          href={ctaLink}
          className="inline-flex items-center justify-center px-8 py-4 text-lg font-semibold text-primary-foreground bg-primary rounded-full hover:bg-primary/90 transition-all hover:scale-105 shadow-[0_0_40px_-10px_var(--primary)] group"
        >
          {ctaText}
          <ChevronRight className="ml-2 w-5 h-5 group-hover:translate-x-1 transition-transform" />
        </a>
      </div>
    </div>
  );
}

Update the import paths to match your project setup.

Features

  • Interactive Progress - Smooth vertical progress line that tracks the active step
  • Code Snippets - Built-in code block display with copy functionality
  • Visual Previews - Support for images or code per step
  • Premium Design - Glassmorphism effects, glow animations, and clean typography
  • Responsive Layout - Stacks elegantly on mobile devices
  • Customizable - Easy to style with Tailwind CSS classes
  • Accessible - Keyboard navigation and semantic structure

Usage

Basic Usage

import OnboardingSteps from "@/components/ui/onboarding-steps";
 
const steps = [
  {
    id: "step-1",
    title: "Sign Up",
    description: "Create your account in seconds.",
    image: "/images/signup.png",
  },
  {
    id: "step-2",
    title: "Connect Data",
    description: "Link your data sources securely.",
    image: "/images/connect.png",
  },
];
 
export default function Page() {
  return (
    <OnboardingSteps
      steps={steps}
      title="Get Started"
      description="Follow these simple steps."
    />
  );
}

With Code Snippets

Perfect for developer tools and API documentation.

const devSteps = [
  {
    id: "install",
    title: "Installation",
    description: "Install the package via npm.",
    code: "npm install my-awesome-package"
  },
  {
    id: "config",
    title: "Configuration",
    description: "Add your API key to the config.",
    code: "export const config = { apiKey: '...' }"
  }
]
 
<OnboardingSteps steps={devSteps} />

Props

OnboardingStepsProps

PropTypeDescriptionDefault
stepsStep[]Array of steps to display.Required
titlestringTitle of the section."How it works"
descriptionstringDescription of the section."Get up and running in minutes..."
ctaTextstringText for the Call to Action button."Get Started"
ctaLinkstringLink for the Call to Action button."#"
classNamestringAdditional CSS classes for the container.-

Step Interface

interface Step {
  id: string;
  title: string;
  description: string;
  code?: string; // Optional: Code to display in the window
  image?: string; // Optional: Image URL to display if no code is provided
}

Use Cases

Perfect for:

  • SaaS Onboarding: Guide users through account setup.
  • Developer Documentation: Show how to install and use your SDK.
  • Feature Walkthroughs: Explain complex features step-by-step.
  • Process Explanation: Visualize a multi-stage process (e.g., "How it works").

Common Patterns

Full Page Onboarding

For a dedicated onboarding page, center the component and add a max-width.

<div className="min-h-screen bg-background flex items-center justify-center py-20">
  <OnboardingSteps className="max-w-5xl" steps={steps} />
</div>

You can wrap the component in a Dialog for a popup guide.

<Dialog>
  <DialogContent className="max-w-4xl bg-background/95 backdrop-blur-xl">
    <OnboardingSteps steps={steps} />
  </DialogContent>
</Dialog>

Customization

Changing Colors

The component uses Tailwind's primary and muted colors by default. You can override these in your tailwind.config.js or by passing a className to the component.

// Example: Custom blue theme
<OnboardingSteps className="[--primary:theme(colors.blue.500)]" steps={steps} />

Adjusting Layout

The component uses a grid layout that switches to a single column on mobile. You can adjust the gap and padding via the className prop.

<OnboardingSteps className="max-w-7xl gap-20" steps={steps} />

Examples

API Integration Guide

const apiSteps = [
  {
    id: "auth",
    title: "Authentication",
    description: "Get your API key from the dashboard.",
    code: "const client = new Client({ apiKey: 'sk_...' })",
  },
  {
    id: "request",
    title: "Make a Request",
    description: "Fetch data from our endpoints.",
    code: "await client.users.list()",
  },
];

SaaS Onboarding Flow

const saasSteps = [
  {
    id: "invite",
    title: "Invite Team",
    description: "Add your team members to collaborate.",
    image: "/onboarding/invite-team.png",
  },
  {
    id: "setup",
    title: "Setup Workflow",
    description: "Define your first automation workflow.",
    image: "/onboarding/workflow.png",
  },
];