# Job application form template

A free job application form with role, portfolio link, start date and cover letter. Copy the HTML, CSS and JS, or use it in React, Next.js, Vue or Svelte.

This form collects contact details, the role, a link to a portfolio or LinkedIn profile, an optional start date and a short cover letter.

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

## Fields

| Field | Name | Type | Required |
| --- | --- | --- | --- |
| Full name | `name` | Short text | Yes |
| Email | `email` | Email | Yes |
| Phone | `phone` | Phone | No |
| Position | `role` | Dropdown (Product designer, Frontend engineer, Customer support, Open application) | Yes |
| LinkedIn or portfolio | `portfolio` | Website | Yes |
| Earliest start date | `startDate` | Date | No |
| Why do you want to join? | `letter` | 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-job-application-form" class="nf-form" action="https://api.nisuform.com/s/YOUR_FORM_KEY" method="POST">
  <style>
#nf-job-application-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-job-application-form *, #nf-job-application-form *::before, #nf-job-application-form *::after { box-sizing: border-box; }
#nf-job-application-form [hidden] { display: none !important; }
#nf-job-application-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-job-application-form .nf-desc { margin: 6px 0 0; font-size: 0.9375em; color: var(--nf-muted); }
#nf-job-application-form .nf-fields { display: grid; gap: 18px; margin-top: 22px; }
#nf-job-application-form .nf-head + .nf-fields { margin-top: 22px; }
#nf-job-application-form > .nf-fields:first-child { margin-top: 0; }
#nf-job-application-form .nf-field { margin: 0; padding: 0; border: 0; min-width: 0; }
#nf-job-application-form .nf-label { display: block; margin: 0 0 6px; padding: 0; font-size: 0.875em; font-weight: 600; color: var(--nf-text); }
#nf-job-application-form .nf-req { color: var(--nf-muted); font-weight: 400; }
#nf-job-application-form .nf-help { margin: 6px 0 0; font-size: 0.8125em; color: var(--nf-muted); }
#nf-job-application-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-job-application-form input.nf-input, #nf-job-application-form select.nf-input { height: 2.75em; padding-top: 0; padding-bottom: 0; }
#nf-job-application-form textarea.nf-input { min-height: 112px; padding-top: 10px; padding-bottom: 10px; resize: vertical; }
#nf-job-application-form .nf-input::placeholder { color: var(--nf-placeholder); opacity: 1; }
#nf-job-application-form .nf-input:hover { border-color: color-mix(in srgb, var(--nf-text) 14%, transparent); }
#nf-job-application-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-job-application-form .nf-input:user-invalid { border-color: var(--nf-error); }
#nf-job-application-form .nf-input:user-invalid:focus { box-shadow: 0 0 0 3px color-mix(in srgb, var(--nf-error) 24%, transparent); }
#nf-job-application-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-job-application-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-job-application-form input[type="number"].nf-input { -moz-appearance: textfield; appearance: textfield; }
#nf-job-application-form input[type="number"].nf-input::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
#nf-job-application-form input[type="number"].nf-input::-webkit-outer-spin-button { -webkit-appearance: none; margin: 0; }
#nf-job-application-form input[type="date"].nf-input { min-width: 0; text-align: left; }
#nf-job-application-form input[type="date"].nf-input::-webkit-date-and-time-value { text-align: left; }
#nf-job-application-form input[type="date"].nf-input.nf-empty:not(:focus) { color: var(--nf-placeholder); }
#nf-job-application-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-job-application-form input[type="date"].nf-input::-webkit-calendar-picker-indicator:hover { opacity: 1; }
#nf-job-application-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-job-application-form select.nf-input:has(option[value=""]:checked) { color: var(--nf-placeholder); }
#nf-job-application-form select.nf-input option { color: var(--nf-field-text); }
#nf-job-application-form .nf-options { display: grid; gap: 8px; }
#nf-job-application-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-job-application-form .nf-option:hover { border-color: color-mix(in srgb, var(--nf-text) 28%, transparent); }
#nf-job-application-form .nf-option:has(input:checked) { border-color: var(--nf-accent); background: color-mix(in srgb, var(--nf-accent) 8%, transparent); }
#nf-job-application-form .nf-option span { min-width: 0; overflow-wrap: anywhere; }
#nf-job-application-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-job-application-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-job-application-form .nf-option input:focus-visible { box-shadow: 0 0 0 3px color-mix(in srgb, var(--nf-accent) 24%, transparent); }
#nf-job-application-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-job-application-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-job-application-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-job-application-form .nf-check input:focus-visible { box-shadow: 0 0 0 3px color-mix(in srgb, var(--nf-accent) 24%, transparent); }
#nf-job-application-form .nf-check input:user-invalid { border-color: var(--nf-error); }
#nf-job-application-form .nf-rating { display: flex; flex-wrap: wrap; margin: 0 0 0 -4px; }
#nf-job-application-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-job-application-form .nf-rating svg { width: 30px; height: 30px; fill: currentColor; stroke: currentColor; stroke-width: 1.5; stroke-linejoin: round; transition: transform 150ms; }
#nf-job-application-form .nf-rating input:checked + label, #nf-job-application-form .nf-rating label:has(~ input:checked) { color: var(--nf-accent); }
#nf-job-application-form .nf-rating:hover input + label { color: var(--nf-control); color: color-mix(in srgb, var(--nf-control) 45%, transparent); }
#nf-job-application-form .nf-rating:hover label:hover, #nf-job-application-form .nf-rating:hover label:has(~ label:hover) { color: var(--nf-accent); }
#nf-job-application-form .nf-rating label:active svg { transform: scale(0.88); }
#nf-job-application-form .nf-rating input:focus-visible + label { outline: 2px solid var(--nf-accent); outline-offset: 0; }
#nf-job-application-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-job-application-form .nf-file-input { position: absolute; top: 0; left: 0; width: 100%; height: 100%; margin: 0; opacity: 0; cursor: pointer; }
#nf-job-application-form .nf-file svg { width: 22px; height: 22px; margin-bottom: 4px; }
#nf-job-application-form .nf-file-text { margin: 0; max-width: 100%; font-size: 0.875em; overflow-wrap: anywhere; }
#nf-job-application-form .nf-file-cta { font-weight: 600; color: var(--nf-text); }
#nf-job-application-form .nf-file-types { margin: 0; font-size: 0.75em; color: var(--nf-placeholder); }
#nf-job-application-form .nf-file:hover { border-color: var(--nf-control); }
#nf-job-application-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-job-application-form .nf-file.nf-file-over { border-color: var(--nf-accent); background: color-mix(in srgb, var(--nf-accent) 8%, var(--nf-field)); }
#nf-job-application-form .nf-file.nf-file-chosen { border-style: solid; border-color: var(--nf-accent); }
#nf-job-application-form .nf-file.nf-file-chosen .nf-file-text { font-weight: 600; color: var(--nf-text); }
#nf-job-application-form .nf-file:has(.nf-file-input:user-invalid) { border-color: var(--nf-error); }
#nf-job-application-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-job-application-form .nf-hp { position: absolute; left: -9999px; width: 1px; height: 1px; overflow: hidden; }
#nf-job-application-form .nf-captcha { margin-top: 18px; }
#nf-job-application-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-job-application-form .nf-submit:hover { filter: brightness(0.94); }
#nf-job-application-form .nf-submit:active { transform: scale(0.98); }
#nf-job-application-form .nf-submit:focus-visible { outline: 2px solid var(--nf-accent); outline-offset: 3px; }
#nf-job-application-form .nf-submit:disabled { opacity: 0.6; cursor: default; }
#nf-job-application-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-job-application-form.nf-sent { display: flex; flex-direction: column; justify-content: center; min-height: min(var(--nf-height, 0px), 440px); }
#nf-job-application-form .nf-success { display: flex; flex-direction: column; align-items: center; padding: 20px 0; text-align: center; outline: none; }
#nf-job-application-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-job-application-form .nf-success-icon svg { display: block; width: 28px; height: 28px; margin: 0; stroke-dasharray: 1; }
#nf-job-application-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-job-application-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-job-application-form .nf-success-title + .nf-success-text { margin-top: 6px; color: var(--nf-muted); }
#nf-job-application-form.nf-sent .nf-success { animation: nf-success-in 400ms cubic-bezier(0.16, 1, 0.3, 1) both; }
#nf-job-application-form.nf-sent .nf-success-icon { animation: nf-success-pop 500ms cubic-bezier(0.34, 1.56, 0.64, 1) both; }
#nf-job-application-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-job-application-form * { transition: none !important; animation: none !important; } }
  </style>
  <div class="nf-head">
    <h2 class="nf-title">Apply for a role</h2>
    <p class="nf-desc">Tell us about yourself. We reply to every application.</p>
  </div>
  <div class="nf-fields">
    <div class="nf-field">
      <label class="nf-label" for="nf-job-application-form-name">Full name<span class="nf-req" aria-hidden="true"> *</span></label>
      <input class="nf-input" id="nf-job-application-form-name" name="name" type="text" autocomplete="name" placeholder="Jane Smith" required>
    </div>
    <div class="nf-field">
      <label class="nf-label" for="nf-job-application-form-email">Email<span class="nf-req" aria-hidden="true"> *</span></label>
      <input class="nf-input" id="nf-job-application-form-email" name="email" type="email" autocomplete="email" placeholder="jane@example.com" required>
    </div>
    <div class="nf-field">
      <label class="nf-label" for="nf-job-application-form-phone">Phone</label>
      <input class="nf-input" id="nf-job-application-form-phone" name="phone" type="tel" autocomplete="tel" placeholder="+1 555 0100">
    </div>
    <div class="nf-field">
      <label class="nf-label" for="nf-job-application-form-role">Position<span class="nf-req" aria-hidden="true"> *</span></label>
      <select class="nf-input" id="nf-job-application-form-role" name="role" required>
        <option value="">Choose a role</option>
        <option value="Product designer">Product designer</option>
        <option value="Frontend engineer">Frontend engineer</option>
        <option value="Customer support">Customer support</option>
        <option value="Open application">Open application</option>
      </select>
    </div>
    <div class="nf-field">
      <label class="nf-label" for="nf-job-application-form-portfolio">LinkedIn or portfolio<span class="nf-req" aria-hidden="true"> *</span></label>
      <input class="nf-input" id="nf-job-application-form-portfolio" name="portfolio" type="url" autocomplete="url" placeholder="https://" required>
    </div>
    <div class="nf-field">
      <label class="nf-label" for="nf-job-application-form-startDate">Earliest start date</label>
      <input class="nf-input" id="nf-job-application-form-startDate" name="startDate" type="date">
    </div>
    <div class="nf-field">
      <label class="nf-label" for="nf-job-application-form-letter">Why do you want to join?<span class="nf-req" aria-hidden="true"> *</span></label>
      <textarea class="nf-input" id="nf-job-application-form-letter" name="letter" rows="4" placeholder="A few sentences is plenty" 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 application</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 for applying!</p>
    <p class="nf-success-text">We’ll be in touch.</p>
  </div>
