diff --git a/apps/apollo-vertex/app/patterns/_meta.ts b/apps/apollo-vertex/app/patterns/_meta.ts
index 3e0062d3d..e3a799d88 100644
--- a/apps/apollo-vertex/app/patterns/_meta.ts
+++ b/apps/apollo-vertex/app/patterns/_meta.ts
@@ -2,6 +2,7 @@ export default {
"ai-chat": "AI Chat",
"feedback-vote-widget": "Feedback Vote Widget",
"metric-card": "Metric Card",
+ "onboarding-tour-joyride": "Onboarding tour (Joyride)",
"page-header": "Page Header",
shell: "Shell",
};
diff --git a/apps/apollo-vertex/app/patterns/onboarding-tour-joyride/onboarding-tour-joyride-demo.tsx b/apps/apollo-vertex/app/patterns/onboarding-tour-joyride/onboarding-tour-joyride-demo.tsx
new file mode 100644
index 000000000..cf50a238c
--- /dev/null
+++ b/apps/apollo-vertex/app/patterns/onboarding-tour-joyride/onboarding-tour-joyride-demo.tsx
@@ -0,0 +1,77 @@
+"use client";
+
+import { Button } from "@/registry/button/button";
+import type { TourDefinition } from "@/registry/onboarding-tour-joyride";
+import {
+ OnboardingTourProvider,
+ resetTourState,
+ useOnboardingTour,
+} from "@/registry/onboarding-tour-joyride";
+
+const DEMO_TOUR: TourDefinition = {
+ id: "onboarding-tour-joyride-page-demo",
+ steps: [
+ {
+ id: "welcome",
+ type: "modal",
+ title: "A tour of the tour component",
+ body: "This is the onboarding tour running on this very page. Click through to see how each piece looks in context.",
+ image: "/assets/onboarding-tour-welcome.png",
+ nextLabel: "Show me around",
+ },
+ {
+ id: "welcome-modal-preview",
+ selector: "#tour-welcome-preview",
+ title: "Welcome modals",
+ body: "Modal-type steps render centered over your app with an image, headline, body, and CTA — ideal for intros and success screens.",
+ placement: "top",
+ },
+ {
+ id: "step-popover-preview",
+ selector: "#tour-popover-preview",
+ title: "Step popovers",
+ body: "The default step type is a popover anchored to any element — progress bars, a tip slot, and navigation.",
+ tip: "You're looking at one right now.",
+ placement: "top",
+ },
+ {
+ id: "features",
+ selector: "#tour-features",
+ title: "What's included",
+ body: "Spotlight overlay, keyboard navigation, conditional steps, persistence, and dark mode support.",
+ placement: "top",
+ },
+ {
+ id: "install",
+ selector: "#tour-install",
+ title: "Drop it in",
+ body: "One command to add it to your project.",
+ placement: "top",
+ },
+ ],
+};
+
+const DEMO_TOURS = [DEMO_TOUR];
+
+function TourTrigger() {
+ const { startTour } = useOnboardingTour();
+
+ return (
+ {
+ resetTourState(DEMO_TOUR.id);
+ startTour(DEMO_TOUR.id);
+ }}
+ >
+ {"Start tour demo"}
+
+ );
+}
+
+export function OnboardingTourJoyrideDemoTrigger() {
+ return (
+
+
+
+ );
+}
diff --git a/apps/apollo-vertex/app/patterns/onboarding-tour-joyride/onboarding-tour-joyride-static-previews.tsx b/apps/apollo-vertex/app/patterns/onboarding-tour-joyride/onboarding-tour-joyride-static-previews.tsx
new file mode 100644
index 000000000..757478d37
--- /dev/null
+++ b/apps/apollo-vertex/app/patterns/onboarding-tour-joyride/onboarding-tour-joyride-static-previews.tsx
@@ -0,0 +1,46 @@
+"use client";
+
+import { OnboardingTourJoyridePopoverCard } from "@/registry/onboarding-tour-joyride/onboarding-tour-joyride-popover";
+import { OnboardingTourJoyrideWelcomeModalCard } from "@/registry/onboarding-tour-joyride/onboarding-tour-joyride-welcome-modal";
+
+function noop() {
+ // no-op for static previews
+}
+
+export function WelcomeModalPreview() {
+ return (
+
+ );
+}
+
+export function PopoverStepPreview() {
+ return (
+
+ );
+}
diff --git a/apps/apollo-vertex/app/patterns/onboarding-tour-joyride/page.mdx b/apps/apollo-vertex/app/patterns/onboarding-tour-joyride/page.mdx
new file mode 100644
index 000000000..d0a41b273
--- /dev/null
+++ b/apps/apollo-vertex/app/patterns/onboarding-tour-joyride/page.mdx
@@ -0,0 +1,214 @@
+import { WelcomeModalPreview, PopoverStepPreview } from './onboarding-tour-joyride-static-previews';
+import { OnboardingTourJoyrideDemoTrigger } from './onboarding-tour-joyride-demo';
+
+# Onboarding tour (Joyride)
+
+A guided onboarding tour system powered by [React Joyride](https://docs.react-joyride.com/) with a custom Apollo Vertex–styled tooltip, welcome modals, and step-by-step navigation. Consumers define their own tour steps and point them at app-specific elements via CSS selectors.
+
+
+
+
+
+## Welcome modal
+
+Use the `type: 'modal'` step to show a full-screen centered modal — ideal for welcome screens, success states, or intro steps.
+
+
+
+
+
+## Step popover
+
+The default step type renders a card-style popover with progress bars, navigation, and an optional tip section.
+
+
+
+## Features
+
+
+
+- **Spotlight overlay**: Highlights target elements with a cutout in a dimmed overlay (via React Joyride)
+- **Popover cards**: Step-by-step cards with progress bars, title, body, tips, and navigation
+- **Welcome modal**: Full-screen modal variant for intro/success steps with optional images
+- **CSS selector targeting**: Target elements by CSS selector — no wrapper components needed
+- **Smooth transitions**: Native React rendering of the tooltip animates cleanly between steps
+- **Conditional steps**: Gate steps on conditions with type-safe generics
+- **Keyboard navigation**: Escape to skip the tour, focus trap via React Joyride
+- **Persistence**: Tracks completed tours in localStorage so they don't repeat
+- **Dark mode**: Automatically adapts overlay color to the current theme
+
+
+
+## Installation
+
+
+
+```bash
+npx shadcn@latest add @uipath/onboarding-tour-joyride
+```
+
+
+
+## Usage
+
+### 1. Define your tour
+
+```tsx
+import type { TourDefinition } from "@/components/ui/onboarding-tour-joyride";
+
+const myTour: TourDefinition = {
+ id: "my-onboarding-tour",
+ steps: [
+ {
+ id: "welcome",
+ type: "modal",
+ title: "Welcome!",
+ body: "Let us show you around your new workspace.",
+ tip: "This tour will only show once.",
+ nextLabel: "Let's go",
+ image: "/assets/welcome.png", // optional
+ },
+ {
+ id: "sidebar",
+ selector: "[data-sidebar]",
+ title: "Navigation",
+ body: "Use the sidebar to move between sections.",
+ placement: "right",
+ },
+ {
+ id: "search",
+ selector: "#search-bar",
+ title: "Quick search",
+ body: "Find anything across your workspace.",
+ placement: "bottom",
+ tip: "Try pressing Cmd+K for a shortcut.",
+ },
+ ],
+};
+```
+
+### 2. Set up the provider
+
+Wrap your app (or a section of it) with `OnboardingTourProvider`:
+
+```tsx
+import {
+ OnboardingTourProvider,
+} from "@/components/ui/onboarding-tour-joyride";
+
+function App() {
+ return (
+
+
+
+ );
+}
+```
+
+### 3. Start the tour
+
+```tsx
+import { useOnboardingTour, isTourCompleted } from "@/components/ui/onboarding-tour-joyride";
+
+function WelcomeBanner() {
+ const { startTour } = useOnboardingTour();
+
+ if (isTourCompleted("my-onboarding-tour")) return null;
+
+ return (
+ startTour("my-onboarding-tour")}>
+ Take a tour
+
+ );
+}
+```
+
+## Type-safe conditional steps
+
+Use generics to make condition keys type-safe. The `waitFor` property gates steps on conditions set via `setCondition`:
+
+```tsx
+type MyConditions = "profile-saved" | "data-loaded";
+
+const tour: TourDefinition = {
+ id: "conditional-tour",
+ steps: [
+ {
+ id: "intro",
+ selector: "#intro-section",
+ title: "Getting started",
+ body: "First, let's set up your profile.",
+ placement: "right",
+ },
+ {
+ id: "results",
+ selector: "#results-panel",
+ title: "Your results",
+ body: "Here's what we found.",
+ placement: "left",
+ waitFor: "profile-saved", // type-safe — must be a MyConditions key
+ },
+ ],
+};
+
+// In your component:
+const { setCondition } = useOnboardingTour();
+
+async function handleSaveProfile(formData: FormData) {
+ await saveProfile(formData);
+ setCondition("profile-saved", true); // type-safe
+}
+```
+
+## Resetting a tour
+
+Import `resetTourState` to clear completion state so a tour can be shown again:
+
+```tsx
+import { resetTourState } from "@/components/ui/onboarding-tour-joyride";
+
+function SettingsPage() {
+ return (
+ resetTourState("my-onboarding-tour")}>
+ Replay onboarding tour
+
+ );
+}
+```
+
+## Step types
+
+| Property | Type | Description |
+|----------|------|-------------|
+| `id` | `string` | Unique step identifier |
+| `selector` | `string` | CSS selector for the target element. Omit for centered/modal steps. |
+| `title` | `string` | Step title |
+| `body` | `ReactNode` | Step body content |
+| `tip` | `string` | Optional tip shown with a lightbulb icon |
+| `placement` | `'top' \| 'bottom' \| 'left' \| 'right'` | Popover position relative to target |
+| `type` | `'popover' \| 'modal'` | Render as popover card (default) or welcome modal |
+| `image` | `string` | Image URL for modal-type steps |
+| `waitFor` | `TCondition` | Type-safe condition key that must be true before step can be reached |
+| `nextLabel` | `string` | Custom label for the Next button |
+| `onEnter` | `() => void` | Callback when step becomes active |
+
+## Components
+
+| Component | Description |
+|-----------|-------------|
+| `OnboardingTourProvider` | Context provider — wrap your app with this. Accepts `tours` and optional `initialConditions`. |
+| `OnboardingTourJoyridePopover` | Joyride `tooltipComponent` that renders the step card. |
+| `OnboardingTourJoyridePopoverCard` | Standalone popover card (useful for previews or custom renderers). |
+| `OnboardingTourJoyrideWelcomeModal` | The welcome/success modal (portal + focus trap via Radix Dialog). |
+| `OnboardingTourJoyrideWelcomeModalCard` | Standalone modal card (useful for previews). |
+
+## API
+
+| Export | Description |
+|--------|-------------|
+| `useOnboardingTour()` | Access tour actions: `startTour`, `setCondition`. Must be inside `OnboardingTourProvider`. |
+| `isTourCompleted(tourId)` | Check whether a tour has been completed. |
+| `markTourCompleted(tourId)` | Manually mark a tour as completed. |
+| `resetTourState(tourId)` | Clear completion state so the tour can be shown again. |
diff --git a/apps/apollo-vertex/public/assets/onboarding-tour-welcome.png b/apps/apollo-vertex/public/assets/onboarding-tour-welcome.png
new file mode 100644
index 000000000..162a4dd7a
Binary files /dev/null and b/apps/apollo-vertex/public/assets/onboarding-tour-welcome.png differ