All Taste components
Onboarding
Learning onboarding
The last intro screen of a learning app, with an open book and lightbulb inside a soft dotted card.
card panel illustration · dotted grid · last step · skip · get started
Learn a little, every day
Five-minute lessons that remember exactly where you left off.
// 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: "Learn what you love", body: "Hundreds of short courses, from coding to cooking." },
{ title: "Practice beats cramming", body: "Quick quizzes bring back what you learned, just before you forget." },
{ title: "Learn a little, every day", body: "Five-minute lessons that remember exactly where you left off." },
];
function Illustration({ className }: { className?: string }) {
return (
<svg viewBox="0 0 240 190" aria-hidden="true" className={cn("h-full w-full", className)}>
<path d="M120 12v10M86 24l7 7M154 24l-7 7M76 54h10M154 54h10" strokeWidth="4" strokeLinecap="round" strokeLinejoin="round" fill="none" className="stroke-[var(--tst-warning-500)]" />
<circle cx="120" cy="54" r="21" className="fill-[var(--tst-warning-300)]" />
<rect x="111" y="71" width="18" height="13" rx="4" className="fill-neutral-300" />
<path d="M113 52l7 7 7-7" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round" fill="none" className="stroke-[var(--tst-warning-500)]" />
<path d="M26 112c30-8 64-6 94 8 30-14 64-16 94-8v64c-30-8-64-6-94 8-30-14-64-16-94-8z" className="fill-brand-600" />
<path d="M36 106c28-6 58-4 84 10v60c-26-14-56-16-84-10z" strokeWidth="2.5" strokeLinejoin="round" className="fill-neutral-50 stroke-brand-800" />
<path d="M204 106c-28-6-58-4-84 10v60c26-14 56-16 84-10z" strokeWidth="2.5" strokeLinejoin="round" className="fill-neutral-50 stroke-brand-800" />
<path d="M52 126c16-3 34-2 52 4M52 140c16-3 34-2 52 4M52 154c12-2 22-2 32 1M188 140c-16-3-34-2-52 4M188 154c-16-3-34-2-52 4" strokeWidth="4" strokeLinecap="round" strokeLinejoin="round" fill="none" className="stroke-brand-200" />
<path d="M170 104v28l7-6 7 6v-30z" className="fill-brand-400" />
</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 LearningOnboarding({ step = 2, 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-end">
<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="mb-6 grid min-h-0 flex-1 place-items-center rounded-xl border border-border bg-surface bg-[radial-gradient(var(--tst-border-strong)_1px,transparent_1.5px)] bg-[length:16px_16px] p-5">
<Illustration />
</div>
<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>
<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.