Zoeken...  ⌘K GitHub

ContactCentered Forms

Gecentreerd contactformulier met 2-koloms velden grid. Volledig gevalideerd met success state.

/contact-centered
src/components/forms/ContactCentered.astro
---
/**
 * ContactCentered
 * Centered contact formulier — headline, sub, 2-koloms grid form.
 */
interface Props {
  eyebrow?: string;
  headline: string;
  sub?: string;
  submitLabel?: string;
  successMessage?: string;
}

const {
  eyebrow,
  headline,
  sub,
  submitLabel = 'Verstuur bericht',
  successMessage = 'Bedankt! We nemen snel contact met je op.',
} = Astro.props;
---

<section class="cc" data-component="contact-centered">
  <div class="cc__inner">
    <div class="cc__header">
      {eyebrow && <p class="cc__eyebrow">{eyebrow}</p>}
      <h2 class="cc__headline">{headline}</h2>
      {sub && <p class="cc__sub">{sub}</p>}
    </div>

    <form class="cc__form" id="cc-form" novalidate>
      <div class="cc__grid">
        <div class="cc__field">
          <label class="cc__label" for="cc-firstname">Voornaam <span class="cc__req">*</span></label>
          <input id="cc-firstname" name="firstname" type="text" class="cc__input" required autocomplete="given-name" />
        </div>
        <div class="cc__field">
          <label class="cc__label" for="cc-lastname">Achternaam</label>
          <input id="cc-lastname" name="lastname" type="text" class="cc__input" autocomplete="family-name" />
        </div>
        <div class="cc__field cc__field--full">
          <label class="cc__label" for="cc-email">E-mailadres <span class="cc__req">*</span></label>
          <input id="cc-email" name="email" type="email" class="cc__input" required autocomplete="email" />
        </div>
        <div class="cc__field cc__field--full">
          <label class="cc__label" for="cc-phone">Telefoonnummer</label>
          <input id="cc-phone" name="phone" type="tel" class="cc__input" autocomplete="tel" />
        </div>
        <div class="cc__field cc__field--full">
          <label class="cc__label" for="cc-subject">Onderwerp</label>
          <select id="cc-subject" name="subject" class="cc__input cc__select">
            <option value="">Kies een onderwerp...</option>
            <option>Algemene vraag</option>
            <option>Samenwerking</option>
            <option>Support</option>
            <option>Anders</option>
          </select>
        </div>
        <div class="cc__field cc__field--full">
          <label class="cc__label" for="cc-message">Bericht <span class="cc__req">*</span></label>
          <textarea id="cc-message" name="message" class="cc__input cc__textarea" required rows={5}></textarea>
        </div>
      </div>

      <div class="cc__footer">
        <p class="cc__privacy">Door te verzenden ga je akkoord met ons <a href="#">privacybeleid</a>.</p>
        <button type="submit" class="cc__submit">{submitLabel}</button>
      </div>
    </form>

    <div class="cc__success" id="cc-success" style="display:none">
      <svg width="48" height="48" viewBox="0 0 48 48" fill="none">
        <circle cx="24" cy="24" r="24" fill="currentColor" opacity="0.1"/>
        <path d="M14 24l8 8 12-14" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"/>
      </svg>
      <p>{successMessage}</p>
    </div>
  </div>
</section>

<style>
  .cc {
    background: var(--color-bg, #fff);
    padding: 5rem 1.5rem;
  }

  .cc__inner {
    max-width: 680px;
    margin: 0 auto;
  }

  .cc__header {
    text-align: center;
    margin-bottom: 3rem;
  }

  .cc__eyebrow {
    font-size: 0.75rem;
    font-weight: 700;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: var(--color-accent, #6366f1);
    margin-bottom: 0.75rem;
  }

  .cc__headline {
    font-size: clamp(1.875rem, 3vw, 2.75rem);
    font-weight: 800;
    letter-spacing: -0.03em;
    line-height: 1.15;
    color: var(--color-primary, #0a0a0a);
    margin-bottom: 0.875rem;
  }

  .cc__sub {
    font-size: 1.0625rem;
    color: var(--color-muted, #6b7280);
    line-height: 1.6;
  }

  .cc__grid {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 1.25rem;
  }

  .cc__field { }
  .cc__field--full { grid-column: 1 / -1; }

  .cc__label {
    display: block;
    font-size: 0.8125rem;
    font-weight: 600;
    color: var(--color-primary, #0a0a0a);
    margin-bottom: 0.5rem;
  }

  .cc__req { color: var(--color-accent, #6366f1); margin-left: 0.1em; }

  .cc__input {
    width: 100%;
    padding: 0.75rem 1rem;
    border: 1.5px solid rgba(0,0,0,0.1);
    border-radius: var(--radius, 0.5rem);
    font-size: 0.9375rem;
    font-family: inherit;
    color: var(--color-primary, #0a0a0a);
    background: #fff;
    transition: border-color 0.15s;
    outline: none;
    -webkit-appearance: none;
  }

  .cc__input:focus { border-color: var(--color-accent, #6366f1); }

  .cc__textarea {
    resize: vertical;
    min-height: 120px;
  }

  .cc__select { cursor: pointer; }

  .cc__footer {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 1rem;
    margin-top: 1.5rem;
    flex-wrap: wrap;
  }

  .cc__privacy {
    font-size: 0.8125rem;
    color: var(--color-muted, #6b7280);
  }

  .cc__privacy a {
    color: var(--color-accent, #6366f1);
    text-decoration: none;
  }

  .cc__submit {
    background: var(--color-accent, #6366f1);
    color: #fff;
    border: none;
    border-radius: var(--radius, 0.5rem);
    padding: 0.875rem 2rem;
    font-size: 1rem;
    font-weight: 700;
    font-family: inherit;
    cursor: pointer;
    transition: filter 0.2s, transform 0.2s;
    white-space: nowrap;
  }

  .cc__submit:hover {
    filter: brightness(1.1);
    transform: translateY(-1px);
  }

  /* Success */
  .cc__success {
    text-align: center;
    padding: 3rem;
    color: var(--color-accent, #6366f1);
  }

  .cc__success p {
    margin-top: 1rem;
    font-size: 1.125rem;
    font-weight: 600;
    color: var(--color-primary, #0a0a0a);
  }

  @media (max-width: 540px) {
    .cc__grid { grid-template-columns: 1fr; }
    .cc__footer { flex-direction: column; align-items: stretch; }
    .cc__submit { width: 100%; text-align: center; }
  }
</style>

<script>
  const form = document.getElementById('cc-form') as HTMLFormElement;
  const success = document.getElementById('cc-success');
  form?.addEventListener('submit', (e) => {
    e.preventDefault();
    if (form) form.style.display = 'none';
    if (success) success.style.display = 'block';
  });
</script>

Props

Prop Type Default Beschrijving
headline * string Formulier titel
eyebrow string Klein label boven titel
sub string Ondertitel
submitLabel string 'Verstuur bericht' Submit knop tekst

* = verplicht