caliper uidocs

Newsletter signup

Block

An email field with consent and success states.

Installs components/ui/newsletter-signup.tsxDepends on @radix-ui/react-slot, lucide-reactMotion none in component source
pnpm dlx shadcn@latest add https://caliperui.site/r/newsletter-signup.json

Get the changelog

One short email when we ship something. Unsubscribe in one click.

We never share your address. Try an address containing “fail” to see an error.

When to use #

Use it for

  • Collecting emails for updates.

Skip it when

  • You need a full account; use the sign-up form.

Installation #

terminal
pnpm dlx shadcn@latest add https://caliperui.site/r/newsletter-signup.json

Adds its 6 files to components/ui/, each with its own stylesheet, and installs their packages. No account needed. The first part also adds Caliper UI’s tokens; import them once as in Installation.

Usage #

This is the code behind the preview above.

example.tsx
import { NewsletterSignup } from "@/components/ui/newsletter-signup"

const wait = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));

export function NewsletterSignupDemo() {
  return (
    <NewsletterSignup
              note="We never share your address. Try an address containing “fail” to see an error."
              onSubmit={async (email) => {
                await wait(900);
                if (/fail/i.test(email)) throw new Error("We couldn't add you right now. Try again in a moment.");
              }}
            />
  );
}

API reference #

NewsletterSignup

component
PropTypeDefaultDescription
titlestring"Get the changelog"
descriptionstring"One short email when we ship something. Unsubscribe in one click."
buttonLabelstring"Subscribe"
successTextstring"You're on the list. Check your inbox to confirm."
onSubmit *(email: string) => void | Promise<void | { error?: string | undefined; }> | { error?: string | undefined; }—
noteReactNode—Small print, such as a privacy link.
classNamestring—

Accessibility #

It exposes status semantics, with aria-labelledby, aria-hidden, aria-invalid and aria-describedby kept in step with state. Changes that matter are announced through a live region.

No custom key handling; Tab and the native activation keys do the work.

Motion #

It declares no animation in its component source. Any transition comes from its stylesheet, which uses the duration tokens that collapse to instant under prefers-reduced-motion.