# Welcome Hero with Step Indicator

> Centered welcome screen featuring a hero image, step counter, and dual CTAs. Perfect for app onboarding flows that guide users through a multi-step setup process.

A shadcn/ui block from the [@beste-ui](https://ui.beste.co) registry, listed on [shadcn.exchange](https://shadcn.exchange/r/beste-ui/onboarding1).

- Page, with a live preview: https://shadcn.exchange/r/beste-ui/onboarding1
- Screenshot: https://abzjhwgwpdpz7ua5.public.blob.vercel-storage.com/shots/beste-ui/onboarding1-light-1aaf9f6d0e3f4762.png
- Registry item: https://ui.beste.co/r/onboarding1.json
- Listed: 2026-10-13, source last changed 2026-10-13

## Install

```bash
npx shadcn@latest add https://ui.beste.co/r/onboarding1.json
```

Run it in a project that has shadcn/ui set up. It adds the files below and installs what they depend on.

## Dependencies

- npm packages: `clsx`, `lucide-react`, `tailwind-merge`
- Registry items: `badge`, `button`

## Files

### `registry/onboarding1/onboarding1.tsx`

```tsx
"use client";

import { ArrowRight, Sparkles } from "lucide-react";

import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import Link from "next/link";
import { cn } from "@/lib/utils";

interface ButtonConfig {
  label: string;
  href: string;
  variant?: "default" | "ghost" | "outline";
}

interface Onboarding1Props {
  badge?: string;
  heading?: string;
  description?: string;
  image?: {
    src: string;
    alt: string;
  };
  currentStep?: number;
  totalSteps?: number;
  buttons?: ButtonConfig[];
  labels?: {
    stepOf?: string;
  };
  className?: string;
}

export const onboarding1Demo: Onboarding1Props = {
  badge: "Welcome",
  heading: "Let's get you started",
  description:
    "We're excited to have you here. Follow a few simple steps to set up your account and unlock the full potential of our platform.",
  image: {
    src: "https://images.unsplash.com/photo-1616449973117-0e1d99c56ed3?q=80&w=2874&auto=format&fit=crop&ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D",
    alt: "Team collaboration",
  },
  currentStep: 1,
  totalSteps: 4,
  buttons: [
    { label: "Get Started", href: "#", variant: "default" },
    { label: "Skip for now", href: "#", variant: "ghost" },
  ],
  labels: {
    stepOf: "Step {current} of {total}",
  },
};

export function Onboarding1({
  badge,
  heading,
  description,
  image,
  currentStep = 1,
  totalSteps = 4,
  buttons = [],
  labels = {},
  className,
}: Onboarding1Props) {
  const mergedLabels = { ...onboarding1Demo.labels, ...labels };
  const stepText = mergedLabels.stepOf
    ?.replace("{current}", String(currentStep))
    .replace("{total}", String(totalSteps));

  return (
    <section className={cn("py-16 md:py-24 w-full", className)}>
      <div className="mx-auto max-w-4xl px-4 md:px-6">
        <div className="flex flex-col items-center text-center">
          {/* Step indicator */}
          {stepText && <p className="mb-4 text-sm text-muted-foreground">{stepText}</p>}

          {/* Badge */}
          {badge && (
            <Badge variant="outline" className="mb-6">
              <Sparkles className="mr-1 size-3" />
              {badge}
            </Badge>
          )}

          {/* Heading */}
          {heading && <h1 className="mb-4 text-3xl font-bold md:text-5xl">{heading}</h1>}

          {/* Description */}
          {description && (
            <p className="mb-8 max-w-2xl text-lg text-muted-foreground">{description}</p>
          )}

          {/* Image */}
          {image && (
            <div className="relative mb-8 aspect-video w-full max-w-2xl overflow-hidden rounded-xl border">
              <img
                className="absolute inset-0 size-full object-cover"
                src={image.src}
                alt={image.alt}
              />
            </div>
          )}

          {/* Buttons */}
          {buttons.length > 0 && (
            <div className="flex flex-col gap-3 sm:flex-row">
              {buttons.map((button, index) => (
                <Button key={index} size="lg" variant={button.variant ?? "default"} asChild>
                  <Link href={button.href}>
                    {button.label}
                    {index === 0 && <ArrowRight className="size-4" />}
                  </Link>
                </Button>
              ))}
            </div>
          )}
        </div>
      </div>
    </section>
  );
}
```
