# Contact form template

A free contact us form with name, email and message fields. Copy the HTML, CSS and JS, or use the React, Next.js, Vue, Svelte or Astro version.

This form asks for a name, an email address to reply to and a message. All three fields are required, and the browser checks the email address before the form is sent.

Live preview, field editor, four themes and styled versions for every framework: https://nisuform.com/templates/contact-form

## Fields

| Field | Name | Type | Required |
| --- | --- | --- | --- |
| Name | `name` | Short text | Yes |
| Email | `email` | Email | Yes |
| Message | `message` | Long text | Yes |

## How to use it

1. Create a free form at https://app.nisuform.com and copy its form key.
2. Paste the code where the form should appear and replace `YOUR_FORM_KEY` with your key.
3. Submit the form once. The submission appears in your Nisuform inbox and you get an email.

## Styled HTML, CSS and JavaScript

The form with its styles and a small script that sends it without a page reload and shows the success message.

```html
<form id="nf-contact-form" class="nf-form" action="https://api.nisuform.com/s/YOUR_FORM_KEY" method="POST">
  <style>
#nf-contact-form {
  --nf-accent: #e60058;
  --nf-on-accent: #ffffff;
  --nf-text: #171717;
  --nf-muted: #525252;
  --nf-field: #f5f5f5;
  --nf-field-text: #171717;
  --nf-placeholder: #6b6b6b;
  --nf-control: #8a8a8a;
  --nf-error: #b91c1c;
  color-scheme: light;
  box-sizing: border-box;
  width: 100%;
  max-width: 480px;
  margin: 0;
  padding: 28px;
  border: 1px solid rgba(0, 0, 0, 0.08);
  border-radius: 16px;
  background: linear-gradient(180deg, #ffffff, #ffffff);
  box-shadow: 0 16px 40px -16px rgba(0, 0, 0, 0.12);
  color: var(--nf-text);
  font: inherit;
  font-size: 16px;
  line-height: 1.5;
  text-align: left;
}
#nf-contact-form *, #nf-contact-form *::before, #nf-contact-form *::after { box-sizing: border-box; }
#nf-contact-form [hidden] { display: none !important; }
#nf-contact-form .nf-title { margin: 0; font-size: 1.25em; font-weight: 650; line-height: 1.25; letter-spacing: -0.01em; color: var(--nf-text); }
#nf-contact-form .nf-desc { margin: 6px 0 0; font-size: 0.9375em; color: var(--nf-muted); }
#nf-contact-form .nf-fields { display: grid; gap: 18px; margin-top: 22px; }
#nf-contact-form .nf-head + .nf-fields { margin-top: 22px; }
#nf-contact-form > .nf-fields:first-child { margin-top: 0; }
#nf-contact-form .nf-field { margin: 0; padding: 0; border: 0; min-width: 0; }
#nf-contact-form .nf-label { display: block; margin: 0 0 6px; padding: 0; font-size: 0.875em; font-weight: 600; color: var(--nf-text); }
#nf-contact-form .nf-req { color: var(--nf-muted); font-weight: 400; }
#nf-contact-form .nf-help { margin: 6px 0 0; font-size: 0.8125em; color: var(--nf-muted); }
#nf-contact-form .nf-input { display: block; width: 100%; margin: 0; padding: 10px 12px; border: 1px solid transparent; border-radius: 10px; background: var(--nf-field); color: var(--nf-field-text); font: inherit; font-size: 1em; line-height: 1.4; outline: 2px solid transparent; outline-offset: 1px; transition: border-color 150ms, box-shadow 150ms; }
#nf-contact-form input.nf-input, #nf-contact-form select.nf-input { height: 2.75em; padding-top: 0; padding-bottom: 0; }
#nf-contact-form textarea.nf-input { min-height: 112px; padding-top: 10px; padding-bottom: 10px; resize: vertical; }
#nf-contact-form .nf-input::placeholder { color: var(--nf-placeholder); opacity: 1; }
#nf-contact-form .nf-input:hover { border-color: color-mix(in srgb, var(--nf-text) 14%, transparent); }
#nf-contact-form .nf-input:focus { border-color: var(--nf-accent); box-shadow: 0 0 0 3px color-mix(in srgb, var(--nf-accent) 24%, transparent); }
#nf-contact-form .nf-input:user-invalid { border-color: var(--nf-error); }
#nf-contact-form .nf-input:user-invalid:focus { box-shadow: 0 0 0 3px color-mix(in srgb, var(--nf-error) 24%, transparent); }
#nf-contact-form .nf-input:-webkit-autofill { -webkit-text-fill-color: var(--nf-field-text); caret-color: var(--nf-field-text); box-shadow: inset 0 0 0 100px var(--nf-field); }
#nf-contact-form .nf-input:-webkit-autofill:focus { box-shadow: inset 0 0 0 100px var(--nf-field), 0 0 0 3px color-mix(in srgb, var(--nf-accent) 24%, transparent); }
#nf-contact-form input[type="number"].nf-input { -moz-appearance: textfield; appearance: textfield; }
#nf-contact-form input[type="number"].nf-input::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
#nf-contact-form input[type="number"].nf-input::-webkit-outer-spin-button { -webkit-appearance: none; margin: 0; }
#nf-contact-form input[type="date"].nf-input { min-width: 0; text-align: left; }
#nf-contact-form input[type="date"].nf-input::-webkit-date-and-time-value { text-align: left; }
#nf-contact-form input[type="date"].nf-input.nf-empty:not(:focus) { color: var(--nf-placeholder); }
#nf-contact-form input[type="date"].nf-input::-webkit-calendar-picker-indicator { margin: 0; padding: 4px; border-radius: 6px; opacity: 0.55; cursor: pointer; transition: opacity 150ms; }
#nf-contact-form input[type="date"].nf-input::-webkit-calendar-picker-indicator:hover { opacity: 1; }
#nf-contact-form select.nf-input { -webkit-appearance: none; appearance: none; padding-right: 40px; background-image: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2024%2024%22%20fill%3D%22none%22%20stroke%3D%22%23525252%22%20stroke-width%3D%222%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%3E%3Cpath%20d%3D%22M6%209l6%206l6%20-6%22%2F%3E%3C%2Fsvg%3E"); background-repeat: no-repeat; background-position: right 12px center; background-size: 16px 16px; cursor: pointer; }
#nf-contact-form select.nf-input:has(option[value=""]:checked) { color: var(--nf-placeholder); }
#nf-contact-form select.nf-input option { color: var(--nf-field-text); }
#nf-contact-form .nf-options { display: grid; gap: 8px; }
#nf-contact-form .nf-option { display: flex; align-items: center; gap: 10px; min-height: 2.75em; margin: 0; padding: 10px 12px; border: 1px solid color-mix(in srgb, var(--nf-text) 14%, transparent); border-radius: 10px; background: transparent; color: var(--nf-text); font-size: 0.9375em; line-height: 1.4; cursor: pointer; transition: border-color 150ms, background-color 150ms; }
#nf-contact-form .nf-option:hover { border-color: color-mix(in srgb, var(--nf-text) 28%, transparent); }
#nf-contact-form .nf-option:has(input:checked) { border-color: var(--nf-accent); background: color-mix(in srgb, var(--nf-accent) 8%, transparent); }
#nf-contact-form .nf-option span { min-width: 0; overflow-wrap: anywhere; }
#nf-contact-form .nf-option input { -webkit-appearance: none; appearance: none; flex: none; width: 18px; height: 18px; margin: 0; border: 1.5px solid var(--nf-control); border-radius: 50%; background: transparent; outline: 2px solid transparent; cursor: pointer; transition: border-color 150ms, box-shadow 150ms; }
#nf-contact-form .nf-option input:checked { border-color: var(--nf-accent); background: radial-gradient(circle, var(--nf-on-accent) 0 3px, var(--nf-accent) 3.5px); }
#nf-contact-form .nf-option input:focus-visible { box-shadow: 0 0 0 3px color-mix(in srgb, var(--nf-accent) 24%, transparent); }
#nf-contact-form .nf-check { display: flex; align-items: flex-start; gap: 10px; margin: 0; font-size: 0.9375em; line-height: 1.5; color: var(--nf-text); cursor: pointer; }
#nf-contact-form .nf-check input { -webkit-appearance: none; appearance: none; flex: none; width: 20px; height: 20px; margin: 1px 0 0; border: 1.5px solid var(--nf-control); border-radius: 6px; background: var(--nf-field) center / 14px 14px no-repeat; outline: 2px solid transparent; cursor: pointer; transition: background-color 150ms, border-color 150ms, box-shadow 150ms; }
#nf-contact-form .nf-check input:checked { border-color: var(--nf-accent); background-color: var(--nf-accent); background-image: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2024%2024%22%20fill%3D%22none%22%20stroke%3D%22%23ffffff%22%20stroke-width%3D%223.5%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%3E%3Cpath%20d%3D%22M5%2012l5%205l10%20-10%22%2F%3E%3C%2Fsvg%3E"); }
#nf-contact-form .nf-check input:focus-visible { box-shadow: 0 0 0 3px color-mix(in srgb, var(--nf-accent) 24%, transparent); }
#nf-contact-form .nf-check input:user-invalid { border-color: var(--nf-error); }
#nf-contact-form .nf-rating { display: flex; flex-wrap: wrap; margin: 0 0 0 -4px; }
#nf-contact-form .nf-rating label { display: inline-flex; padding: 4px; border-radius: 8px; color: var(--nf-control); color: color-mix(in srgb, var(--nf-control) 45%, transparent); cursor: pointer; transition: color 150ms; }
#nf-contact-form .nf-rating svg { width: 30px; height: 30px; fill: currentColor; stroke: currentColor; stroke-width: 1.5; stroke-linejoin: round; transition: transform 150ms; }
#nf-contact-form .nf-rating input:checked + label, #nf-contact-form .nf-rating label:has(~ input:checked) { color: var(--nf-accent); }
#nf-contact-form .nf-rating:hover input + label { color: var(--nf-control); color: color-mix(in srgb, var(--nf-control) 45%, transparent); }
#nf-contact-form .nf-rating:hover label:hover, #nf-contact-form .nf-rating:hover label:has(~ label:hover) { color: var(--nf-accent); }
#nf-contact-form .nf-rating label:active svg { transform: scale(0.88); }
#nf-contact-form .nf-rating input:focus-visible + label { outline: 2px solid var(--nf-accent); outline-offset: 0; }
#nf-contact-form .nf-file { position: relative; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 4px; min-height: 112px; padding: 16px; border: 1.5px dashed var(--nf-control); border-color: color-mix(in srgb, var(--nf-control) 55%, transparent); border-radius: 10px; background: var(--nf-field); color: var(--nf-muted); text-align: center; transition: border-color 150ms, background-color 150ms, box-shadow 150ms; }
#nf-contact-form .nf-file-input { position: absolute; top: 0; left: 0; width: 100%; height: 100%; margin: 0; opacity: 0; cursor: pointer; }
#nf-contact-form .nf-file svg { width: 22px; height: 22px; margin-bottom: 4px; }
#nf-contact-form .nf-file-text { margin: 0; max-width: 100%; font-size: 0.875em; overflow-wrap: anywhere; }
#nf-contact-form .nf-file-cta { font-weight: 600; color: var(--nf-text); }
#nf-contact-form .nf-file-types { margin: 0; font-size: 0.75em; color: var(--nf-placeholder); }
#nf-contact-form .nf-file:hover { border-color: var(--nf-control); }
#nf-contact-form .nf-file:focus-within { border-color: var(--nf-accent); box-shadow: 0 0 0 3px color-mix(in srgb, var(--nf-accent) 24%, transparent); }
#nf-contact-form .nf-file.nf-file-over { border-color: var(--nf-accent); background: color-mix(in srgb, var(--nf-accent) 8%, var(--nf-field)); }
#nf-contact-form .nf-file.nf-file-chosen { border-style: solid; border-color: var(--nf-accent); }
#nf-contact-form .nf-file.nf-file-chosen .nf-file-text { font-weight: 600; color: var(--nf-text); }
#nf-contact-form .nf-file:has(.nf-file-input:user-invalid) { border-color: var(--nf-error); }
#nf-contact-form .nf-sr { position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0; overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0; }
#nf-contact-form .nf-hp { position: absolute; left: -9999px; width: 1px; height: 1px; overflow: hidden; }
#nf-contact-form .nf-captcha { margin-top: 18px; }
#nf-contact-form .nf-submit { display: inline-flex; align-items: center; justify-content: center; width: 100%; min-height: 44px; margin: 22px 0 0; padding: 10px 18px; border: 0; border-radius: 10px; background: var(--nf-accent); color: var(--nf-on-accent); font: inherit; font-size: 1em; font-weight: 600; cursor: pointer; transition: filter 150ms, transform 150ms; }
#nf-contact-form .nf-submit:hover { filter: brightness(0.94); }
#nf-contact-form .nf-submit:active { transform: scale(0.98); }
#nf-contact-form .nf-submit:focus-visible { outline: 2px solid var(--nf-accent); outline-offset: 3px; }
#nf-contact-form .nf-submit:disabled { opacity: 0.6; cursor: default; }
#nf-contact-form .nf-error { margin: 14px 0 0; padding: 10px 12px; border-radius: 10px; background: color-mix(in srgb, var(--nf-error) 10%, transparent); font-size: 0.875em; color: var(--nf-error); }
#nf-contact-form.nf-sent { display: flex; flex-direction: column; justify-content: center; min-height: min(var(--nf-height, 0px), 440px); }
#nf-contact-form .nf-success { display: flex; flex-direction: column; align-items: center; padding: 20px 0; text-align: center; outline: none; }
#nf-contact-form .nf-success-icon { display: flex; flex: none; align-items: center; justify-content: center; width: 56px; height: 56px; margin: 0 0 20px; border-radius: 50%; background: var(--nf-accent); color: var(--nf-on-accent); box-shadow: 0 0 0 8px color-mix(in srgb, var(--nf-accent) 12%, transparent); }
#nf-contact-form .nf-success-icon svg { display: block; width: 28px; height: 28px; margin: 0; stroke-dasharray: 1; }
#nf-contact-form .nf-success-title { margin: 0; max-width: 24em; font-size: 1.125em; font-weight: 650; line-height: 1.35; letter-spacing: -0.01em; color: var(--nf-text); text-wrap: balance; overflow-wrap: anywhere; }
#nf-contact-form .nf-success-text { margin: 0; max-width: 30em; font-size: 0.9375em; line-height: 1.5; color: var(--nf-text); text-wrap: pretty; overflow-wrap: anywhere; }
#nf-contact-form .nf-success-title + .nf-success-text { margin-top: 6px; color: var(--nf-muted); }
#nf-contact-form.nf-sent .nf-success { animation: nf-success-in 400ms cubic-bezier(0.16, 1, 0.3, 1) both; }
#nf-contact-form.nf-sent .nf-success-icon { animation: nf-success-pop 500ms cubic-bezier(0.34, 1.56, 0.64, 1) both; }
#nf-contact-form.nf-sent .nf-success-icon path { animation: nf-success-draw 360ms 160ms cubic-bezier(0.65, 0, 0.35, 1) both; }
@keyframes nf-success-in { from { opacity: 0; transform: translateY(6px); } }
@keyframes nf-success-pop { from { transform: scale(0.6); } }
@keyframes nf-success-draw { from { stroke-dashoffset: 1; } }
@media (prefers-reduced-motion: reduce) { #nf-contact-form * { transition: none !important; animation: none !important; } }
  </style>
  <div class="nf-head">
    <h2 class="nf-title">Let’s talk</h2>
    <p class="nf-desc">Send us a message and we will get back to you soon.</p>
  </div>
  <div class="nf-fields">
    <div class="nf-field">
      <label class="nf-label" for="nf-contact-form-name">Name<span class="nf-req" aria-hidden="true"> *</span></label>
      <input class="nf-input" id="nf-contact-form-name" name="name" type="text" autocomplete="name" placeholder="Jane Smith" required>
    </div>
    <div class="nf-field">
      <label class="nf-label" for="nf-contact-form-email">Email<span class="nf-req" aria-hidden="true"> *</span></label>
      <input class="nf-input" id="nf-contact-form-email" name="email" type="email" autocomplete="email" placeholder="jane@example.com" required>
    </div>
    <div class="nf-field">
      <label class="nf-label" for="nf-contact-form-message">Message<span class="nf-req" aria-hidden="true"> *</span></label>
      <textarea class="nf-input" id="nf-contact-form-message" name="message" rows="4" placeholder="Tell us about your project" required></textarea>
    </div>
  </div>
  <div class="nf-hp" aria-hidden="true"><input type="text" name="_gotcha" tabindex="-1" autocomplete="off"></div>
  <button class="nf-submit" type="submit">Send message</button>
  <p class="nf-error" role="alert" hidden></p>
  <div class="nf-success" role="status" tabindex="-1" hidden>
    <span class="nf-success-icon" aria-hidden="true"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" focusable="false"><path d="M5 12l5 5l10 -10" pathLength="1"/></svg></span>
    <p class="nf-success-title">Thanks!</p>
    <p class="nf-success-text">We got your message and will reply soon.</p>
  </div>
</form>
<script>
(function () {
  var form = document.getElementById("nf-contact-form")
  if (!form || !window.fetch) return
  var started = Date.now()
  var button = form.querySelector('.nf-submit')
  var error = form.querySelector('.nf-error')
  var success = form.querySelector('.nf-success')
  form.addEventListener('submit', function (event) {
    event.preventDefault()
    var data = new FormData(form)
    data.set('_elapsed', String(Date.now() - started))
    var label = button.textContent
    button.disabled = true
    button.textContent = 'Sending...'
    error.hidden = true
    fetch(form.action, {
      method: 'POST',
      headers: { Accept: 'application/json' },
      body: new URLSearchParams(data),
    })
      .then(function (response) {
        return response.json().catch(function () { return {} }).then(function (body) {
          if (!response.ok || !body.ok) throw new Error(body.message || 'Something went wrong. Please try again.')
        })
      })
      .then(function () {
        var redirect = form.getAttribute('data-redirect')
        if (redirect) {
          window.location.href = redirect
          return
        }
        form.style.setProperty('--nf-height', form.offsetHeight + 'px')
        Array.prototype.forEach.call(form.children, function (child) {
          if (child !== success && child.tagName !== 'STYLE') child.hidden = true
        })
        form.classList.add('nf-sent')
        success.hidden = false
        success.focus()
      })
      .catch(function (reason) {
        error.textContent = reason && reason.message ? reason.message : 'Something went wrong. Please try again.'
        error.hidden = false
      })
      .finally(function () {
        button.disabled = false
        button.textContent = label
        if (window.turnstile) window.turnstile.reset()
      })
  })
})()
</script>
```

