Product Tour Switcher

(Inside the product)

One screen for the work everyone keeps in spreadsheets.

Auralis holds the brief, the schedule and the invoice in the same place, so nobody has to reconstruct a project from three inboxes.

Every brief written against the same spine

New work starts from a template your team can argue with, not a blank document. Missing answers stay visible instead of quietly becoming assumptions.

Hallvard Press, catalogue

Brief completeness, 7 of 9 answered

AudienceIndependent booksellersAgreed
Success measurePreorders before printAgreed
Budget ceilingNot yet statedOpen
About this block

Product Tour SwitcherPRO

Product tour driven by pill tabs: each step pairs an explanation and a pill CTA with a soft interface panel built from divs, listing labelled rows with their value and current status.

Saas106: Product Tour Switcher

Product tour driven by pill tabs. Each step pairs an explanation and a pill CTA with a soft interface panel built entirely from divs, listing labelled rows with their value and current status, so the tour needs no screenshots to stay current.

Upgrade to Pro

Pro blocks install through the shadcn CLI with your license key and ship their full source. Docs and live previews stay open to everyone, so you can read every block's details first.

Upgrade Now

Installation

Swap YOUR_EMAIL and YOUR_KEY for the email and license key on your account. Find your license key on your account page.

Radix flavor

bash
npx shadcn add "https://ui.beste.co/r/saas106?email=YOUR_EMAIL&license_key=YOUR_KEY"

Base UI flavor

bash
npx shadcn add "https://ui.beste.co/r-base/saas106?email=YOUR_EMAIL&license_key=YOUR_KEY"

This installs the block to components/beste/block/saas106.tsx plus the badge7 eyebrow and button12 pill button it uses.

Quick start

The installed file exports saas106Demo alongside the block: the exact props behind the preview above. Spread it to get a working tour in one line.

tsx
import { Saas106, saas106Demo } from "@/components/beste/block/saas106";

export default function Page() {
  return <Saas106 {...saas106Demo} />;
}

Then replace the demo with your own props. Written out, a trimmed setup looks like this:

tsx
import { Saas106 } from "@/components/beste/block/saas106";

export default function Page() {
  return (
    <Saas106
      badge={{ label: "Inside the product" }}
      heading="One screen for the work everyone keeps in spreadsheets."
      description="The brief, the schedule and the invoice in the same place."
      steps={[
        {
          label: "Briefs",
          title: "Every brief written against the same spine",
          description: "New work starts from a template your team can argue with.",
          panelTitle: "Hallvard Press, catalogue",
          panelCaption: "Brief completeness, 7 of 9 answered",
          rows: [{ title: "Audience", value: "Independent booksellers", status: "Agreed" }],
        },
      ]}
      button={{ label: "Take the full tour", href: "/tour" }}
    />
  );
}

Props

PropTypeDefaultDescription
badge{ label: string }–Eyebrow label rendered as a Badge7
headingstring–Section heading in the left column
descriptionstring–Supporting paragraph, right-aligned from md up
stepsTourStep[][]Each step is one pill tab, one explanation and one panel
buttonActionButton–Pill CTA under the active explanation, rendered as Button12
classNamestring–Extra classes for the outer <section>
ts
type TourStep = {
  label: string;
  title: string;
  description: string;
  panelTitle: string;
  panelCaption: string;
  rows: PanelRow[];
};

type PanelRow = {
  title: string;
  value: string;
  status: string;
};

type ActionButton = {
  label: string;
  href: string;
};

Behavior notes

  • The active step lives in useState(0), so the first step renders on the server and the panel is populated before hydration.
  • Tabs are <button type="button"> with aria-pressed and an explicit cursor-pointer, and switching never touches the URL.
  • The interface panel is drawn with plain divs on bg-muted rather than an image, so it never goes stale against the real product and costs nothing to load.
  • Panel rows use border-current/10 rather than the page border token, which is the set's rule for hairlines inside a tinted surface: the rule inherits the panel's own text colour instead of fighting it.
  • Rows are a grid, not a justify-between flex row: md:grid-cols-[minmax(0,10rem)_1fr_auto] fixes the label track so every value starts on the same x position and every status ends flush right, which a flex row cannot do once the labels differ in length.
  • Below md the grid drops to two tracks with the status wrapping onto its own line, so a long value never squeezes the status into a single character per line.
  • Row status is free text, so a row can say Agreed, Overdue or Held until Friday without the block owning a status enum or a colour scale.
  • The button is a single prop shared by every step rather than one per step, which keeps the primary action stable while the tour content changes.

More SaaS blocks

View all SaaS
PRO

saas13

Plan Comparison Table

PRO

saas104

Plan Comparison Table

PRO

saas103

Onboarding Split

Get Pro access