splitforms.com
LEAD CAPTURE · CONTACT FORM TEMPLATE

Multi-Column Contact Form (responsive grid)

A contact form laid out in two columns on desktop and one column on mobile. Pure CSS Grid, responsive without media queries, no framework or library required.

1,000/mo free·no card·works on any host
form.htmlhtml34 lines
01<form action="https://splitforms.com/api/submit" method="POST">
02 <input type="hidden" name="access_key" value="YOUR_ACCESS_KEY">
03 <input type="hidden" name="subject" value="New detailed contact submission">
04
05 <label for="name">Full name *</label>
06 <input id="name" type="text" name="name" placeholder="Jane Builder" required>
07 <label for="email">Work email *</label>
08 <input id="email" type="email" name="email" placeholder="jane@company.com" required>
09 <label for="phone">Phone *</label>
10 <input id="phone" type="tel" name="phone" placeholder="+1 415 555 0142" required>
11 <label for="company">Company</label>
12 <input id="company" type="text" name="company" placeholder="Acme Inc">
13 <label for="reason">What's this about? *</label>
14 <select id="reason" name="reason" required>
15 <option value="">Choose…</option>
16 <option>Sales / pricing</option>
17 <option>Partnerships</option>
18 <option>Press / media</option>
19 <option>Support</option>
20 <option>Careers</option>
21 <option>Other</option>
22 </select>
23 <label for="message">Message *</label>
24 <textarea id="message" name="message" placeholder="Give us context — links, dates, deal size, anything we should know." required></textarea>
25
26 <!-- honeypot — bots fill every field -->
27 <input type="checkbox" name="botcheck" style="display:none" tabindex="-1" autocomplete="off">
28
29 <button type="submit">Send</button>
30</form>
31
32<p style="margin-top:12px;font-size:11px;color:#888;text-align:right">
33 Powered by <a href="https://splitforms.com" style="color:#888;text-decoration:none" target="_blank" rel="noopener">splitforms</a>
34</p>
1,000
submissions / mo, free
6
fields, ready to ship
5
code outputs
60s
from copy to inbox
§ 01Why it mattersthe qualifying-fields argument

Long contact forms feel less daunting in two columns. The CSS-only trick is `grid-template-columns: repeat(auto-fit, minmax(280px, 1fr))` — fields automatically reflow to one column on narrow viewports without media queries. Group related fields (first name / last name, city / state) into the same row and full-width fields (message, subject) span both columns with `grid-column: 1 / -1`.

Two columns on desktop, one column on mobile, zero media queries.
✦ at a glance
  • Detailed contact form · 6 fields
  • HTML, JS, React, PHP, cURL outputs
  • One POST endpoint, no SDK
  • Honeypot + classifier, no CAPTCHA
§ 02Live previewinteractive · sandboxed · no key required

See exactly what your visitors see — and you’ll receive.

Left: the rendered form, fully interactive in a sandboxed iframe. Right: the email and dashboard view that lands the moment a visitor submits.

preview · multi-column-contact-formlocalhost:3000
✦ what you’ll see in your inbox

Every submission becomes an email plus a dashboard row. The fields below are the exact payload your form will send. Reply-to is wired to the visitor’s email so hitting reply goes back to them.

dashboard · new submission14ms · 200 OK
SUBJECT · New detailed contact submission
Full name
Maya Iyer
Work email
maya@studio71.co
Phone
+1 415 555 0142
Company
Studio 71
What's this about?
Sales / pricing
Message
Loved your last open house in Hayes — looking for similar with parking. Pre-approved through Wells Fargo.

Iframe is sandboxed — submit doesn’t actually fire. Get your access key to wire it up live.

§ 03Three steps3 steps · ~60 seconds

Generate, embed, receive.

Three actions stand between you and your first lead. None of them require a backend, a database, or a CAPTCHA library.

STEP 01GENERATE

Wrap fields in a grid container

Add `display: grid; gap: 16px; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));` on the form or a wrapping div.

Create your form
key=sk_live_••••••••
STEP 02EMBED

Span full-width fields

Subject, message, and submit button get `grid-column: 1 / -1` so they span both columns regardless of viewport.

snippethtml
<form action="https://splitforms.com/api/submit" method="POST">
  …
</form>
STEP 03RECEIVE

Add field groups for related inputs

First/last name share a row. City/state/zip share a row. Use semantic <label> + <input> pairs so the grid layout doesn't break accessibility.

inbox · 1 newjust now
FROM contact@yoursite.com
New detailed contact submission
Maya Iyer maya@studio71.co
Loved your last open house in Hayes — looking for similar with parking. Pre-approved through Wells Fargo.
§ 04Copy & ship5 languages · same endpoint

Five outputs. One backend.

HTML by default. Click open the language you ship in — every variant POSTs to the same /api/submit endpoint.

