Inline Newsletter Form
One row, one field, one button - the smallest honest email capture.
6 frameworksBeginner
A bordered card with a heading, a pitch, the form, and the privacy note underneath.
<!--
aria-labelledby ties the <section> to its own heading, so a screen reader
announces "Subscribe to the changelog, region" instead of an anonymous
landmark. Cheaper than aria-label and it cannot drift from the visible text.
The privacy note is a real <p> under the form, not a tooltip. It is the answer
to the only question the visitor is actually asking.
-->
<section class="nl-card" aria-labelledby="nl-card-title">
<h2 class="nl-card__title" id="nl-card-title">Subscribe to the changelog</h2>
<p class="nl-card__copy">
Every shipped feature, once a fortnight. Written by the people who built it.
</p>
<form class="nl-card__form" action="/api/subscribe" method="post">
<label class="nl-card__label" for="nl-card-email">Email address</label>
<input
class="nl-card__input"
id="nl-card-email"
name="email"
type="email"
autocomplete="email"
placeholder="you@company.com"
required
/>
<button class="nl-card__submit" type="submit">Subscribe</button>
</form>
<p class="nl-card__note">No spam. Unsubscribe in one click.</p>
</section>| Prop | Type | Default | Description |
|---|---|---|---|
titlerequired | string | - | Heading text for the card. |
copy | string | - | Body text shown under the title. |
ctaLabel | string | 'Subscribe' | Call-to-action button text. |
loading | boolean | false | Shows a spinner and blocks input while true. |
className | string | - | Additional classes merged onto the root element. |
`aria-labelledby` points the `<section>` at its own heading, so it announces as "Subscribe to the changelog, region" rather than an anonymous landmark - cheaper than `aria-label` and it cannot drift from the visible text. The privacy note is a real `<p>` under the form, not a tooltip: it is the answer to the only question the visitor is actually asking, so do not hide it behind a hover. One dark-mode detail worth keeping: the input goes to `gray-950` while the card sits at `gray-900`. A field the same colour as its container dissolves into it - on a card, the input has to go a step *darker* than the surface it sits on, not lighter.