# Support ticket form template

A free support and bug report form with topic, priority, page link and description. Copy the HTML, CSS and JS, or use it in React, Next.js, Vue, Svelte or Astro.

This form asks for the topic, the priority, the page with the problem and a description. It works as a help desk contact form or as a bug report form for an app.

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

## Fields

| Field | Name | Type | Required |
| --- | --- | --- | --- |
| Name | `name` | Short text | Yes |
| Email | `email` | Email | Yes |
| What do you need help with? | `topic` | Dropdown (Bug report, Billing, Account access, Feature request, Something else) | Yes |
| How urgent is it? | `priority` | Multiple choice (Low, Normal, Urgent) | Yes |
| Page with the problem | `page` | Website | No |
| Describe the problem | `description` | 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-support-ticket-form" class="nf-form" action="https://api.nisuform.com/s/YOUR_FORM_KEY" method="POST">
  <style>
#nf-support-ticket-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-support-ticket-form *, #nf-support-ticket-form *::before, #nf-support-ticket-form *::after { box-sizing: border-box; }
#nf-support-ticket-form [hidden] { display: none !important; }
#nf-support-ticket-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-support-ticket-form .nf-desc { margin: 6px 0 0; font-size: 0.9375em; color: var(--nf-muted); }
#nf-support-ticket-form .nf-fields { display: grid; gap: 18px; margin-top: 22px; }
#nf-support-ticket-form .nf-head + .nf-fields { margin-top: 22px; }
#nf-support-ticket-form > .nf-fields:first-child { margin-top: 0; }
#nf-support-ticket-form .nf-field { margin: 0; padding: 0; border: 0; min-width: 0; }
#nf-support-ticket-form .nf-label { display: block; margin: 0 0 6px; padding: 0; font-size: 0.875em; font-weight: 600; color: var(--nf-text); }
#nf-support-ticket-form .nf-req { color: var(--nf-muted); font-weight: 400; }
#nf-support-ticket-form .nf-help { margin: 6px 0 0; font-size: 0.8125em; color: var(--nf-muted); }
#nf-support-ticket-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-support-ticket-form input.nf-input, #nf-support-ticket-form select.nf-input { height: 2.75em; padding-top: 0; padding-bottom: 0; }
#nf-support-ticket-form textarea.nf-input { min-height: 112px; padding-top: 10px; padding-bottom: 10px; resize: vertical; }
#nf-support-ticket-form .nf-input::placeholder { color: var(--nf-placeholder); opacity: 1; }
#nf-support-ticket-form .nf-input:hover { border-color: color-mix(in srgb, var(--nf-text) 14%, transparent); }
#nf-support-ticket-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-support-ticket-form .nf-input:user-invalid { border-color: var(--nf-error); }
#nf-support-ticket-form .nf-input:user-invalid:focus { box-shadow: 0 0 0 3px color-mix(in srgb, var(--nf-error) 24%, transparent); }
#nf-support-ticket-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-support-ticket-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-support-ticket-form input[type="number"].nf-input { -moz-appearance: textfield; appearance: textfield; }
#nf-support-ticket-form input[type="number"].nf-input::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
#nf-support-ticket-form input[type="number"].nf-input::-webkit-outer-spin-button { -webkit-appearance: none; margin: 0; }
#nf-support-ticket-form input[type="date"].nf-input { min-width: 0; text-align: left; }
#nf-support-ticket-form input[type="date"].nf-input::-webkit-date-and-time-value { text-align: left; }
#nf-support-ticket-form input[type="date"].nf-input.nf-empty:not(:focus) { color: var(--nf-placeholder); }
#nf-support-ticket-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-support-ticket-form input[type="date"].nf-input::-webkit-calendar-picker-indicator:hover { opacity: 1; }
#nf-support-ticket-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-support-ticket-form select.nf-input:has(option[value=""]:checked) { color: var(--nf-placeholder); }
#nf-support-ticket-form select.nf-input option { color: var(--nf-field-text); }
#nf-support-ticket-form .nf-options { display: grid; gap: 8px; }
#nf-support-ticket-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-support-ticket-form .nf-option:hover { border-color: color-mix(in srgb, var(--nf-text) 28%, transparent); }
#nf-support-ticket-form .nf-option:has(input:checked) { border-color: var(--nf-accent); background: color-mix(in srgb, var(--nf-accent) 8%, transparent); }
#nf-support-ticket-form .nf-option span { min-width: 0; overflow-wrap: anywhere; }
#nf-support-ticket-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-support-ticket-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-support-ticket-form .nf-option input:focus-visible { box-shadow: 0 0 0 3px color-mix(in srgb, var(--nf-accent) 24%, transparent); }
#nf-support-ticket-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-support-ticket-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-support-ticket-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-support-ticket-form .nf-check input:focus-visible { box-shadow: 0 0 0 3px color-mix(in srgb, var(--nf-accent) 24%, transparent); }
#nf-support-ticket-form .nf-check input:user-invalid { border-color: var(--nf-error); }
#nf-support-ticket-form .nf-rating { display: flex; flex-wrap: wrap; margin: 0 0 0 -4px; }
#nf-support-ticket-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-support-ticket-form .nf-rating svg { width: 30px; height: 30px; fill: currentColor; stroke: currentColor; stroke-width: 1.5; stroke-linejoin: round; transition: transform 150ms; }
#nf-support-ticket-form .nf-rating input:checked + label, #nf-support-ticket-form .nf-rating label:has(~ input:checked) { color: var(--nf-accent); }
#nf-support-ticket-form .nf-rating:hover input + label { color: var(--nf-control); color: color-mix(in srgb, var(--nf-control) 45%, transparent); }
#nf-support-ticket-form .nf-rating:hover label:hover, #nf-support-ticket-form .nf-rating:hover label:has(~ label:hover) { color: var(--nf-accent); }
#nf-support-ticket-form .nf-rating label:active svg { transform: scale(0.88); }
#nf-support-ticket-form .nf-rating input:focus-visible + label { outline: 2px solid var(--nf-accent); outline-offset: 0; }
#nf-support-ticket-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-support-ticket-form .nf-file-input { position: absolute; top: 0; left: 0; width: 100%; height: 100%; margin: 0; opacity: 0; cursor: pointer; }
#nf-support-ticket-form .nf-file svg { width: 22px; height: 22px; margin-bottom: 4px; }
#nf-support-ticket-form .nf-file-text { margin: 0; max-width: 100%; font-size: 0.875em; overflow-wrap: anywhere; }
#nf-support-ticket-form .nf-file-cta { font-weight: 600; color: var(--nf-text); }
#nf-support-ticket-form .nf-file-types { margin: 0; font-size: 0.75em; color: var(--nf-placeholder); }
#nf-support-ticket-form .nf-file:hover { border-color: var(--nf-control); }
#nf-support-ticket-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-support-ticket-form .nf-file.nf-file-over { border-color: var(--nf-accent); background: color-mix(in srgb, var(--nf-accent) 8%, var(--nf-field)); }
#nf-support-ticket-form .nf-file.nf-file-chosen { border-style: solid; border-color: var(--nf-accent); }
#nf-support-ticket-form .nf-file.nf-file-chosen .nf-file-text { font-weight: 600; color: var(--nf-text); }
#nf-support-ticket-form .nf-file:has(.nf-file-input:user-invalid) { border-color: var(--nf-error); }
#nf-support-ticket-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-support-ticket-form .nf-hp { position: absolute; left: -9999px; width: 1px; height: 1px; overflow: hidden; }
#nf-support-ticket-form .nf-captcha { margin-top: 18px; }
#nf-support-ticket-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-support-ticket-form .nf-submit:hover { filter: brightness(0.94); }
#nf-support-ticket-form .nf-submit:active { transform: scale(0.98); }
#nf-support-ticket-form .nf-submit:focus-visible { outline: 2px solid var(--nf-accent); outline-offset: 3px; }
#nf-support-ticket-form .nf-submit:disabled { opacity: 0.6; cursor: default; }
#nf-support-ticket-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-support-ticket-form.nf-sent { display: flex; flex-direction: column; justify-content: center; min-height: min(var(--nf-height, 0px), 440px); }
#nf-support-ticket-form .nf-success { display: flex; flex-direction: column; align-items: center; padding: 20px 0; text-align: center; outline: none; }
#nf-support-ticket-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-support-ticket-form .nf-success-icon svg { display: block; width: 28px; height: 28px; margin: 0; stroke-dasharray: 1; }
#nf-support-ticket-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-support-ticket-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-support-ticket-form .nf-success-title + .nf-success-text { margin-top: 6px; color: var(--nf-muted); }
#nf-support-ticket-form.nf-sent .nf-success { animation: nf-success-in 400ms cubic-bezier(0.16, 1, 0.3, 1) both; }
#nf-support-ticket-form.nf-sent .nf-success-icon { animation: nf-success-pop 500ms cubic-bezier(0.34, 1.56, 0.64, 1) both; }
#nf-support-ticket-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-support-ticket-form * { transition: none !important; animation: none !important; } }
  </style>
  <div class="nf-head">
    <h2 class="nf-title">Contact support</h2>
    <p class="nf-desc">Tell us what’s going on and we’ll get back to you.</p>
  </div>
  <div class="nf-fields">
    <div class="nf-field">
      <label class="nf-label" for="nf-support-ticket-form-name">Name<span class="nf-req" aria-hidden="true"> *</span></label>
      <input class="nf-input" id="nf-support-ticket-form-name" name="name" type="text" autocomplete="name" placeholder="Jane Smith" required>
    </div>
    <div class="nf-field">
      <label class="nf-label" for="nf-support-ticket-form-email">Email<span class="nf-req" aria-hidden="true"> *</span></label>
      <input class="nf-input" id="nf-support-ticket-form-email" name="email" type="email" autocomplete="email" placeholder="jane@example.com" required>
    </div>
    <div class="nf-field">
      <label class="nf-label" for="nf-support-ticket-form-topic">What do you need help with?<span class="nf-req" aria-hidden="true"> *</span></label>
      <select class="nf-input" id="nf-support-ticket-form-topic" name="topic" required>
        <option value="">Choose a topic</option>
        <option value="Bug report">Bug report</option>
        <option value="Billing">Billing</option>
        <option value="Account access">Account access</option>
        <option value="Feature request">Feature request</option>
        <option value="Something else">Something else</option>
      </select>
    </div>
    <fieldset class="nf-field nf-group">
      <legend class="nf-label">How urgent is it?<span class="nf-req" aria-hidden="true"> *</span></legend>
      <div class="nf-options">
        <label class="nf-option"><input type="radio" name="priority" value="Low" required><span>Low</span></label>
        <label class="nf-option"><input type="radio" name="priority" value="Normal"><span>Normal</span></label>
        <label class="nf-option"><input type="radio" name="priority" value="Urgent"><span>Urgent</span></label>
      </div>
    </fieldset>
    <div class="nf-field">
      <label class="nf-label" for="nf-support-ticket-form-page">Page with the problem</label>
      <input class="nf-input" id="nf-support-ticket-form-page" name="page" type="url" autocomplete="url" placeholder="https://">
    </div>
    <div class="nf-field">
      <label class="nf-label" for="nf-support-ticket-form-description">Describe the problem<span class="nf-req" aria-hidden="true"> *</span></label>
      <textarea class="nf-input" id="nf-support-ticket-form-description" name="description" rows="4" placeholder="What happened, and what did you expect?" 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 request</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">Your request is in and we’ll reply soon.</p>
  </div>