## Unstyled HTML

Bare markup that picks up your own CSS. It posts without JavaScript and keeps the hidden `_gotcha` field that stops bots.

```html
<form action="https://api.nisuform.com/s/YOUR_FORM_KEY" method="POST">
  <label>
    Name
    <input name="name" type="text" placeholder="Jane Smith" required>
  </label>
  <label>
    Email
    <input name="email" type="email" placeholder="jane@example.com" required>
  </label>
  <label>
    Message
    <textarea name="message" rows="5" placeholder="Tell us about your project" required></textarea>
  </label>
  <input type="text" name="_gotcha" tabindex="-1" autocomplete="off" style="display:none">
  <button type="submit">Send message</button>
</form>
```

## Framework components

### React

```jsx
import { useRef, useState } from 'react'

export function ContactForm() {
  const renderedAt = useRef(Date.now())
  const [status, setStatus] = useState('idle')

  async function onSubmit(event) {
    event.preventDefault()
    setStatus('sending')
    const data = Object.fromEntries(new FormData(event.currentTarget))
    data._elapsed = String(Date.now() - renderedAt.current)
    const response = await fetch("https://api.nisuform.com/s/YOUR_FORM_KEY", {
      method: 'POST',
      headers: { 'Content-Type': 'application/json', Accept: 'application/json' },
      body: JSON.stringify(data),
    })
    setStatus(response.ok ? 'sent' : 'error')
  }

  if (status === 'sent') return <p>Thanks! We got your message and will reply soon.</p>

  return (
    <form onSubmit={onSubmit}>
      <label>
        Name
        <input name="name" type="text" placeholder="Jane Smith" required />
      </label>
      <label>
        Email
        <input name="email" type="email" placeholder="jane@example.com" required />
      </label>
      <label>
        Message
        <textarea name="message" rows={5} placeholder="Tell us about your project" required></textarea>
      </label>
      <input type="text" name="_gotcha" tabIndex={-1} autoComplete="off" style={{ display: 'none' }} />
      <button type="submit" disabled={status === 'sending'}>Send message</button>
      {status === 'error' && <p role="alert">Something went wrong. Please try again.</p>}
    </form>
  )
}
```