</form>
<script>
(function () {
  var form = document.getElementById("nf-job-application-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')
  Array.prototype.forEach.call(form.querySelectorAll('input[type=date]'), function (input) {
    function sync() { input.classList.toggle('nf-empty', !input.value) }
    input.addEventListener('input', sync)
    input.addEventListener('change', sync)
    sync()
  })
  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>
    Full 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>
    Phone
    <input name="phone" type="tel" placeholder="+1 555 0100">
  </label>
  <label>
    Position
    <select name="role" required>
      <option value="" disabled selected>Choose one</option>
      <option value="Product designer">Product designer</option>
      <option value="Frontend engineer">Frontend engineer</option>
      <option value="Customer support">Customer support</option>
      <option value="Open application">Open application</option>
    </select>
  </label>
  <label>
    LinkedIn or portfolio
    <input name="portfolio" type="url" placeholder="https://" required>
  </label>
  <label>
    Earliest start date
    <input name="startDate" type="date">
  </label>
  <label>
    Why do you want to join?
    <textarea name="letter" rows="5" placeholder="A few sentences is plenty" required></textarea>
  </label>
  <input type="text" name="_gotcha" tabindex="-1" autocomplete="off" style="display:none">
  <button type="submit">Send application</button>
</form>
```

## Framework components

### React

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

export function JobApplicationForm() {
  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 for applying! We’ll be in touch.</p>

  return (
    <form onSubmit={onSubmit}>
      <label>
        Full 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>
        Phone
        <input name="phone" type="tel" placeholder="+1 555 0100" />
      </label>
      <label>
        Position
        <select name="role" defaultValue="" required>
          <option value="" disabled>Choose one</option>
          <option value="Product designer">Product designer</option>
          <option value="Frontend engineer">Frontend engineer</option>
          <option value="Customer support">Customer support</option>
          <option value="Open application">Open application</option>
        </select>
      </label>
      <label>
        LinkedIn or portfolio
        <input name="portfolio" type="url" placeholder="https://" required />
      </label>
      <label>
        Earliest start date
        <input name="startDate" type="date" />
      </label>
      <label>
        Why do you want to join?
        <textarea name="letter" rows={5} placeholder="A few sentences is plenty" required></textarea>
      </label>
      <input type="text" name="_gotcha" tabIndex={-1} autoComplete="off" style={{ display: 'none' }} />
      <button type="submit" disabled={status === 'sending'}>Send application</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 JobApplicationForm() {
  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 for applying! We’ll be in touch.</p>

  return (
    <form onSubmit={onSubmit}>
      <label>
        Full 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>
        Phone
        <input name="phone" type="tel" placeholder="+1 555 0100" />
      </label>
      <label>
        Position
        <select name="role" defaultValue="" required>
          <option value="" disabled>Choose one</option>
          <option value="Product designer">Product designer</option>
          <option value="Frontend engineer">Frontend engineer</option>
          <option value="Customer support">Customer support</option>
          <option value="Open application">Open application</option>
        </select>
      </label>
      <label>
        LinkedIn or portfolio
        <input name="portfolio" type="url" placeholder="https://" required />
      </label>
      <label>
        Earliest start date
        <input name="startDate" type="date" />
      </label>
      <label>
        Why do you want to join?
        <textarea name="letter" rows={5} placeholder="A few sentences is plenty" required></textarea>
      </label>
      <input type="text" name="_gotcha" tabIndex={-1} autoComplete="off" style={{ display: 'none' }} />
      <button type="submit" disabled={status === 'sending'}>Send application</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 for applying! We’ll be in touch.</p>
  <form v-else @submit.prevent="onSubmit">
    <label>
      Full 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>
      Phone
      <input name="phone" type="tel" placeholder="+1 555 0100">
    </label>
    <label>
      Position
      <select name="role" required>
        <option value="" disabled selected>Choose one</option>
        <option value="Product designer">Product designer</option>
        <option value="Frontend engineer">Frontend engineer</option>
        <option value="Customer support">Customer support</option>
        <option value="Open application">Open application</option>
      </select>
    </label>
    <label>
      LinkedIn or portfolio
      <input name="portfolio" type="url" placeholder="https://" required>
    </label>
    <label>
      Earliest start date
      <input name="startDate" type="date">
    </label>
    <label>
      Why do you want to join?
      <textarea name="letter" rows="5" placeholder="A few sentences is plenty" required></textarea>
    </label>
    <input type="text" name="_gotcha" tabindex="-1" autocomplete="off" style="display:none">
    <button type="submit" :disabled="status === 'sending'">Send application</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 for applying! We’ll be in touch.</p>
{:else}
  <form {onsubmit}>
    <label>
      Full 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>
      Phone
      <input name="phone" type="tel" placeholder="+1 555 0100">
    </label>
    <label>
      Position
      <select name="role" required>
        <option value="" disabled selected>Choose one</option>
        <option value="Product designer">Product designer</option>
        <option value="Frontend engineer">Frontend engineer</option>
        <option value="Customer support">Customer support</option>
        <option value="Open application">Open application</option>
      </select>
    </label>
    <label>
      LinkedIn or portfolio
      <input name="portfolio" type="url" placeholder="https://" required>
    </label>
    <label>
      Earliest start date
      <input name="startDate" type="date">
    </label>
    <label>
      Why do you want to join?
      <textarea name="letter" rows="5" placeholder="A few sentences is plenty" required></textarea>
    </label>
    <input type="text" name="_gotcha" tabindex="-1" autocomplete="off" style="display:none">
    <button type="submit" disabled={status === 'sending'}>Send application</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>
    Full 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>
    Phone
    <input name="phone" type="tel" placeholder="+1 555 0100">
  </label>
  <label>
    Position
    <select name="role" required>
      <option value="" disabled selected>Choose one</option>
      <option value="Product designer">Product designer</option>
      <option value="Frontend engineer">Frontend engineer</option>
      <option value="Customer support">Customer support</option>
      <option value="Open application">Open application</option>
    </select>
  </label>
  <label>
    LinkedIn or portfolio
    <input name="portfolio" type="url" placeholder="https://" required>
  </label>
  <label>
    Earliest start date
    <input name="startDate" type="date">
  </label>
  <label>
    Why do you want to join?
    <textarea name="letter" rows="5" placeholder="A few sentences is plenty" required></textarea>
  </label>
  <input type="text" name="_gotcha" tabindex="-1" autocomplete="off" style="display:none">
  <button type="submit">Send application</button>
</form>
```

## When to use it

- A careers or jobs page on a company website
- Internship and graduate programs
- Freelancer and contractor applications
- Volunteer and ambassador programs

## Customize it

### Accept CVs as files

On Pro, turn on file uploads, add `<input type="file" name="resume">` and set `enctype="multipart/form-data"` on the form. Each application can include up to 5 PDFs or images.

### Set the role from the job page

If each job has its own page, replace the position dropdown with `<input type="hidden" name="role" value="Frontend engineer">`.

### Send applications to your hiring channel

On Pro, post each application to Slack or Discord, or send it to your applicant tracking system with a webhook.

## Frequently asked questions

### Can applicants upload a resume?

Yes, on Pro. Turn on file uploads for the form and add a file input. Each submission can include up to 5 files, which are stored privately and shared only through links that expire after 15 minutes.

### Is it safe to collect job applications this way?

Submissions are encrypted in transit and at rest and are only used to run the service. You can turn off IP address storage and delete applications once you no longer need them. A Data Processing Agreement is included with every account.

### Can more than one person get the applications?

On Pro, notifications can go to several email addresses or to a Slack, Discord or Telegram channel, so the whole hiring team sees new applications.
