# Quote request form template

A free request a quote form with service, budget and project details. Copy the HTML, CSS and JS, or use it in React, Next.js, Vue, Svelte or Astro.

This form asks for the service, a budget range and the project details, so you can reply with an estimate. Company and budget are optional.

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

## Fields

| Field | Name | Type | Required |
| --- | --- | --- | --- |
| Name | `name` | Short text | Yes |
| Email | `email` | Email | Yes |
| Company | `company` | Short text | No |
| Service | `service` | Dropdown (Website design, Web development, Branding, Something else) | Yes |
| Budget | `budget` | Dropdown (Under $1,000, $1,000 to $5,000, $5,000 to $20,000, Over $20,000) | No |
| Project details | `details` | 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-quote-request-form" class="nf-form" action="https://api.nisuform.com/s/YOUR_FORM_KEY" method="POST">
  <style>
#nf-quote-request-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-quote-request-form *, #nf-quote-request-form *::before, #nf-quote-request-form *::after { box-sizing: border-box; }
#nf-quote-request-form [hidden] { display: none !important; }
#nf-quote-request-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-quote-request-form .nf-desc { margin: 6px 0 0; font-size: 0.9375em; color: var(--nf-muted); }
#nf-quote-request-form .nf-fields { display: grid; gap: 18px; margin-top: 22px; }
#nf-quote-request-form .nf-head + .nf-fields { margin-top: 22px; }
#nf-quote-request-form > .nf-fields:first-child { margin-top: 0; }
#nf-quote-request-form .nf-field { margin: 0; padding: 0; border: 0; min-width: 0; }
#nf-quote-request-form .nf-label { display: block; margin: 0 0 6px; padding: 0; font-size: 0.875em; font-weight: 600; color: var(--nf-text); }
#nf-quote-request-form .nf-req { color: var(--nf-muted); font-weight: 400; }
#nf-quote-request-form .nf-help { margin: 6px 0 0; font-size: 0.8125em; color: var(--nf-muted); }
#nf-quote-request-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-quote-request-form input.nf-input, #nf-quote-request-form select.nf-input { height: 2.75em; padding-top: 0; padding-bottom: 0; }
#nf-quote-request-form textarea.nf-input { min-height: 112px; padding-top: 10px; padding-bottom: 10px; resize: vertical; }
#nf-quote-request-form .nf-input::placeholder { color: var(--nf-placeholder); opacity: 1; }
#nf-quote-request-form .nf-input:hover { border-color: color-mix(in srgb, var(--nf-text) 14%, transparent); }
#nf-quote-request-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-quote-request-form .nf-input:user-invalid { border-color: var(--nf-error); }
#nf-quote-request-form .nf-input:user-invalid:focus { box-shadow: 0 0 0 3px color-mix(in srgb, var(--nf-error) 24%, transparent); }
#nf-quote-request-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-quote-request-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-quote-request-form input[type="number"].nf-input { -moz-appearance: textfield; appearance: textfield; }
#nf-quote-request-form input[type="number"].nf-input::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
#nf-quote-request-form input[type="number"].nf-input::-webkit-outer-spin-button { -webkit-appearance: none; margin: 0; }
#nf-quote-request-form input[type="date"].nf-input { min-width: 0; text-align: left; }
#nf-quote-request-form input[type="date"].nf-input::-webkit-date-and-time-value { text-align: left; }
#nf-quote-request-form input[type="date"].nf-input.nf-empty:not(:focus) { color: var(--nf-placeholder); }
#nf-quote-request-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-quote-request-form input[type="date"].nf-input::-webkit-calendar-picker-indicator:hover { opacity: 1; }
#nf-quote-request-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-quote-request-form select.nf-input:has(option[value=""]:checked) { color: var(--nf-placeholder); }
#nf-quote-request-form select.nf-input option { color: var(--nf-field-text); }
#nf-quote-request-form .nf-options { display: grid; gap: 8px; }
#nf-quote-request-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-quote-request-form .nf-option:hover { border-color: color-mix(in srgb, var(--nf-text) 28%, transparent); }
#nf-quote-request-form .nf-option:has(input:checked) { border-color: var(--nf-accent); background: color-mix(in srgb, var(--nf-accent) 8%, transparent); }
#nf-quote-request-form .nf-option span { min-width: 0; overflow-wrap: anywhere; }
#nf-quote-request-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-quote-request-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-quote-request-form .nf-option input:focus-visible { box-shadow: 0 0 0 3px color-mix(in srgb, var(--nf-accent) 24%, transparent); }
#nf-quote-request-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-quote-request-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-quote-request-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-quote-request-form .nf-check input:focus-visible { box-shadow: 0 0 0 3px color-mix(in srgb, var(--nf-accent) 24%, transparent); }
#nf-quote-request-form .nf-check input:user-invalid { border-color: var(--nf-error); }
#nf-quote-request-form .nf-rating { display: flex; flex-wrap: wrap; margin: 0 0 0 -4px; }
#nf-quote-request-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-quote-request-form .nf-rating svg { width: 30px; height: 30px; fill: currentColor; stroke: currentColor; stroke-width: 1.5; stroke-linejoin: round; transition: transform 150ms; }
#nf-quote-request-form .nf-rating input:checked + label, #nf-quote-request-form .nf-rating label:has(~ input:checked) { color: var(--nf-accent); }
#nf-quote-request-form .nf-rating:hover input + label { color: var(--nf-control); color: color-mix(in srgb, var(--nf-control) 45%, transparent); }
#nf-quote-request-form .nf-rating:hover label:hover, #nf-quote-request-form .nf-rating:hover label:has(~ label:hover) { color: var(--nf-accent); }
#nf-quote-request-form .nf-rating label:active svg { transform: scale(0.88); }
#nf-quote-request-form .nf-rating input:focus-visible + label { outline: 2px solid var(--nf-accent); outline-offset: 0; }
#nf-quote-request-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-quote-request-form .nf-file-input { position: absolute; top: 0; left: 0; width: 100%; height: 100%; margin: 0; opacity: 0; cursor: pointer; }
#nf-quote-request-form .nf-file svg { width: 22px; height: 22px; margin-bottom: 4px; }
#nf-quote-request-form .nf-file-text { margin: 0; max-width: 100%; font-size: 0.875em; overflow-wrap: anywhere; }
#nf-quote-request-form .nf-file-cta { font-weight: 600; color: var(--nf-text); }
#nf-quote-request-form .nf-file-types { margin: 0; font-size: 0.75em; color: var(--nf-placeholder); }
#nf-quote-request-form .nf-file:hover { border-color: var(--nf-control); }
#nf-quote-request-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-quote-request-form .nf-file.nf-file-over { border-color: var(--nf-accent); background: color-mix(in srgb, var(--nf-accent) 8%, var(--nf-field)); }
#nf-quote-request-form .nf-file.nf-file-chosen { border-style: solid; border-color: var(--nf-accent); }
#nf-quote-request-form .nf-file.nf-file-chosen .nf-file-text { font-weight: 600; color: var(--nf-text); }
#nf-quote-request-form .nf-file:has(.nf-file-input:user-invalid) { border-color: var(--nf-error); }
#nf-quote-request-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-quote-request-form .nf-hp { position: absolute; left: -9999px; width: 1px; height: 1px; overflow: hidden; }
#nf-quote-request-form .nf-captcha { margin-top: 18px; }
#nf-quote-request-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-quote-request-form .nf-submit:hover { filter: brightness(0.94); }
#nf-quote-request-form .nf-submit:active { transform: scale(0.98); }
#nf-quote-request-form .nf-submit:focus-visible { outline: 2px solid var(--nf-accent); outline-offset: 3px; }
#nf-quote-request-form .nf-submit:disabled { opacity: 0.6; cursor: default; }
#nf-quote-request-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-quote-request-form.nf-sent { display: flex; flex-direction: column; justify-content: center; min-height: min(var(--nf-height, 0px), 440px); }
#nf-quote-request-form .nf-success { display: flex; flex-direction: column; align-items: center; padding: 20px 0; text-align: center; outline: none; }
#nf-quote-request-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-quote-request-form .nf-success-icon svg { display: block; width: 28px; height: 28px; margin: 0; stroke-dasharray: 1; }
#nf-quote-request-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-quote-request-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-quote-request-form .nf-success-title + .nf-success-text { margin-top: 6px; color: var(--nf-muted); }
#nf-quote-request-form.nf-sent .nf-success { animation: nf-success-in 400ms cubic-bezier(0.16, 1, 0.3, 1) both; }
#nf-quote-request-form.nf-sent .nf-success-icon { animation: nf-success-pop 500ms cubic-bezier(0.34, 1.56, 0.64, 1) both; }
#nf-quote-request-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-quote-request-form * { transition: none !important; animation: none !important; } }
  </style>
  <div class="nf-head">
    <h2 class="nf-title">Request a quote</h2>
    <p class="nf-desc">Tell us about your project and we’ll send an estimate within two business days.</p>
  </div>
  <div class="nf-fields">
    <div class="nf-field">
      <label class="nf-label" for="nf-quote-request-form-name">Name<span class="nf-req" aria-hidden="true"> *</span></label>
      <input class="nf-input" id="nf-quote-request-form-name" name="name" type="text" autocomplete="name" placeholder="Jane Smith" required>
    </div>
    <div class="nf-field">
      <label class="nf-label" for="nf-quote-request-form-email">Email<span class="nf-req" aria-hidden="true"> *</span></label>
      <input class="nf-input" id="nf-quote-request-form-email" name="email" type="email" autocomplete="email" placeholder="jane@example.com" required>
    </div>
    <div class="nf-field">
      <label class="nf-label" for="nf-quote-request-form-company">Company</label>
      <input class="nf-input" id="nf-quote-request-form-company" name="company" type="text" placeholder="Acme Inc.">
    </div>
    <div class="nf-field">
      <label class="nf-label" for="nf-quote-request-form-service">Service<span class="nf-req" aria-hidden="true"> *</span></label>
      <select class="nf-input" id="nf-quote-request-form-service" name="service" required>
        <option value="">Choose a service</option>
        <option value="Website design">Website design</option>
        <option value="Web development">Web development</option>
        <option value="Branding">Branding</option>
        <option value="Something else">Something else</option>
      </select>
    </div>
    <div class="nf-field">
      <label class="nf-label" for="nf-quote-request-form-budget">Budget</label>
      <select class="nf-input" id="nf-quote-request-form-budget" name="budget">
        <option value="">Choose a range</option>
        <option value="Under $1,000">Under $1,000</option>
        <option value="$1,000 to $5,000">$1,000 to $5,000</option>
        <option value="$5,000 to $20,000">$5,000 to $20,000</option>
        <option value="Over $20,000">Over $20,000</option>
      </select>
    </div>
    <div class="nf-field">
      <label class="nf-label" for="nf-quote-request-form-details">Project details<span class="nf-req" aria-hidden="true"> *</span></label>
      <textarea class="nf-input" id="nf-quote-request-form-details" name="details" rows="4" placeholder="What do you need, and by when?" 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">Request a quote</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’ll send your quote soon.</p>
  </div>