### Next.js

```jsx
'use client'

import { useRef, useState } from 'react'

export function ContactForm() {
  const renderedAt = useRef(Date.now())
  const [status, setStatus] = useState('idle')

  async function onSubmit(event) {
    event.preventDefault()
    setStatus('sending')
    const data = Object.fromEntries(new FormData(event.currentTarget))
    data._elapsed = String(Date.now() - renderedAt.current)
    const response = await fetch("https://api.nisuform.com/s/YOUR_FORM_KEY", {
      method: 'POST',
      headers: { 'Content-Type': 'application/json', Accept: 'application/json' },
      body: JSON.stringify(data),
    })
    setStatus(response.ok ? 'sent' : 'error')
  }

  if (status === 'sent') return <p>Thanks! We got your message and will reply soon.</p>

  return (
    <form onSubmit={onSubmit}>
      <label>
        Name
        <input name="name" type="text" placeholder="Jane Smith" required />
      </label>
      <label>
        Email
        <input name="email" type="email" placeholder="jane@example.com" required />
      </label>
      <label>
        Message
        <textarea name="message" rows={5} placeholder="Tell us about your project" required></textarea>
      </label>
      <input type="text" name="_gotcha" tabIndex={-1} autoComplete="off" style={{ display: 'none' }} />
      <button type="submit" disabled={status === 'sending'}>Send message</button>
      {status === 'error' && <p role="alert">Something went wrong. Please try again.</p>}
    </form>
  )
}
```