</form>
<script>
(function () {
  var form = document.getElementById("nf-support-ticket-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>
    What do you need help with?
    <select name="topic" required>
      <option value="" disabled selected>Choose one</option>
      <option value="Bug report">Bug report</option>
      <option value="Billing">Billing</option>
      <option value="Account access">Account access</option>
      <option value="Feature request">Feature request</option>
      <option value="Something else">Something else</option>
    </select>
  </label>
  <fieldset>
    <legend>How urgent is it?</legend>
    <label><input type="radio" name="priority" value="Low" required> Low</label>
    <label><input type="radio" name="priority" value="Normal"> Normal</label>
    <label><input type="radio" name="priority" value="Urgent"> Urgent</label>
  </fieldset>
  <label>
    Page with the problem
    <input name="page" type="url" placeholder="https://">
  </label>
  <label>
    Describe the problem
    <textarea name="description" rows="5" placeholder="What happened, and what did you expect?" required></textarea>
  </label>
  <input type="text" name="_gotcha" tabindex="-1" autocomplete="off" style="display:none">
  <button type="submit">Send request</button>
</form>
```

## Framework components

### React

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

export function SupportForm() {
  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! Your request is in and we’ll 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>
        What do you need help with?
        <select name="topic" defaultValue="" required>
          <option value="" disabled>Choose one</option>
          <option value="Bug report">Bug report</option>
          <option value="Billing">Billing</option>
          <option value="Account access">Account access</option>
          <option value="Feature request">Feature request</option>
          <option value="Something else">Something else</option>
        </select>
      </label>
      <fieldset>
        <legend>How urgent is it?</legend>
        <label><input type="radio" name="priority" value="Low" required /> Low</label>
        <label><input type="radio" name="priority" value="Normal" /> Normal</label>
        <label><input type="radio" name="priority" value="Urgent" /> Urgent</label>
      </fieldset>
      <label>
        Page with the problem
        <input name="page" type="url" placeholder="https://" />
      </label>
      <label>
        Describe the problem
        <textarea name="description" rows={5} placeholder="What happened, and what did you expect?" required></textarea>
      </label>
      <input type="text" name="_gotcha" tabIndex={-1} autoComplete="off" style={{ display: 'none' }} />
      <button type="submit" disabled={status === 'sending'}>Send request</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 SupportForm() {
  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! Your request is in and we’ll 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>
        What do you need help with?
        <select name="topic" defaultValue="" required>
          <option value="" disabled>Choose one</option>
          <option value="Bug report">Bug report</option>
          <option value="Billing">Billing</option>
          <option value="Account access">Account access</option>
          <option value="Feature request">Feature request</option>
          <option value="Something else">Something else</option>
        </select>
      </label>
      <fieldset>
        <legend>How urgent is it?</legend>
        <label><input type="radio" name="priority" value="Low" required /> Low</label>
        <label><input type="radio" name="priority" value="Normal" /> Normal</label>
        <label><input type="radio" name="priority" value="Urgent" /> Urgent</label>
      </fieldset>
      <label>
        Page with the problem
        <input name="page" type="url" placeholder="https://" />
      </label>
      <label>
        Describe the problem
        <textarea name="description" rows={5} placeholder="What happened, and what did you expect?" required></textarea>
      </label>
      <input type="text" name="_gotcha" tabIndex={-1} autoComplete="off" style={{ display: 'none' }} />
      <button type="submit" disabled={status === 'sending'}>Send request</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! Your request is in and we’ll 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>
      What do you need help with?
      <select name="topic" required>
        <option value="" disabled selected>Choose one</option>
        <option value="Bug report">Bug report</option>
        <option value="Billing">Billing</option>
        <option value="Account access">Account access</option>
        <option value="Feature request">Feature request</option>
        <option value="Something else">Something else</option>
      </select>
    </label>
    <fieldset>
      <legend>How urgent is it?</legend>
      <label><input type="radio" name="priority" value="Low" required> Low</label>
      <label><input type="radio" name="priority" value="Normal"> Normal</label>
      <label><input type="radio" name="priority" value="Urgent"> Urgent</label>
    </fieldset>
    <label>
      Page with the problem
      <input name="page" type="url" placeholder="https://">
    </label>
    <label>
      Describe the problem
      <textarea name="description" rows="5" placeholder="What happened, and what did you expect?" required></textarea>
    </label>
    <input type="text" name="_gotcha" tabindex="-1" autocomplete="off" style="display:none">
    <button type="submit" :disabled="status === 'sending'">Send request</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! Your request is in and we’ll 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>
      What do you need help with?
      <select name="topic" required>
        <option value="" disabled selected>Choose one</option>
        <option value="Bug report">Bug report</option>
        <option value="Billing">Billing</option>
        <option value="Account access">Account access</option>
        <option value="Feature request">Feature request</option>
        <option value="Something else">Something else</option>
      </select>
    </label>
    <fieldset>
      <legend>How urgent is it?</legend>
      <label><input type="radio" name="priority" value="Low" required> Low</label>
      <label><input type="radio" name="priority" value="Normal"> Normal</label>
      <label><input type="radio" name="priority" value="Urgent"> Urgent</label>
    </fieldset>
    <label>
      Page with the problem
      <input name="page" type="url" placeholder="https://">
    </label>
    <label>
      Describe the problem
      <textarea name="description" rows="5" placeholder="What happened, and what did you expect?" required></textarea>
    </label>
    <input type="text" name="_gotcha" tabindex="-1" autocomplete="off" style="display:none">
    <button type="submit" disabled={status === 'sending'}>Send request</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>
    What do you need help with?
    <select name="topic" required>
      <option value="" disabled selected>Choose one</option>
      <option value="Bug report">Bug report</option>
      <option value="Billing">Billing</option>
      <option value="Account access">Account access</option>
      <option value="Feature request">Feature request</option>
      <option value="Something else">Something else</option>
    </select>
  </label>
  <fieldset>
    <legend>How urgent is it?</legend>
    <label><input type="radio" name="priority" value="Low" required> Low</label>
    <label><input type="radio" name="priority" value="Normal"> Normal</label>
    <label><input type="radio" name="priority" value="Urgent"> Urgent</label>
  </fieldset>
  <label>
    Page with the problem
    <input name="page" type="url" placeholder="https://">
  </label>
  <label>
    Describe the problem
    <textarea name="description" rows="5" placeholder="What happened, and what did you expect?" required></textarea>
  </label>
  <input type="text" name="_gotcha" tabindex="-1" autocomplete="off" style="display:none">
  <button type="submit">Send request</button>
</form>
```

## When to use it

- A help or support page for a SaaS product
- Bug reports from beta testers
- IT and internal help desk requests
- Feature requests from customers

## Customize it

### Route urgent tickets

On Pro, send every ticket to a Slack, Discord or Telegram channel, and use a webhook to open issues in your help desk or bug tracker.

### Fill in the page for them

Nisuform already records the page each submission was sent from, so you can drop the page field and add the app version in a hidden field instead.

### Accept screenshots

On Pro, add a file input so people can attach screenshots and PDFs, up to 5 files per ticket.

## Frequently asked questions

### Can I use this as a bug report form?

Yes. Keep "Bug report" as a topic, or rename the fields to steps to reproduce, expected result and actual result. Every named field is saved.

### Can tickets go to Jira, Linear or GitHub Issues?

On Pro, use a webhook, Zapier or Make to create an issue from each submission. Webhooks are signed, so your endpoint can check that they came from Nisuform.

### Can the person get an automatic reply?

On Pro, turn on auto-replies to send a confirmation with their own answers, so they know the request arrived.
