Once UI Handbook
Beginner Guides
What is Once UI?
Install & config
Page skeleton
Your first page
Build And Launch
How to launch a portfolio site that actually gets you hired
How to build a documentation site with MDX and Next.js
How to ship a landing page and dashboard with authentication in Next.js
How to launch a social app with Next.js and Supabase
Vibe Coding
Introduction to vibe coding
Set up your local dev environment
Essential tools for vibe coding
Build your first Once UI app
Common Patterns
Hero section
Static panel
Form layout
Highlighted card
Responsive stacking
Decoration layers
Dialog & modal
Toast & feedback
Tables & lists
Loading states
App shell navigation
Dashboard & charts
Chat & messaging
Auth & verification
Settings & split panels
Data filters & toolbar
Command palette
Pricing & plans
Accordion & FAQ
Media & uploads
Empty & error states
SEO, Open Graph, and structured data
Documentation code blocks with live preview
Carousels, galleries, and before/after comparisons
Date pickers and scheduling
Context menus and dropdown actions
Tags, chips, and multi-value inputs
Roadmap & kanban
Form inputs & controls
Progress, status & badges
Scrolling & feeds
Profile, avatars & identity
Social proof & logo clouds
Masonry & media grids
Table pagination, search & bulk selection
Banners & announcements
Footer layouts
Onboarding & first run
Waitlist & coming soon
Design Tips
Color & surfaces
Harness overview
Row, Column & Grid
Spacing & rhythm
Icons & buttons
Reveal & motion
Theme tokens, FOUC-free init, and runtime style controls
Tooltips and hover cards
Block updates
Block updates changelog
Agent Resources
Column, not Flex
Semantics over CSS
Typography scale
Common gotchas
Block anchors
Validate generated code
Pro block registry
Match task bundles
TrademarkTrademark
Ctrl k
Search...
Sign up
Once UI Handbook
Beginner Guides
What is Once UI?
Install & config
Page skeleton
Your first page
Build And Launch
How to launch a portfolio site that actually gets you hired
How to build a documentation site with MDX and Next.js
How to ship a landing page and dashboard with authentication in Next.js
How to launch a social app with Next.js and Supabase
Vibe Coding
Introduction to vibe coding
Set up your local dev environment
Essential tools for vibe coding
Build your first Once UI app
Common Patterns
Hero section
Static panel
Form layout
Highlighted card
Responsive stacking
Decoration layers
Dialog & modal
Toast & feedback
Tables & lists
Loading states
App shell navigation
Dashboard & charts
Chat & messaging
Auth & verification
Settings & split panels
Data filters & toolbar
Command palette
Pricing & plans
Accordion & FAQ
Media & uploads
Empty & error states
SEO, Open Graph, and structured data
Documentation code blocks with live preview
Carousels, galleries, and before/after comparisons
Date pickers and scheduling
Context menus and dropdown actions
Tags, chips, and multi-value inputs
Roadmap & kanban
Form inputs & controls
Progress, status & badges
Scrolling & feeds
Profile, avatars & identity
Social proof & logo clouds
Masonry & media grids
Table pagination, search & bulk selection
Banners & announcements
Footer layouts
Onboarding & first run
Waitlist & coming soon
Design Tips
Color & surfaces
Harness overview
Row, Column & Grid
Spacing & rhythm
Icons & buttons
Reveal & motion
Theme tokens, FOUC-free init, and runtime style controls
Tooltips and hover cards
Block updates
Block updates changelog
Agent Resources
Column, not Flex
Semantics over CSS
Typography scale
Common gotchas
Block anchors
Validate generated code
Pro block registry
Match task bundles
TrademarkTrademark
Once UIDocumentationBlog
© Once UI. All rights reserved.
Built with Aveiro
Common patterns

Waitlist & coming soon

Centered hero, email capture, RevealFx entry, and subscriber trust rows from Waitlist1.
Updated 19d ago
Onboarding & first run
7 min · Pattern
Waitlist pages combine a centered hero, email capture, and social proof counters. Follow Waitlist1 for structure — RevealFx entry, Input validation, and AvatarGroup trust rows.

What this pattern covers

Coming-soon and waitlist flows need:
  • Centered hero with eyebrow, headline, and subcopy
  • Email Input with inline validation
  • Optional subscriber count animation (describe CountFx in prose)
  • Ambient Background decoration behind the form
Gold reference: packages/core/ai/examples/blocks/Waitlist1.tsx, task bundle packages/core/ai/tasks/marketing-hero.json.