01HTMLform.html34 lines
<form action="https://splitforms.com/api/submit" method="POST">
  <input type="hidden" name="access_key" value="YOUR_ACCESS_KEY">
  <input type="hidden" name="subject" value="New detailed contact submission">

  <label for="name">Full name *</label>
  <input id="name" type="text" name="name" placeholder="Jane Builder" required>
  <label for="email">Work email *</label>
  <input id="email" type="email" name="email" placeholder="jane@company.com" required>
  <label for="phone">Phone *</label>
  <input id="phone" type="tel" name="phone" placeholder="+1 415 555 0142" required>
  <label for="company">Company</label>
  <input id="company" type="text" name="company" placeholder="Acme Inc">
  <label for="reason">What's this about? *</label>
  <select id="reason" name="reason" required>
    <option value="">Choose…</option>
    <option>Sales / pricing</option>
    <option>Partnerships</option>
    <option>Press / media</option>
    <option>Support</option>
    <option>Careers</option>
    <option>Other</option>
  </select>
  <label for="message">Message *</label>
  <textarea id="message" name="message" placeholder="Give us context — links, dates, deal size, anything we should know." required></textarea>

  <!-- honeypot — bots fill every field -->
  <input type="checkbox" name="botcheck" style="display:none" tabindex="-1" autocomplete="off">

  <button type="submit">Send</button>
</form>

<p style="margin-top:12px;font-size:11px;color:#888;text-align:right">
  Powered by <a href="https://splitforms.com" style="color:#888;text-decoration:none" target="_blank" rel="noopener">splitforms</a>
</p>
02JavaScriptform.js50 lines
<form id="lf-form">
  <label for="name">Full name *</label>
  <input id="name" type="text" name="name" placeholder="Jane Builder" required>
  <label for="email">Work email *</label>
  <input id="email" type="email" name="email" placeholder="jane@company.com" required>
  <label for="phone">Phone *</label>
  <input id="phone" type="tel" name="phone" placeholder="+1 415 555 0142" required>
  <label for="company">Company</label>
  <input id="company" type="text" name="company" placeholder="Acme Inc">
  <label for="reason">What's this about? *</label>
  <select id="reason" name="reason" required>
    <option value="">Choose…</option>
    <option>Sales / pricing</option>
    <option>Partnerships</option>
    <option>Press / media</option>
    <option>Support</option>
    <option>Careers</option>
    <option>Other</option>
  </select>
  <label for="message">Message *</label>
  <textarea id="message" name="message" placeholder="Give us context — links, dates, deal size, anything we should know." required></textarea>
  <button type="submit">Send</button>
</form>

<p style="margin-top:12px;font-size:11px;color:#888;text-align:right">
  Powered by <a href="https://splitforms.com" style="color:#888;text-decoration:none" target="_blank" rel="noopener">splitforms</a>
</p>

<script>
  document.getElementById('lf-form').addEventListener('submit', async (e) => {
    e.preventDefault();
    const data = new FormData(e.target);
    data.set('access_key', 'YOUR_ACCESS_KEY');
    data.set('subject', 'New detailed contact submission');

    const res = await fetch('https://splitforms.com/api/submit', {
      method: 'POST',
      body: data,
      headers: { Accept: 'application/json' },
    });

    const json = await res.json();
    if (json.success) {
      e.target.reset();
      alert('Sent!');
    } else {
      alert('Error: ' + (json.message || 'Try again'));
    }
  });
</script>
03React / Next.jsForm.tsx65 lines
'use client';

import { useState, type FormEvent } from 'react';

export default function ContactDetailedForm() {
  const [status, setStatus] = useState<'idle' | 'sending' | 'sent' | 'error'>('idle');

  async function onSubmit(e: FormEvent<HTMLFormElement>) {
    e.preventDefault();
    setStatus('sending');

    const data = new FormData(e.currentTarget);
    data.set('access_key', 'YOUR_ACCESS_KEY');
    data.set('subject', 'New detailed contact submission');

    const res = await fetch('https://splitforms.com/api/submit', {
      method: 'POST',
      body: data,
      headers: { Accept: 'application/json' },
    });

    const json = await res.json();
    setStatus(json.success ? 'sent' : 'error');
    if (json.success) e.currentTarget.reset();
  }

  if (status === 'sent') return <p>Thanks — we&rsquo;ll be in touch.</p>;

  return (
    <>
    <form onSubmit={onSubmit}>
      <label htmlFor="name">Full name *</label>
      <input id="name" type="text" name="name" placeholder="Jane Builder" required />
      <label htmlFor="email">Work email *</label>
      <input id="email" type="email" name="email" placeholder="jane@company.com" required />
      <label htmlFor="phone">Phone *</label>
      <input id="phone" type="tel" name="phone" placeholder="+1 415 555 0142" required />
      <label htmlFor="company">Company</label>
      <input id="company" type="text" name="company" placeholder="Acme Inc" />
      <label htmlFor="reason">What's this about? *</label>
      <select id="reason" name="reason" required>
        <option value="">Choose…</option>
        <option>Sales / pricing</option>
        <option>Partnerships</option>
        <option>Press / media</option>
        <option>Support</option>
        <option>Careers</option>
        <option>Other</option>
      </select>
      <label htmlFor="message">Message *</label>
      <textarea id="message" name="message" placeholder="Give us context — links, dates, deal size, anything we should know." required />

      <button type="submit" disabled={status === 'sending'}>
        {status === 'sending' ? 'Sending…' : 'Send'}
      </button>

      {status === 'error' && <p>Something went wrong. Try again.</p>}
    </form>

      <p style={{ marginTop: 12, fontSize: 11, color: '#888', textAlign: 'right' }}>
        Powered by <a href="https://splitforms.com" target="_blank" rel="noopener" style={{ color: '#888', textDecoration: 'none' }}>splitforms</a>
      </p>
    </>
  );
}
04PHPsubmit.php28 lines
<?php
// Drop into a PHP page. Receives a form POST and proxies it to splitforms.com.
// Useful when you want to add server-side validation or rate limiting.

