Skip to content
All Taste components

Onboarding

Travel onboarding

A trip planner's intro with the headline first and a globe, pin and dotted flight path below it.

text above illustration · step counter · dotted route · 3-step dots · skip · next

2 of 3

Plan every trip in one place

Flights, stays and day plans in a single itinerary you can share.

// Mobile-first screen for a React web app; the same markup maps to React Native via NativeWind (View, Text, Pressable, react-native-svg).
import { cn } from "@/lib/utils";

const slides = [
  { title: "Find your next trip", body: "Ideas for weekends away and longer journeys, sorted by budget." },
  { title: "Plan every trip in one place", body: "Flights, stays and day plans in a single itinerary you can share." },
  { title: "Go light", body: "Tickets and bookings work offline, right when you need them." },
];

function Illustration({ className }: { className?: string }) {
  return (
    <svg viewBox="0 0 240 210" aria-hidden="true" className={cn("h-full w-full", className)}>
      <path d="M22 62a10 10 0 0 1 18-7 8 8 0 0 1 14 7z" className="fill-primary-soft" />
      <path d="M168 128a9 9 0 0 1 16-6 7 7 0 0 1 12 6z" className="fill-primary-soft" />
      <circle cx="112" cy="132" r="70" className="fill-brand-200" />
      <path d="M68 98c10-8 26-6 30 4s-6 14 2 22-4 20-16 16-12-14-20-12-10-12-2-18 0-8 6-12z" className="fill-brand-400" />
      <path d="M138 126c8-4 22 0 26 10s-2 22-12 24-10-6-16-4-8-10-4-16 2-10 6-14z" className="fill-brand-400" />
      <path d="M118 78c6-2 12 2 10 6s-8 4-12 2-2-6 2-8z" className="fill-brand-400" />
      <path d="M80 92C76 52 128 30 182 58" strokeWidth="3.5" strokeDasharray="0.1 10" strokeLinecap="round" strokeLinejoin="round" fill="none" className="stroke-brand-600" />
      <path d="M80 126c-11-13-15-19-15-26a15 15 0 0 1 30 0c0 7-4 13-15 26z" className="fill-brand-800" />
      <circle cx="80" cy="100" r="5.5" className="fill-neutral-50" />
      <path d="M-26 4 0-8l26 12v7L0 4l-26 9z" transform="translate(198 52) rotate(32)" className="fill-brand-600" />
      <path d="M-10 20 0 15l10 5v5L0 22l-10 3z" transform="translate(198 52) rotate(32)" className="fill-brand-600" />
      <rect x="-5" y="-24" width="10" height="48" rx="5" strokeWidth="2.5" transform="translate(198 52) rotate(32)" className="fill-neutral-50 stroke-brand-800" />
    </svg>
  );
}

function Dots({ step, total, className }: { step: number; total: number; className?: string }) {
  return (
    <div className={cn("flex gap-1.5", className)} role="img" aria-label={"Step " + (step + 1) + " of " + total}>
      {Array.from({ length: total }, (_, i) => (
        <span key={i} className={cn("h-1.5 rounded-full transition-all", i === step ? "w-[22px] bg-primary" : "w-1.5 bg-border-strong")} />
      ))}
    </div>
  );
}

export default function TravelOnboarding({ step = 1, onNext, onSkip }: { step?: number; onNext?: () => void; onSkip?: () => void }) {
  const slide = slides[step];
  return (
    <main className="mx-auto flex min-h-dvh max-w-md flex-col bg-bg px-6 pb-8 font-sans text-fg">
      <header className="-mr-3.5 flex h-12 items-center justify-between">
        <span className="text-[13px] font-medium tabular-nums text-fg-subtle">{step + 1} of {slides.length}</span>
        <button type="button" onClick={onSkip} className="h-11 rounded px-3.5 text-sm font-medium text-fg-muted hover:bg-surface-2">
          Skip
        </button>
      </header>
      <div className="mt-3">
        <h1 className="text-[26px] font-semibold leading-8 tracking-tight [text-wrap:balance]">{slide.title}</h1>
        <p className="mt-2.5 text-[15px] leading-[22px] text-fg-muted">{slide.body}</p>
      </div>
      <div className="mt-2 min-h-0 flex-1">
        <Illustration />
      </div>
      <Dots step={step} total={slides.length} className="my-6" />
      <button type="button" onClick={onNext} className="h-[52px] w-full rounded-lg bg-primary text-[15px] font-semibold text-primary-fg hover:bg-primary-hover">
        {step === slides.length - 1 ? "Get started" : "Next"}
      </button>
    </main>
  );
}

The code uses the Taste Tailwind preset. Make yours on the Taste page with Export my system.