Glassmorphism Onboarding Steps: Frosted Progress and Feature Slides
Build frosted-glass onboarding flows in React with backdrop-filter steps, animated progress bars, and feature slides that don't feel generic.
Why Onboarding Is Where Glassmorphism Actually Earns Its Keep
Onboarding flows are the one place in your app where you control everything — the background, the content, the pacing. That makes them perfect for glassmorphism. You're not fighting an existing dark mode or a cluttered dashboard layout. You get a clean slate with a vivid background you designed, and frosted panels that sit on top of it like they belong there.
In practice, most SaaS onboarding flows look the same: a white card, some dots at the bottom, a Next button in brand blue. Users have seen it a thousand times and they skip it as fast as possible. A frosted glass step that blurs a colorful gradient behind it actually slows people down — not because it's distracting, but because it feels intentional. Like someone cared.
Worth noting: glassmorphism without a meaningful background is just a slightly transparent white box. The blur only works when there's something interesting behind it. This is why the onboarding context is so valuable — you can engineer that background yourself. A mesh gradient at 800×600, a subtle animated aurora, a product screenshot — all of it becomes the canvas your glass panels live on.
If you want to see what this looks like done right, check out the glassmorphism components in Empire UI. The patterns translate directly into step-by-step onboarding shells.
The CSS Foundation: backdrop-filter and the Glass Panel
Everything starts with two CSS properties: backdrop-filter: blur() and a semi-transparent background. The blur value matters more than people think. blur(4px) is subtle — almost invisible on a slow gradient. blur(20px) starts looking like frosted glass on a shower door. For onboarding panels, you want something in the blur(10px) to blur(16px) range. That's enough to communicate depth without hiding the background entirely.
Here's the base panel style that works across most onboarding layouts:
``css
.glass-step {
background: rgba(255, 255, 255, 0.08);
backdrop-filter: blur(12px);
-webkit-backdrop-filter: blur(12px);
border: 1px solid rgba(255, 255, 255, 0.15);
border-radius: 20px;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.25);
}
`
The box-shadow` at 32px spread adds the depth that makes the panel feel like it's floating. Drop it and the card looks flat even with the blur. Keep both and you get something that reads as physically layered.
One more thing — Safari has required -webkit-backdrop-filter since around 2017 and still does in 2026. Don't skip that vendor prefix. You'll ship a broken experience to every iOS user and not notice it until someone complains.
You can tune the border color to match your brand hue. A white border at 15% opacity works universally. But if your background is purple-heavy, try rgba(200, 180, 255, 0.2) instead — it picks up the ambient color and the panel looks like it belongs rather than hovering alien-like above the scene. The glassmorphism generator lets you dial these values in real time, which is faster than guessing.
Building the Step Component in React
The cleanest way to handle multi-step onboarding in React is a single stateful parent with an array of step configs and a currentStep index. Each step is just data — no logic baked into the UI component itself. This keeps the glass panel reusable and the content swappable.
Here's a minimal but complete version:
``tsx
type OnboardingStep = {
title: string;
description: string;
icon: React.ReactNode;
};
const steps: OnboardingStep[] = [
{
title: "Connect your workspace",
description: "Link your tools in under 60 seconds.",
icon: <WorkspaceIcon />,
},
{
title: "Invite your team",
description: "Add members — they'll get an email automatically.",
icon: <TeamIcon />,
},
{
title: "Set your first goal",
description: "Pick a metric. We'll track it from day one.",
icon: <GoalIcon />,
},
];
export function OnboardingFlow() {
const [current, setCurrent] = React.useState(0);
const step = steps[current];
return (
<div className="onboarding-bg">
<div className="glass-step">
<div className="step-icon">{step.icon}</div>
<h2>{step.title}</h2>
<p>{step.description}</p>
<ProgressBar total={steps.length} current={current} />
<div className="step-actions">
{current > 0 && (
<button onClick={() => setCurrent(c => c - 1)}>Back</button>
)}
<button onClick={() => setCurrent(c => Math.min(c + 1, steps.length - 1))}>
{current === steps.length - 1 ? "Get started" : "Next"}
</button>
</div>
</div>
</div>
);
}
``
Keep it this flat. You don't need a state machine library for three to five steps.
Honestly, the biggest mistake I see in onboarding code is putting transition animations directly on the step content instead of on a wrapper. When you animate the container opacity from 0 → 1 on every step change, you get a clean fade that doesn't require you to think about direction. If you want slide transitions (left-to-right on Next, right-to-left on Back), wrap content in a <motion.div> from Framer Motion and flip the x offset based on direction. But for most B2B SaaS flows, a 200ms opacity fade is all you need.
Quick aside: add aria-live="polite" to the step content div so screen readers announce the new step text. Glassmorphism doesn't have to mean inaccessible.
Frosted Progress Indicators That Actually Communicate Progress
The dots-at-the-bottom pattern is fine but it doesn't tell users where they are relative to the end. A progress bar does. For a glass onboarding flow, you want the bar to feel like it's part of the same material — slightly translucent track, frosted fill, maybe a subtle glow on the active segment.
``tsx
function ProgressBar({ total, current }: { total: number; current: number }) {
const pct = Math.round(((current + 1) / total) * 100);
return (
<div
role="progressbar"
aria-valuenow={pct}
aria-valuemin={0}
aria-valuemax={100}
style={{
width: "100%",
height: "4px",
borderRadius: "9999px",
background: "rgba(255,255,255,0.12)",
overflow: "hidden",
marginTop: "24px",
}}
>
<div
style={{
height: "100%",
width: ${pct}%,
borderRadius: "9999px",
background: "linear-gradient(90deg, rgba(139,92,246,0.9), rgba(59,130,246,0.9))",
boxShadow: "0 0 8px rgba(139,92,246,0.6)",
transition: "width 400ms cubic-bezier(0.4, 0, 0.2, 1)",
}}
/>
</div>
);
}
`
The 8px glow on the fill is subtle at 0.6 opacity but it reads as active and lit without looking like a neon sign. The cubic-bezier` easing makes the bar feel springy rather than mechanical.
That said, dots still have a place. If you want both — a step count label ("Step 2 of 4") plus the progress bar — put the label above in 12px text at 60% opacity white. Don't compete with the heading. The label is context, not content.
For feature slides specifically (where each step showcases a different product capability), consider animating the icon or illustration rather than the full panel. A 300ms scale from 0.85 to 1.0 with an opacity fade on the icon alone creates a sense of reveal without making users feel like the page is rebuilding itself every click.
Feature Slides: Showing Off Product Capabilities Without Looking Like a Sales Deck
Feature slides in onboarding are tricky. Done wrong, they feel like a product tour someone forced you to sit through before you can use the thing you signed up for. Done right, they give users a mental model of the app so they don't land on the dashboard feeling lost. The glass aesthetic helps here because it visually separates these slides from the rest of your app — they feel like a moment apart, not a blocker.
Each feature slide should follow the same pattern: a bold icon or micro-illustration at the top (48px to 64px), a one-line title, and two sentences of description max. If you can't explain the feature in two sentences, the feature probably needs a docs page, not an onboarding slide. Resist the urge to add bullet lists. They break the visual rhythm of the glass panel.
``tsx
const featureSlides = [
{
icon: "⚡",
title: "Real-time sync across all devices",
description:
"Every change you make appears instantly everywhere. No manual saves, no version conflicts.",
},
{
icon: "🔒",
title: "End-to-end encryption by default",
description:
"Your data is encrypted before it leaves your device. We can't read it even if we wanted to.",
},
{
icon: "📊",
title: "Analytics that don't need a manual",
description:
"Automated reports hit your inbox every Monday. Drill down when you need to, ignore them when you don't.",
},
];
`
If you're using emoji icons, set them at font-size: 40px with line-height: 1`. Don't wrap them in a colored circle background — that clashes with the glass aesthetic. Let the emoji sit directly on the frosted surface.
Look, you also need to decide whether these slides are skippable. For most SaaS products in 2026, users expect a Skip button. Put it in the top-right corner at 80% opacity — present but not prominent. Don't make users hunt for it. The moment onboarding feels like a hostage situation, you've lost trust before they've even seen your product.
Background Design: Making the Glass Have Something to Blur
The background behind your onboarding glass panels is half the design. You've got options: a static mesh gradient, a CSS animated gradient, a looping video, or a blurred screenshot of your own dashboard. Each has trade-offs.
Static mesh gradient is the safest choice. Zero performance cost, looks great at all viewport sizes, and you can generate one in under a minute with the gradient generator. For a purple-to-blue SaaS vibe, try something like:
``css
.onboarding-bg {
min-height: 100vh;
background:
radial-gradient(ellipse at 20% 50%, rgba(120, 40, 200, 0.4) 0%, transparent 60%),
radial-gradient(ellipse at 80% 20%, rgba(59, 130, 246, 0.4) 0%, transparent 60%),
radial-gradient(ellipse at 60% 80%, rgba(16, 185, 129, 0.3) 0%, transparent 50%),
#0a0a0f;
}
``
That gives you a dark base with three soft color blobs. The glass panels will blur them into a hazy, layered backdrop that looks expensive.
Animated gradients are appealing but watch your paint cost. A slow CSS keyframe animation (animation-duration: 8s) on a gradient is fine. Anything that triggers layout or involves JavaScript RAF on every frame is going to hurt on low-end Android devices. Test on a mid-range device, not your M3 MacBook Pro.
One more thing — if you use a blurred screenshot of your actual app as the background, make sure it's blurred enough that it doesn't read as a functional UI. filter: blur(40px) at the background level usually handles this. You want suggestion of depth, not a confusing ghost of the real interface behind the onboarding panel.
Worth noting: the glassmorphism components include pre-built background patterns paired with glass panels. If you're starting from scratch, those are worth borrowing from rather than inventing your own from first principles.
Animations, Transitions, and Knowing When to Stop
Animation in onboarding glass flows has a ceiling. Cross it and the whole experience starts feeling like a Dribbble shot — beautiful to look at, painful to actually use. The sweet spot is two to three animated properties max: panel entrance, progress bar fill, and icon/illustration swap.
For panel entrance, a 250ms ease-out from translateY(16px) opacity(0) to translateY(0) opacity(1) works universally. You don't need spring physics here. That said, if your brand has a bouncier personality, Framer Motion's spring preset with stiffness: 300, damping: 25 gives a satisfying pop without overshooting.
``tsx
import { motion, AnimatePresence } from "framer-motion";
function AnimatedStep({ step, direction }: { step: OnboardingStep; direction: 1 | -1 }) {
return (
<AnimatePresence mode="wait" custom={direction}>
<motion.div
key={step.title}
custom={direction}
initial={{ opacity: 0, y: direction > 0 ? 12 : -12 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: direction > 0 ? -12 : 12 }}
transition={{ duration: 0.2, ease: "easeOut" }}
>
<h2>{step.title}</h2>
<p>{step.description}</p>
</motion.div>
</AnimatePresence>
);
}
`
The custom={direction}` prop lets you flip the slide direction based on whether the user went forward or back. Small touch but it makes the flow feel spatially coherent.
Respect prefers-reduced-motion. Wrap any animation in a check or use Framer Motion's built-in useReducedMotion hook and fall back to instant transitions. It's a one-liner and it's the right thing to do.
Browse Empire UI when you're ready to pull in production-ready component patterns rather than building from scratch. The amount of time you save not debugging Safari backdrop-filter edge cases is real.
FAQ
Yes, with the caveat that Safari still requires -webkit-backdrop-filter as a prefix. Chromium-based browsers and Firefox have supported the unprefixed version since 2022. Always include both declarations or you'll break iOS Safari silently.
Five is the practical ceiling. Beyond that, completion rates drop sharply and users start looking for the skip button immediately. If you have more than five things to communicate, cut ruthlessly — most of it belongs in tooltips inside the product itself, not in an onboarding gate.
Technically yes, but it's much harder to make it look good. The blur effect needs contrast behind the panel to read as frosted glass. On a white or very light background, you end up with a barely-visible panel that just looks like a shadow. Dark or richly-colored backgrounds are where glass components actually shine.
Full page wins for first-run onboarding. Modals feel like an interruption even when they're not. A full-page glass flow where the URL changes per step also lets users refresh without losing their place and makes analytics tracking (step dropoff rates) much simpler to implement.