Page skeleton

Waitlist pages are single-column, vertically centered — no sidebar:
<Column as="main" fill minHeight="100vh" center padding="l">
  <Column gap="40" maxWidth="xs" horizontal="center">
    {/* eyebrow */}
    {/* headline + subcopy */}
    {/* email form */}
    {/* trust row */}
  </Column>
</Column>
Use maxWidth="xs" so the headline and form stay narrow. Add an absolute Background layer on the outer Column for ambient glow — see Decoration layers.

Eyebrow badge

Waitlist1 wraps Logo + status text in a Badge-like Row. Describe in prose: use Badge with background="brand-alpha-weak" and onBackground="brand-medium" for the “Arriving soon” chip. Include dark and light Logo variants for theme switching.
<Column horizontal="center" gap="8">
  <Text variant="label-default-s" onBackground="brand-medium">
    Arriving soon
  </Text>
</Column>
If you need the full Badge + Logo composition, read Waitlist1.tsx — the gold block shows dark/light Logo pairs separated by a vertical Line.

Headline stack

Animate the hero in two beats with RevealFx — headline first, subcopy second:
<Column gap="24" horizontal="center">
  <Heading variant="display-strong-xl" align="center">
    The all-in-one course platform
  </Heading>
  <Text
    variant="heading-default-xl"
    onBackground="neutral-medium"
    align="center"
  >
    Launch and sell your courses with ease. We handle enrollment and analytics.
  </Text>
</Column>
Describe in prose: wrap each block in RevealFx with delay={0.4} and delay={0.6} (seconds, not milliseconds). See Reveal & motion.

Email capture row

Pair Input with an IconButton or Button submit. Validate before calling your API:
On mobile, stack vertically with s={{ direction: "column" }} on the Row. Show validation errors via Input error prop — not a separate toast for empty submit.
Describe in prose: after successful subscribe, swap the form for a short confirmation Text and optionally increment a CountFx subscriber total.

Subscriber count

Waitlist1 animates a subscriber count when the block enters the viewport. Describe in prose:
  • Drive CountFx value from state, not a trigger prop (see gotchas.json)
  • Latch the target number with a viewport hook so the counter runs once
  • Pair with AvatarGroup showing recent signups for social proof
For overlapping avatars, use AvatarGroup from the catalog — describe props in prose rather than nesting raw Avatar stacks.

Post-submit state

Three branches after the user submits:
State
UI
IdleInput + submit visible
SubmittingButton `loading` or Input disabled
SuccessConfirmation Text, hide Input
Use Toast & feedback only for unexpected API failures — success is inline.

Agent checklist

When generating waitlist pages from marketing-hero.json:
  • Single centered Column — no dashboard chrome
  • RevealFx on headline blocks, not every Input label
  • Input validation before submit
  • CountFx driven by viewport latch, not trigger
  • Background decoration absolute with pointerEvents="none"

Write this / not this

✅ Once UI way
❌ Common mistake
Narrow `maxWidth="xs"` heroFull-width form lost in whitespace
Inline success confirmationToast-only feedback with no visible state change
RevealFx delay in seconds`delay={400}` treating seconds as ms
Input `error` for validationAlert dialog on empty email

Check yourself

  • Page centers vertically on large screens
  • Email validation runs before API call
  • Success state replaces the form, not stacked beneath it
  • Ambient Background is absolute, non-interactive

Related

→ Hero section → Reveal & motion → Social proof & logo clouds → Gold block: packages/core/ai/examples/blocks/Waitlist1.tsx
<Row fillWidth gap="8" vertical="center">
  <Input
    id="email"
    label="Email"
    placeholder="you@company.com"
    value={email}
    error={error}
    onChange={(e) => setEmail(e.target.value)}
  />
  <IconButton icon="arrowRight" variant="primary" size="m" />
</Row>
<Row gap="12" vertical="center">
  <Row>
    <Avatar src="/a1.jpg" size="s" />
    <Avatar src="/a2.jpg" size="s" />
    <Avatar src="/a3.jpg" size="s" />
  </Row>
  <Text variant="body-default-s" onBackground="neutral-weak">
    Join 1,300+ on the waitlist
  </Text>
</Row>
{subscribed ? (
  <Column gap="8" horizontal="center">
    <Icon name="check" size="m" onBackground="success-medium" />
    <Text variant="body-strong-s" align="center">
      You are on the list
    </Text>
  </Column>
) : (
  {/* email Row */}
)}