### Vue

```vue
<script setup>
import { ref } from 'vue'

const shownAt = Date.now()
const status = ref('idle')

async function onSubmit(event) {
  status.value = 'sending'
  const data = new FormData(event.target)
  data.set('_elapsed', String(Date.now() - shownAt))
  const response = await fetch("https://api.nisuform.com/s/YOUR_FORM_KEY", {
    method: 'POST',
    headers: { Accept: 'application/json' },
    body: data,
  })
  status.value = response.ok ? 'sent' : 'error'
}
</script>

<template>
  <p v-if="status === 'sent'">Thanks! We got your message and will reply soon.</p>
  <form v-else @submit.prevent="onSubmit">
    <label>
      Name
      <input name="name" type="text" placeholder="Jane Smith" required>
    </label>
    <label>
      Email
      <input name="email" type="email" placeholder="jane@example.com" required>
    </label>
    <label>
      Message
      <textarea name="message" rows="5" placeholder="Tell us about your project" required></textarea>
    </label>
    <input type="text" name="_gotcha" tabindex="-1" autocomplete="off" style="display:none">
    <button type="submit" :disabled="status === 'sending'">Send message</button>
    <p v-if="status === 'error'" role="alert">Something went wrong. Please try again.</p>
  </form>
</template>
```

### Svelte