if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    $allowed = ['name', 'email', 'phone', 'company', 'reason', 'message'];
    $payload = ['access_key' => 'YOUR_ACCESS_KEY'];
    $payload['subject'] = 'New detailed contact submission';

    foreach ($allowed as $f) {
        if (isset($_POST[$f])) $payload[$f] = $_POST[$f];
    }

    $ch = curl_init('https://splitforms.com/api/submit');
    curl_setopt($ch, CURLOPT_POST, true);
    curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($payload));
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_HTTPHEADER, ['Accept: application/json']);
    $response = curl_exec($ch);
    $status   = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    curl_close($ch);

    header('Content-Type: application/json');
    http_response_code($status);
    echo $response;
    exit;
}
?>
05cURLtest.sh10 lines
curl -X POST https://splitforms.com/api/submit \
  -H "Accept: application/json" \
  -d "access_key=YOUR_ACCESS_KEY" \
  -d "subject=New detailed contact submission" \
  -d "name=Jane Builder" \
  -d "email=jane@example.com" \
  -d "phone=+15555555555" \
  -d "company=Jane Builder" \
  -d "reason=Sales / pricing" \
  -d "message=Hello from cURL" 

Replace YOUR_ACCESS_KEY with the key from your dashboard. That’s the only edit.

§ 04bUse this template with…25 frameworks · same backend

One template. Every framework.

The same field set works on every framework splitforms supports. HTML, React, Next.js, Vue, Astro, Hugo, WordPress — same POST, same backend.

Detailed contact form for HTML/forms/html/contact-detailedDetailed contact form for Next.js/forms/nextjs/contact-detailedDetailed contact form for React/forms/react/contact-detailedDetailed contact form for Vue/forms/vue/contact-detailedDetailed contact form for Astro/forms/astro/contact-detailedDetailed contact form for Svelte/forms/svelte/contact-detailedDetailed contact form for Webflow/forms/webflow/contact-detailedDetailed contact form for Carrd/forms/carrd/contact-detailedDetailed contact form for WordPress/forms/wordpress/contact-detailedDetailed contact form for Tailwind CSS/forms/tailwind/contact-detailedDetailed contact form for AJAX (vanilla JS)/forms/ajax/contact-detailedDetailed contact form for Hugo/forms/hugo/contact-detailedDetailed contact form for Gatsby/forms/gatsby/contact-detailedDetailed contact form for Eleventy/forms/eleventy/contact-detailedDetailed contact form for SvelteKit/forms/sveltekit/contact-detailedDetailed contact form for Framer/forms/framer/contact-detailedDetailed contact form for Nuxt/forms/nuxt/contact-detailedDetailed contact form for Alpine.js/forms/alpinejs/contact-detailedDetailed contact form for Bootstrap/forms/bootstrap/contact-detailedDetailed contact form for Jekyll/forms/jekyll/contact-detailedDetailed contact form for Vite/forms/vite/contact-detailedDetailed contact form for JavaScript/forms/javascript/contact-detailedDetailed contact form for Cloudflare Pages/forms/cloudflare/contact-detailedDetailed contact form for Netlify/forms/netlify/contact-detailedDetailed contact form for Vercel/forms/vercel/contact-detailed
§ 06FAQ4 answered

Things people ask before they ship.

Direct answers, no marketing fluff. Missing one? Email hello@splitforms.com.

01Why auto-fit instead of explicit media queries?
auto-fit collapses columns when the minimum width can't fit — so a 2-column desktop layout becomes 1-column at any viewport narrower than ~600px without writing a single media query. Cleaner and more resilient than explicit breakpoints.
02How do I make the message field span both columns?
Wrap it (or its label) in an element with grid-column: 1 / -1. That spans from the first track to the last regardless of how many tracks the grid currently has.
03Will this work in Internet Explorer 11?
No — IE11 has partial CSS Grid support that doesn't include auto-fit. If you must support IE11, fall back to flex-wrap with explicit width: 50% on field wrappers. Otherwise auto-fit is the right call.
04Does the form still work without CSS?
Yes. With CSS disabled, all fields stack vertically — the HTML order matches the visual order in a 2-column layout, so it degrades cleanly.
✻ ✻ ✻

Ship your multi-column contact form (responsive grid) in 60 seconds.

1,000 free submissions per month. No credit card. Copy the snippet, paste your access key, watch leads land in your inbox.

Get free access key →Browse all 67 templates →
v0.1 · founders pricing locked in · early access open