# iOS Onboarding

> Three-step intro, page dots and the system notification alert.

- Author: Atelier UI
- License: MIT
- Page: https://chromotion.dev/en/templates/ios-onboarding
- Site: Atelier UI (https://chromotion.dev)

## Install

```bash
npx shadcn@latest add https://chromotion.dev/r/ios-onboarding.json
```

Adds the page at `app/ios-onboarding/page.tsx` together with the components below. Stack: React + TypeScript + Tailwind CSS v4, shadcn/ui conventions. `cn` comes from `@/lib/utils`.

## Components

- [iOS Kit](https://chromotion.dev/r/ios-kit.md): `@/components/ios-kit`

## Dependencies

```bash
npm install lucide-react
```

## Source

```tsx title="app/ios-onboarding/page.tsx"
"use client"

import * as React from "react"
import { Bell, MapPin, Sparkles, Heart, Layers } from "lucide-react"

import { cn } from "@/lib/utils"
import { IOSDevice } from "@/components/ios-kit"

const steps = [
  { icon: Sparkles, color: "from-indigo-500 to-violet-600", title: "Welcome to Atelier", body: "A calmer way to design, collect and ship interface ideas — all in one place." },
  { icon: Layers, color: "from-sky-400 to-blue-600", title: "Everything in sync", body: "Your boards, components and themes follow you from iPhone to iPad to Mac." },
  { icon: Heart, color: "from-rose-400 to-pink-600", title: "Made for how you work", body: "Quick capture, gentle reminders and a Home Screen widget that stays out of the way." },
]

export default function IOSOnboarding() {
  const [step, setStep] = React.useState(0)
  const [alert, setAlert] = React.useState(false)
  const [done, setDone] = React.useState<null | "allowed" | "later">(null)
  const current = steps[step]
  const last = step === steps.length - 1
  const Icon = current.icon

  return (
    <IOSDevice>
      {done ? (
        <div className="flex flex-1 flex-col items-center justify-center gap-4 px-10 text-center">
          <span className="flex size-20 items-center justify-center rounded-full bg-(color:--ios-green) text-white">
            <Bell className="size-9" />
          </span>
          <h1 className="text-[28px] font-bold">You&apos;re all set</h1>
          <p className="text-[17px] text-(color:--ios-label2)">
            {done === "allowed" ? "Notifications are on. We'll only nudge you when it matters." : "You can turn on notifications any time in Settings."}
          </p>
          <button type="button" onClick={() => { setDone(null); setStep(0) }} className="mt-4 text-[17px] text-(color:--ios-blue) active:opacity-50">
            Replay onboarding
          </button>
        </div>
      ) : (
        <div className="flex min-h-0 flex-1 flex-col px-6 pt-20 pb-10">
          <div className="flex flex-1 flex-col items-center justify-center text-center">
            <span key={step} className={cn("flex size-[132px] animate-in items-center justify-center rounded-[32px] bg-gradient-to-br text-white shadow-xl duration-500 zoom-in-90 fade-in", current.color)}>
              <Icon className="size-16" strokeWidth={1.5} />
            </span>
            <h1 key={`t${step}`} className="mt-10 animate-in text-[32px] leading-[38px] font-bold duration-500 fade-in slide-in-from-bottom-2">{current.title}</h1>
            <p key={`b${step}`} className="mt-3 max-w-[300px] animate-in text-[17px] leading-[24px] text-(color:--ios-label2) delay-75 duration-500 fade-in slide-in-from-bottom-2">{current.body}</p>
          </div>

          <div className="flex justify-center gap-2 py-6" role="tablist" aria-label="Steps">
            {steps.map((s, i) => (
              <button
                key={s.title}
                type="button"
                role="tab"
                aria-selected={i === step}
                aria-label={`Step ${i + 1}`}
                onClick={() => setStep(i)}
                className={cn("h-2 rounded-full transition-all duration-300", i === step ? "w-6 bg-(color:--ios-label)" : "w-2 bg-(color:--ios-label3)")}
              />
            ))}
          </div>

          <button
            type="button"
            onClick={() => (last ? setAlert(true) : setStep(step + 1))}
            className="h-[50px] w-full rounded-[14px] bg-[#0064d2] text-[17px] font-semibold text-white transition-opacity active:opacity-70"
          >
            {last ? "Get Started" : "Continue"}
          </button>
          <button type="button" onClick={() => setAlert(true)} className="mt-3 h-8 text-[15px] text-(color:--ios-blue) active:opacity-50">
            Skip
          </button>
        </div>
      )}

      {alert && (
        <div className="absolute inset-0 z-50 flex animate-in items-center justify-center bg-black/40 px-10 duration-200 fade-in">
          <div role="alertdialog" aria-modal="true" aria-labelledby="ios-alert-title" className="w-[270px] animate-in overflow-hidden rounded-[14px] bg-(color:--ios-surface)/90 text-center backdrop-blur-2xl duration-200 zoom-in-95">
            <div className="px-4 pt-5 pb-4">
              <MapPin className="mx-auto mb-2 size-6 text-(color:--ios-blue)" />
              <h2 id="ios-alert-title" className="text-[17px] leading-[22px] font-semibold">&ldquo;Atelier&rdquo; Would Like to Send You Notifications</h2>
              <p className="mt-1 text-[13px] leading-[18px]">Notifications may include alerts, sounds and icon badges. You can configure them in Settings.</p>
            </div>
            <div className="grid grid-cols-2 border-t border-(color:--ios-sep)">
              <button type="button" onClick={() => { setAlert(false); setDone("later") }} className="border-r border-(color:--ios-sep) py-[11px] text-[17px] text-(color:--ios-blue) active:bg-(color:--ios-fill)">
                Don&apos;t Allow
              </button>
              <button type="button" onClick={() => { setAlert(false); setDone("allowed") }} className="py-[11px] text-[17px] font-semibold text-(color:--ios-blue) active:bg-(color:--ios-fill)">
                Allow
              </button>
            </div>
          </div>
        </div>
      )}
    </IOSDevice>
  )
}
```
