Attract Phase

The opening screen of an onboarding flow. Plays a staged reveal storyboard on mount — atmospheric glow blobs, two waves of hero orbs, a layer of dim backdrop orbs, a periodic firefly pulse, then the headline / subtitle / CTA — so the user has something to watch before being asked to do anything.

Content slots are fully external. Pass title, subtitle, and cta as ReactNode. The phase owns no progression state; wire the CTA's onClick to your onboarding flow's "advance" handler (or use onAdvance as the canonical hook).

Patterns hide in every problem.

Can you see them?

Customize
Slots

Installation

npx shadcn@latest add https://craftbits.dev/r/attract-phase.json

Usage

import { AttractPhase } from "@craft-bits/core";
 
function FirstScreen({ onStart }: { onStart: () => void }) {
  return (
    <AttractPhase
      title="Patterns hide in every problem."
      subtitle="Can you see them?"
      cta={
        <button
          onClick={onStart}
          className="rounded-cb-md bg-cb-accent px-10 py-3 text-cb-accent-fg"
        >
          Show me
        </button>
      }
      onAdvance={onStart}
    />
  );
}

Compose with the top-level Onboarding shell so the phase swap is animated for free:

import { Onboarding, AttractPhase, type OnboardingPhase } from "@craft-bits/core";
 
const PHASES: OnboardingPhase[] = [
  {
    id: "attract",
    label: "Welcome",
    content: (ctx) => (
      <AttractPhase
        title="Patterns hide in every problem."
        subtitle="Can you see them?"
        onAdvance={ctx.advance}
        cta={<button onClick={ctx.advance}>Show me</button>}
      />
    ),
  },
];

Understanding the component

  1. Storyboard timing. The reveal runs through six stages over 1.6s — glow blobs fade in immediately, hero orbs arrive in two staggered waves (300ms / 500ms), dim backdrop orbs at 700ms, the firefly pulse starts at 900ms, the title at 1100ms, the subtitle at 1300ms, and the CTA at 1600ms. Each stage uses SPRINGS.* from the motion tokens — never an inline transition.
  2. Hero vs dim orbs. The first six positions render as larger cb-accent-tinted orbs with a soft glow; the remaining six render as smaller, lower-opacity placeholders. The contrast is intentional — the heroes pull the eye, the dims fill the canvas.
  3. Firefly cycling. Once the dim orbs land, one hero pulses to ~1.18x scale on a fixed sequence ([0, 3, 1, 5, 2, 4]) at 2.2s intervals. It's a signal — patterns hide everywhere; one lights up now and then to remind you.
  4. External content. Every text slot is a ReactNode so the phase makes no assumptions about copy length, weight, or wrapping. Wrap the CTA in a fragment if you need both a primary button and a secondary skip link (see the demo).
  5. Reduced motion. Under prefers-reduced-motion: reduce, the phase short-circuits to the final stage on mount — no scale-in, no firefly pulse, no staggered reveal — and renders the same static composition.

Props

PropTypeDefaultDescription
titleReactNodeHeadline shown after the orb field. Rendered inside <h2>.
subtitleReactNodeSupporting copy. Rendered inside <p>.
ctaReactNodeCall-to-action node anchored at the bottom (typically a <button>).
onAdvance() => voidFires when the user wants to advance. Wire to your CTA's onClick.
regionAriaLabelstring"Attract phase"Accessible name for the outer role="region" landmark.
classNamestringMerged onto the outer <div> via cn().

Accessibility

  • The root renders as a role="region" landmark with a configurable aria-label so assistive tech can jump straight to the screen.
  • The orb field is aria-hidden="true" — it's atmosphere, not content. Screen-reader users hear the title / subtitle / cta only.
  • The staged reveal respects prefers-reduced-motion: reduce: the storyboard collapses to a static final-state render with no entry animation, scale-in, or firefly pulse.
  • The phase does NOT auto-advance. Progression is the consumer's job — wire cta's onClick to onAdvance (or to whatever your flow needs) so users can take the action with the keyboard alone.

Credits

  • Extracted from: AlgoFlashcards (src/platform/ui/onboarding/AttractPhase.tsx). The source baked in track-color theming via TRACKS.*, a GlowNode SVG primitive, an internal stage reducer keyed on lessonId, hardcoded headline copy, and an onShowMe / onSkip dual-button contract. craft-bits' version strips all of that: orbs are token-driven (cb-accent + cb-border), every text slot becomes a ReactNode, and the CTA composition is the consumer's call.