```svelte
<script>
  const shownAt = Date.now()
  let status = $state('idle')

  async function onsubmit(event) {
    event.preventDefault()
    status = 'sending'
    const data = new FormData(event.currentTarget)
    data.set('_elapsed', String(Date.now() - shownAt))
    const response = await fetch("https://api.nisuform.com/s/YOUR_FORM_KEY", {
      method: 'POST',
      headers: { Accept: 'application/json' },
      body: data,
    })
    status = response.ok ? 'sent' : 'error'
  }
</script>

{#if status === 'sent'}
  <p>Thanks! We got your message and will reply soon.</p>
{:else}
  <form {onsubmit}>
    <label>
      Name
      <input name="name" type="text" placeholder="Jane Smith" required>
    </label>
    <label>
      Email
      <input name="email" type="email" placeholder="jane@example.com" required>
    </label>
    <label>
      Message
      <textarea name="message" rows="5" placeholder="Tell us about your project" required></textarea>
    </label>
    <input type="text" name="_gotcha" tabindex="-1" autocomplete="off" style="display:none">
    <button type="submit" disabled={status === 'sending'}>Send message</button>
    {#if status === 'error'}
      <p role="alert">Something went wrong. Please try again.</p>
    {/if}
  </form>
{/if}
```

### Astro

