Dot Pattern
BackgroundsA field of dots behind content, faded toward the edges, with an optional slow pulse.
An email signup section built on a real form: native validation, an invalid state that only shows after the user has typed, a submit button whose label swaps for a spinner when the form carries data-pending, a status line for your handler, and a dot field behind. It takes a list service's form as it is (field name, hidden fields, a honeypot, form attributes) and only posts. Zero JavaScript of its own; wire the fetch yourself or let the form post.
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
JavaScript of its own
0 B
Uses Reveal and the shared runtime (1.9 kB raw, once per site). With those included: 3.0 kB raw.
CSS 12.5 kB raw including the base tokens. Measured from a production build, and again in CI for every change that can move it.
Browser support
every browser.
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:
npx shadcn@latest add @moonarc-pro/newsletter-section---
import NewsletterSection from '@/components/moonarc/pro/NewsletterSection.astro';
---
<NewsletterSection
headline="A monthly email with the changelog in prose"
copy="No launch announcements, discounts or drip campaigns."
action="/api/subscribe"
note="Unsubscribe in one click."
/>Without a key the registry answers 401 with an upgrade message. Everything this section composes is free: dot-pattern, reveal, spinner.
| Prop | Type | Default | Description |
|---|---|---|---|
headline | string | none | Headline. |
action | string | none | Where the form posts. Required unless demo; the build fails without it. |
copy | string | none | Supporting copy. |
method | 'post' | 'post' | Only post: a GET form writes the email into the URL, the history and server logs. Any other value fails the build with that rule in the message. |
name | string | 'email' | Name of the email field, as your list service expects it: Mailchimp's embed form posts EMAIL, Kit's email_address. |
placeholder | string | 'you@example.com' | Input placeholder. |
label | string | 'Email address' | The email field's accessible name, as its visually hidden <label>. |
submitLabel | string | 'Subscribe' | The submit button's label. |
honeypot | string | none | Name of a honeypot field (Mailchimp's b_<u>_<id>): rendered display:none, tabindex -1, autocomplete off. A filled one marks a bot for your service. |
formAttrs | Record<string, string | number | boolean> | none | Extra attributes on the <form>: target, name, id, class, a service's data-* flag, or data-ma-form and the data-msg-* texts for the core form lib. action and method fail the build here; use their props. |
demo | boolean | false | A form that sends nothing: method="dialog" (goes nowhere outside a <dialog>, even without JavaScript), no action, and demoLabel shown under the form. Native validation still runs. |
demoLabel | string | 'Demo: nothing is sent.' | The note a demo form shows. |
note | string | none | Small print under the form. |
tone | 'surface' | 'muted' | 'ink' | 'brand' | unset (the card colour) | 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. Decoration is strongest on brand. |
density | 'compact' | 'cozy' | 'roomy' | 'cozy' | Block padding ×0.8 / ×1 / ×1.25 through --ma-density. |
align | 'start' | 'center' | 'center' | Text alignment inside the panel. |
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.
.ma-newsletter.ma-newsletter__aside.ma-newsletter__busy.ma-newsletter__copy.ma-newsletter__eyebrow.ma-newsletter__form.ma-newsletter__headline.ma-newsletter__inner.ma-newsletter__label.ma-newsletter__note.ma-newsletter__sr.ma-newsletter__status
slot="eyebrow"slot="headline"slot="copy"slot="fields"slot="aside"
Fades in; the dots hold still; the spinner still turns, because it shows status.
ClientRouterInherits Reveal; the form is plain HTML, so nothing needs rebinding.
Replaces: Newsletter sections (Tailwind Plus). See the migration table.
A field of dots behind content, faded toward the edges, with an optional slow pulse.
Entrance on scroll that plays once and stays, staggers its children, scrubs with the scroll position when asked, honours reduced motion, and keeps working after every ClientRouter navigation.
An indeterminate arc whose length breathes while it rotates, so it looks like it is working rather than spinning a fixed wedge.
A closing call to action: a deep brand panel with an aurora and film grain behind it, a headline that catches a shine, a magnetic primary action, a secondary one, and a typed command.
Also: view transitions · how costs are measured · browser support · accessibility policy · five-minute setup · MCP for agents · this page as Markdown