</form>
<script>
(function () {
  var form = document.getElementById("nf-quote-request-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>
    Company
    <input name="company" type="text" placeholder="Acme Inc.">
  </label>
  <label>
    Service
    <select name="service" required>
      <option value="" disabled selected>Choose one</option>
      <option value="Website design">Website design</option>
      <option value="Web development">Web development</option>
      <option value="Branding">Branding</option>
      <option value="Something else">Something else</option>
    </select>
  </label>
  <label>
    Budget
    <select name="budget">
      <option value="" disabled selected>Choose one</option>
      <option value="Under $1,000">Under $1,000</option>
      <option value="$1,000 to $5,000">$1,000 to $5,000</option>
      <option value="$5,000 to $20,000">$5,000 to $20,000</option>
      <option value="Over $20,000">Over $20,000</option>
    </select>
  </label>
  <label>
    Project details
    <textarea name="details" rows="5" placeholder="What do you need, and by when?" required></textarea>
  </label>
  <input type="text" name="_gotcha" tabindex="-1" autocomplete="off" style="display:none">
  <button type="submit">Request a quote</button>
</form>
```

## Framework components

### React

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

export function QuoteRequestForm() {
  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’ll send your quote 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>
        Company
        <input name="company" type="text" placeholder="Acme Inc." />
      </label>
      <label>
        Service
        <select name="service" defaultValue="" required>
          <option value="" disabled>Choose one</option>
          <option value="Website design">Website design</option>
          <option value="Web development">Web development</option>
          <option value="Branding">Branding</option>
          <option value="Something else">Something else</option>
        </select>
      </label>
      <label>
        Budget
        <select name="budget" defaultValue="">
          <option value="" disabled>Choose one</option>
          <option value="Under $1,000">Under $1,000</option>
          <option value="$1,000 to $5,000">$1,000 to $5,000</option>
          <option value="$5,000 to $20,000">$5,000 to $20,000</option>
          <option value="Over $20,000">Over $20,000</option>
        </select>
      </label>
      <label>
        Project details
        <textarea name="details" rows={5} placeholder="What do you need, and by when?" required></textarea>
      </label>
      <input type="text" name="_gotcha" tabIndex={-1} autoComplete="off" style={{ display: 'none' }} />
      <button type="submit" disabled={status === 'sending'}>Request a quote</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 QuoteRequestForm() {
  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’ll send your quote 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>
        Company
        <input name="company" type="text" placeholder="Acme Inc." />
      </label>
      <label>
        Service
        <select name="service" defaultValue="" required>
          <option value="" disabled>Choose one</option>
          <option value="Website design">Website design</option>
          <option value="Web development">Web development</option>
          <option value="Branding">Branding</option>
          <option value="Something else">Something else</option>
        </select>
      </label>
      <label>
        Budget
        <select name="budget" defaultValue="">
          <option value="" disabled>Choose one</option>
          <option value="Under $1,000">Under $1,000</option>
          <option value="$1,000 to $5,000">$1,000 to $5,000</option>
          <option value="$5,000 to $20,000">$5,000 to $20,000</option>
          <option value="Over $20,000">Over $20,000</option>
        </select>
      </label>
      <label>
        Project details
        <textarea name="details" rows={5} placeholder="What do you need, and by when?" required></textarea>
      </label>
      <input type="text" name="_gotcha" tabIndex={-1} autoComplete="off" style={{ display: 'none' }} />
      <button type="submit" disabled={status === 'sending'}>Request a quote</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’ll send your quote 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>
      Company
      <input name="company" type="text" placeholder="Acme Inc.">
    </label>
    <label>
      Service
      <select name="service" required>
        <option value="" disabled selected>Choose one</option>
        <option value="Website design">Website design</option>
        <option value="Web development">Web development</option>
        <option value="Branding">Branding</option>
        <option value="Something else">Something else</option>
      </select>
    </label>
    <label>
      Budget
      <select name="budget">
        <option value="" disabled selected>Choose one</option>
        <option value="Under $1,000">Under $1,000</option>
        <option value="$1,000 to $5,000">$1,000 to $5,000</option>
        <option value="$5,000 to $20,000">$5,000 to $20,000</option>
        <option value="Over $20,000">Over $20,000</option>
      </select>
    </label>
    <label>
      Project details
      <textarea name="details" rows="5" placeholder="What do you need, and by when?" required></textarea>
    </label>
    <input type="text" name="_gotcha" tabindex="-1" autocomplete="off" style="display:none">
    <button type="submit" :disabled="status === 'sending'">Request a quote</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’ll send your quote 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>
      Company
      <input name="company" type="text" placeholder="Acme Inc.">
    </label>
    <label>
      Service
      <select name="service" required>
        <option value="" disabled selected>Choose one</option>
        <option value="Website design">Website design</option>
        <option value="Web development">Web development</option>
        <option value="Branding">Branding</option>
        <option value="Something else">Something else</option>
      </select>
    </label>
    <label>
      Budget
      <select name="budget">
        <option value="" disabled selected>Choose one</option>
        <option value="Under $1,000">Under $1,000</option>
        <option value="$1,000 to $5,000">$1,000 to $5,000</option>
        <option value="$5,000 to $20,000">$5,000 to $20,000</option>
        <option value="Over $20,000">Over $20,000</option>
      </select>
    </label>
    <label>
      Project details
      <textarea name="details" rows="5" placeholder="What do you need, and by when?" required></textarea>
    </label>
    <input type="text" name="_gotcha" tabindex="-1" autocomplete="off" style="display:none">
    <button type="submit" disabled={status === 'sending'}>Request a quote</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>
    Company
    <input name="company" type="text" placeholder="Acme Inc.">
  </label>
  <label>
    Service
    <select name="service" required>
      <option value="" disabled selected>Choose one</option>
      <option value="Website design">Website design</option>
      <option value="Web development">Web development</option>
      <option value="Branding">Branding</option>
      <option value="Something else">Something else</option>
    </select>
  </label>
  <label>
    Budget
    <select name="budget">
      <option value="" disabled selected>Choose one</option>
      <option value="Under $1,000">Under $1,000</option>
      <option value="$1,000 to $5,000">$1,000 to $5,000</option>
      <option value="$5,000 to $20,000">$5,000 to $20,000</option>
      <option value="Over $20,000">Over $20,000</option>
    </select>
  </label>
  <label>
    Project details
    <textarea name="details" rows="5" placeholder="What do you need, and by when?" required></textarea>
  </label>
  <input type="text" name="_gotcha" tabindex="-1" autocomplete="off" style="display:none">
  <button type="submit">Request a quote</button>
</form>
```

## When to use it

- Agencies and freelancers selling design, development or marketing
- Contractors, cleaners, movers and other local services
- B2B products with custom pricing
- Print shops, caterers and event suppliers

## Customize it

### List your own services and prices

Edit the `<option>` elements to match what you sell and how you price it. Answers are saved as text, exactly as written in the option.

### Reply faster

On Pro, send each new request to Slack, Discord or Telegram, so your team sees it right away.

### Track where leads come from

Nisuform records the page each request was sent from. Add a hidden field like `campaign` to tell ads and landing pages apart.

## Frequently asked questions

### What should a request a quote form include?

Contact details, the service the person needs, a budget range and a description of the job.

### Can I get quote requests in Slack or my CRM?

On Pro, every request can go to Slack, Discord or Telegram, and to most CRMs through webhooks, Zapier or Make.

### Can people attach files like briefs or floor plans?

On Pro, add a file input to accept PDFs and images. Each submission can include up to 5 files.
