On this page
It's easy to assume every modern form needs a JavaScript framework. It doesn't. Plain HTML forms have worked since the early web, and today's browsers add validation, rich input types and powerful CSS selectors on top. A no-JavaScript form loads instantly, works when scripts fail, and has fewer things to break.
Here's how to build one that validates, looks polished and actually delivers submissions.
What HTML can do on its own
- Send data — the
<form>element posts it to any URL. - Validate it —
required,type,pattern,min/max,minlength/maxlength. - Show pickers and keyboards — dates, colours, files, number pads.
- Suggest values —
<datalist>. - Style states — CSS can react to valid, invalid, checked, focused and filled fields.
The only thing HTML can't do is receive the data. That needs a server — or a form backend service.
Step 1: The form
<form class="nojs" action="https://api.formclick.io/submit" method="POST">
<input type="hidden" name="access_key" value="YOUR_ACCESS_KEY">
<input type="hidden" name="subject" value="New enquiry">
<input type="hidden" name="redirect" value="https://yourwebsite.com/thanks.html">
<input type="checkbox" name="botcheck" class="hp" tabindex="-1" autocomplete="off">
<div class="field">
<label for="name">Name</label>
<input id="name" name="name" autocomplete="name" required minlength="2">
<span class="hint">At least 2 characters</span>
</div>
<div class="field">
<label for="email">Email</label>
<input id="email" name="email" type="email" autocomplete="email" required>
<span class="hint">So we can reply, e.g. name@example.com</span>
</div>
<div class="field">
<label for="website">Website (optional)</label>
<input id="website" name="website" type="url" placeholder="https://">
</div>
<div class="field">
<label for="message">Message</label>
<textarea id="message" name="message" rows="5" required minlength="10"></textarea>
</div>
<button type="submit">Send</button>
</form>
Notice there's no <script> anywhere. The redirect field sends the visitor to your own thank-you page after submitting.
Step 2: Style valid and invalid states with CSS
The trick is :user-invalid — it only matches after the person has interacted with a field, so you don't shout "error!" at an empty form on page load. It's supported in all current major browsers.
.nojs { max-width: 520px; margin: 40px auto; font-family: system-ui, sans-serif; }
.field { margin-bottom: 18px; }
.field label { display: block; font-weight: 600; margin-bottom: 6px; }
.field input, .field textarea {
width: 100%;
padding: 12px 14px;
font: inherit;
border: 1px solid #d4d4e0;
border-radius: 10px;
}
.field input:focus, .field textarea:focus {
outline: none;
border-color: #645cec;
box-shadow: 0 0 0 3px rgba(100, 92, 236, .2);
}
/* Hints are hidden until a field is invalid after interaction */
.hint { display: none; margin-top: 6px; font-size: 13px; color: #dc2626; }
.field :user-invalid { border-color: #dc2626; }
.field :user-invalid ~ .hint { display: block; }
.field :user-valid { border-color: #16a34a; }
button {
width: 100%;
padding: 14px;
font: inherit;
font-weight: 700;
color: #fff;
background: #645cec;
border: 0;
border-radius: 10px;
cursor: pointer;
}
.hp { display: none; }
Step 3: CSS-only interactions
A surprising amount of "interactive" UI needs no script:
Highlight the selected option
.option:has(input:checked) {
border-color: #645cec;
background: #f6f5ff;
}
Reveal a field only when needed
<label><input type="checkbox" name="has_company" class="toggle"> I'm contacting you for a company</label>
<div class="reveal">
<label for="company">Company name</label>
<input id="company" name="company">
</div>
.reveal { display: none; }
label:has(.toggle:checked) + .reveal { display: block; }
Disable the submit button until the form is valid
form:has(:invalid) button[type="submit"] {
opacity: .5;
pointer-events: none;
}
Use this one carefully — people can't see why the button is disabled. Showing errors on submit is usually friendlier.
Collapsible sections with details/summary
<details>
<summary>Add delivery instructions</summary>
<label for="notes">Instructions</label>
<textarea id="notes" name="delivery_notes"></textarea>
</details>
Step 4: Receive the submissions
Here's where most "no-JavaScript" forms hit a wall: without a server, where does the data go? A mailto: action is not the answer — it depends on the visitor having a desktop email app configured, and most don't.
Pointing the form at a form backend solves it. With Formclick, the form above posts directly to https://api.formclick.io/submit; the message is emailed to you, saved in your dashboard, filtered for spam, and the visitor is redirected to your thank-you page. No JavaScript in the browser, no code on a server.
What you give up without JavaScript
- The page reloads (or redirects) on submit rather than showing an inline message.
- Cross-field rules — "end date must be after start date" — can't be checked in the browser. Your backend still checks what matters.
- Drag-and-drop file uploads and multi-step flows need a little script (see How to Create a Multi-Step HTML Form).
A good approach is progressive enhancement: build the form so it works without JavaScript, then optionally add a script — such as formclick.min.js — for inline success messages. If the script fails to load, the form still works.
Summary
- HTML handles fields, validation and submission on its own.
- CSS
:user-invalid,:has()anddetailscover most interactive needs. - A form backend receives the data, so you don't need a server either.
For a full rundown of validation attributes, read HTML Form Attributes Explained, or learn how to submit an HTML form without a backend.



