src/components/forms/NewsletterBar.astro
---
/**
* NewsletterBar
* Compacte e-mail aanmeldbalk. Inline of gestapeld.
*
* Props:
* - headline?: string
* - placeholder?: string
* - btnLabel?: string
* - action: string — form action (Mailchimp, ConvertKit, eigen API)
* - disclaimer?: string — privacy tekst
* - layout?: 'inline' | 'stacked'
*/
interface Props {
headline?: string;
placeholder?: string;
btnLabel?: string;
action: string;
disclaimer?: string;
layout?: 'inline' | 'stacked';
}
const {
headline = 'Blijf op de hoogte',
placeholder = 'jouw@email.nl',
btnLabel = 'Aanmelden',
action,
disclaimer,
layout = 'inline',
} = Astro.props;
---
<section class={`newsletter newsletter--${layout}`}>
<div class="newsletter__inner">
{headline && <p class="newsletter__headline">{headline}</p>}
<form class="newsletter__form" action={action} method="POST" data-newsletter>
<div class="newsletter__fields">
<input
class="newsletter__input"
type="email"
name="email"
required
placeholder={placeholder}
autocomplete="email"
/>
<button type="submit" class="newsletter__btn">{btnLabel}</button>
</div>
{disclaimer && <p class="newsletter__disclaimer">{disclaimer}</p>}
<p class="newsletter__success" hidden aria-live="polite">Gelukt! Check je inbox.</p>
</form>
</div>
</section>
<style>
.newsletter {
padding: 3rem 1.5rem;
background: color-mix(in srgb, var(--color-text) 5%, transparent);
}
.newsletter__inner {
max-width: 640px;
margin: 0 auto;
display: flex;
flex-direction: column;
gap: 1rem;
}
.newsletter--inline .newsletter__inner {
flex-direction: row;
align-items: center;
max-width: 900px;
gap: 2rem;
flex-wrap: wrap;
}
.newsletter__headline {
font-weight: 700;
font-size: 1.125rem;
margin: 0;
white-space: nowrap;
}
.newsletter--inline .newsletter__form { flex: 1; min-width: 280px; }
.newsletter__fields {
display: flex;
gap: 0.5rem;
}
.newsletter__input {
flex: 1;
padding: 0.75rem 1rem;
border: 1px solid color-mix(in srgb, var(--color-text) 20%, transparent);
border-radius: var(--radius);
background: var(--color-bg);
color: var(--color-text);
font-size: 1rem;
font-family: inherit;
min-width: 0;
}
.newsletter__input:focus {
outline: none;
border-color: var(--color-accent);
box-shadow: 0 0 0 3px color-mix(in srgb, var(--color-accent) 20%, transparent);
}
.newsletter__btn {
padding: 0.75rem 1.25rem;
background: var(--color-accent);
color: #fff;
border: none;
border-radius: var(--radius);
font-weight: 700;
font-size: 0.9375rem;
cursor: pointer;
white-space: nowrap;
font-family: inherit;
transition: opacity 0.2s;
}
.newsletter__btn:hover { opacity: 0.85; }
.newsletter__disclaimer {
font-size: 0.75rem;
color: var(--color-muted);
margin: 0;
}
.newsletter__success {
font-size: 0.9375rem;
color: #15803d;
font-weight: 600;
margin: 0;
}
</style>
<script>
const form = document.querySelector('[data-newsletter]') as HTMLFormElement;
const success = form?.querySelector('.newsletter__success') as HTMLElement;
form?.addEventListener('submit', async (e) => {
e.preventDefault();
const btn = form.querySelector('button') as HTMLButtonElement;
btn.disabled = true;
try {
await fetch(form.action, { method: 'POST', body: new FormData(form), headers: { Accept: 'application/json' } });
form.querySelector('.newsletter__fields')?.setAttribute('hidden', '');
if (form.querySelector('.newsletter__disclaimer')) {
(form.querySelector('.newsletter__disclaimer') as HTMLElement).hidden = true;
}
success.hidden = false;
} catch {
btn.disabled = false;
}
});
</script>
Props
| Prop | Type | Default | Beschrijving |
|---|---|---|---|
action * | string | — | Form submit URL |
headline | string | — | Boven het formulier |
placeholder | string | 'jouw@email.nl' | Input placeholder |
btnLabel | string | 'Aanmelden' | Knop tekst |
disclaimer | string | — | Privacy tekst onder formulier |
layout | 'inline' | 'stacked' | 'inline' | Inline of gestapeld |
* = verplicht