```astro
---
const endpoint = "https://api.nisuform.com/s/YOUR_FORM_KEY"
---

<form action={endpoint} method="POST">
  <label>
    Name
    <input name="name" type="text" placeholder="Jane Smith" required>
  </label>
  <label>
    Email
    <input name="email" type="email" placeholder="jane@example.com" required>
  </label>
  <label>
    Message
    <textarea name="message" rows="5" placeholder="Tell us about your project" required></textarea>
  </label>
  <input type="text" name="_gotcha" tabindex="-1" autocomplete="off" style="display:none">
  <button type="submit">Send message</button>
</form>
```

## When to use it

- A "Contact us" page on a business or agency website
- An enquiry form on a portfolio or freelancer site
- A get-in-touch section at the bottom of a landing page
- Static sites on GitHub Pages, Netlify or Cloudflare Pages, where there is no server to run PHP

## Customize it

### Add a subject or phone field

Copy one of the field blocks and give it a new `name`, like `subject` or `phone`. Nisuform saves every named field, so the new answer appears in your inbox.

### Send messages to your team

Every submission is emailed to you. On Pro, notifications can also go to other addresses or to Slack, Discord and Telegram.

### Reply automatically

On Pro, turn on auto-replies to email the sender a confirmation with their own answers in it.

### Use your own thank-you page

The styled form shows its success message in place. The unstyled form shows the Nisuform thank-you page, or on Pro, redirects to a page on your site.

## Frequently asked questions

### Does this contact form work without PHP or a server?

Yes. The form posts straight to your Nisuform endpoint, which filters spam, stores the submission and emails you. You do not need PHP, a database or a server, so it works on any static site.

### How do I get contact form submissions by email?

Create a free form in Nisuform, replace YOUR_FORM_KEY in the code with your form key and publish the page. Every new submission is emailed to you within seconds and saved in your inbox.

### Can I use this contact form on GitHub Pages, Webflow or WordPress?

Yes. It is plain HTML, CSS and JavaScript, so it works anywhere you can paste HTML, including GitHub Pages, Netlify, Cloudflare Pages, Webflow embeds, Framer and WordPress custom HTML blocks.

### Is this contact form template free?

Yes. The template is free to copy and change, and the Free plan includes 100 submissions a month and 3 forms with spam filtering and email alerts. No credit card is needed.
