Skip to content

Components / Sections

Thank You

A thanks page for after a signup: a headline, the reader's place in line rolling up, a referral link with a copy button, and one burst of confetti. The values come from the server or from the record the form lib leaves for this page, never from the URL. Without any values, the page still reads as complete.

Live demo

You're in

Thanks, you're on the list.

We'll write once, the day it opens. Until then, every friend who joins through your link moves you up.

Your place in line
1,284
Your referral link

Back to Pro

Live, from the library itself, at full size: scroll, hover, navigate away and back. The demo is never gated.

Variants · each row writes the attribute the prop writes; the section's own CSS answers

tone
density
align

Measured

JavaScript of its own

981 B raw

608 B gzip.

Uses CopyButton and the shared runtime (1.9 kB raw, once per site) and Confetti and the shared canvas helper (2.1 kB raw, once per site). With those included: 7.6 kB raw.

With none: the same script as the default, 981 B raw. The none path is part of it and adds nothing; measured on its own fixture.

CSS 16.7 kB raw including the base tokens. Measured from a production build, and again in CI for every change that can move it.

Browser support

Baseline · newly available

Chrome 125 · Firefox 121 · Safari 15.4; needs css-math, clipboard, has. Elsewhere: the place in line as plain text; the copy button says it failed where the clipboard is refused; without :has() a page with neither block keeps one empty gap where the list was.

Install Pro

The demo above is never gated; the source is. With a key in components.json (see account), the registry serves it like any other item:

terminal
npx shadcn@latest add @moonarc-pro/thank-you
src/pages/index.astro
---
import ThankYou from '@/components/moonarc/pro/ThankYou.astro';
---
<!-- after a signup: the page the form's data-success points at; the record the form lib wrote fills it in -->
<ThankYou
  eyebrow="You're in"
  headline="Thanks, you're on the list."
  copy="We'll write once, the day it opens."
  referralUrl="https://example.com/?ref={ref}"
  back={{ href: '/', label: 'Back home' }}
/>

<!-- or from the server: values you already know, both blocks rendered, no burst -->
<ThankYou
  headline="Thanks, you're on the list."
  position={1284}
  referral="ada-2418"
  referralUrl="https://example.com/?ref={ref}"
  celebrate="none"
>
  <a slot="actions" href="/changelog/">Read the changelog</a>
</ThankYou>

Without a key the registry answers 401 with an upgrade message. Everything this section composes is free: number-flow, copy-button, confetti.

Props

PropTypeDefaultDescription
headlinestringnoneHeadline; an h1 unless `level` says 2. Required unless the headline slot is filled (the slot wins). The build fails without either.
eyebrowstringnoneShort line above the headline. Slot "eyebrow" wins.
copystringnoneSupporting copy. Slot "copy" wins.
level1 | 21Heading level: 1 on the thanks page itself, 2 under another h1 (a demo).
positionnumbernonePlace in line from the server. A fresh record for this page overrides it (1–15 digits, as a number or as a string, because the form lib carries a service's "12" as it came); without either the block stays hidden.
referralstringnoneReferral code from the server. A fresh record for this page overrides it; without either the block stays hidden.
referralUrlstringnoneThe link to share, absolute (http or https, with its origin), with {ref} where the code goes: 'https://example.com/?ref={ref}', or in Astro new URL('/?ref=', Astro.site) + '{ref}'. The build fails on a relative URL or one without {ref}: the link is copied and sent to people who are not on this page. Its text is the URL and CopyButton copies it. Without it there is no referral block.
positionLabelstring'Your place in line'Label of the position block.
referralLabelstring'Your referral link'Label of the referral block.
copyLabels{ label?, doneLabel?, errorLabel? }'Copy link' · 'Copied' · 'Copy failed'The copy button's three faces, passed to CopyButton.
digitsnumber5Columns NumberFlow reserves for the position, so it rolls without the row changing width; a longer number from the record is written as plain text.
localestring'en'Locale of the position's thousands separator.
celebrate'submit' | 'always' | 'none''submit'submit: one burst when a fresh record arrives, none on reload · always: every page view · none: no Confetti rendered, no confetti JavaScript shipped.
back{ href, label } | false{ href: '/', label: 'Back home' }The link back, used when the "actions" slot is empty; false for none.
tone'surface' | 'muted' | 'ink' | 'brand'unset (paints nothing)The ground: the page surface, a muted band, an always-dark ink panel, or a deep brand panel. Each paints --background, --ma-tone-muted (over --muted, with a --border hairline), --ma-tone-ink or --ma-tone-brand (over --primary) with the matching text. With theme.css, a painted tone also re-declares --card, --border, --input and --ring inside the section, so cards and fields follow the ground. The two blocks take the same ground with a hairline of the text colour.
density'compact' | 'cozy' | 'roomy''cozy'Block padding and gaps ×0.8 / ×1 / ×1.25 through --ma-density.
align'start' | 'center''center'Text alignment.

Theming and overrides

The section reads the theme contract (shadcn's variable names with the base tokens as fallback), so a pasted theme or one --ma-brand recolours it. Its class names are the override surface and stay stable; a named slot replaces the matching prop with your own markup.

Classes

.ma-nf.ma-nf__static.ma-thanks.ma-thanks__actions.ma-thanks__aside.ma-thanks__back.ma-thanks__card.ma-thanks__copy.ma-thanks__eyebrow.ma-thanks__facts.ma-thanks__fire.ma-thanks__head.ma-thanks__headline.ma-thanks__inner.ma-thanks__label.ma-thanks__link.ma-thanks__pos.ma-thanks__ref.ma-thanks__share.ma-thanks__value

Slots

slot="eyebrow"slot="headline"slot="copy"slot="media"slot="actions"slot="aside"

Reduced motion

No confetti: a fresh record asks for none (no attribute, no event) and is still marked celebrated, and celebrate="always" has its request dropped by Confetti, not kept; the place in line is shown without rolling; the back link does not fade.

With ClientRouter

Bound through the shared runtime: the record is read again on every page view, and a burst asked for before Confetti binds waits in its attribute. The form lib leaves with a full navigation, so the thanks page always starts clean.

Why it is built this way

Replaces: Hosted waitlist thank-you pages · Hand-rolled success screens (confetti + copy link). See the migration table.

one canvas · gone when the last piece lands

Confetti

Pointer

Fires a burst of paper confetti on load, on a click or on an ma:confetti event.

every browserloadclick
npx astro add moonarc
click · check · back

A button that copies text to the clipboard: the icon turns into a check, the label says "Copied", and both come back after the ambient duration.

every browserclick

1,284

A number whose digits roll to the new value when it changes.

newly · Chrome 125 · Firefox 118 · Safari 15.4click

Also: view transitions · how costs are measured · browser support · accessibility policy · five-minute setup · MCP for agents · this page as Markdown