Skip to content

COMPONENT

Form

The parts of a form that are not controls: the browser's own validation, and a decoy field that catches a bot before you spend a request on it.

Two fields, validated by the browser

Focus a field, leave it blank, and tab away. The error appears only after you have interacted with it β€” never on load. No script is running here.

Leave it blank, tab away, and the browser objects on its own.

Type something that is not an address to see the same treatment.

HTML
<form class="flow" method="post" action="#sent">
  <div class="field">
    <label class="field-label" for="sp-name">NAME <span class="field-required">*</span></label>
    <input class="input" id="sp-name" name="name" type="text" required placeholder="Ada Lovelace" />
    <p class="field-hint">Leave it blank, tab away, and the browser objects on its own.</p>
  </div>

  <div class="field">
    <label class="field-label" for="sp-mail">EMAIL <span class="field-required">*</span></label>
    <input class="input" id="sp-mail" name="email" type="email" required placeholder="[email protected]" />
    <p class="field-hint">Type something that is not an address to see the same treatment.</p>
  </div>

  <input class="honeypot" type="text" name="company" tabindex="-1" aria-hidden="true" autocomplete="off" />

  <button class="btn btn-primary" type="submit">SEND</button>
</form>

Reference

Read from components/form.css when this page was built, so it cannot fall behind the stylesheet.

CLASSES.honeypot
READSNothing from the theme β€” it is arrangement only.

Notes

:user-invalid, NEVER :invalid

:invalid matches from the moment the page loads, so a blank required field is painted red before anybody has typed a character β€” an empty form accusing its reader of a mistake they have not made yet. :user-invalid waits until the person has actually interacted with the field and left it, which is the behaviour every hand-rolled validation script is written to imitate. It needs no script at all.

It shares its rule with aria-invalid, the attribute a server sets, on purpose: the browser's verdict and the server's must look identical, or a reader learns that red means two different things depending on which one got there first. See .field for the states themselves.

THE HONEYPOT MUST HIDE FROM SCREEN READERS TOO

This is the one people get wrong, and it is worth being blunt about. A merely visually hidden decoy is still announced, still focusable, and still labelled something plausible like "Company" β€” so a blind visitor fills it in, and the form silently discards their enquiry with no error and no explanation. A honeypot built that way is a trap for exactly the people least able to work out what happened.

aria-hidden="true", tabindex="-1" and autocomplete="off" are all required, and none of them can come from the stylesheet β€” so the caller owes every one. The last is not pedantry either: a password manager will happily complete a field called "company" that nobody can see, and then a real person is treated as a bot.

WHY NOT DISPLAY: NONE

It is the obvious spelling and the weak one β€” the first thing a scraper checks for, precisely because it is what naive honeypots use. Off-screen and clipped costs the same and looks like a real field to anything that is not rendering the page.

AND THE SERVER DROPS IT SILENTLY

A filled honeypot should be accepted and thrown away, never rejected with an error. A bot that is told it failed simply tries again with the field left blank; one that believes it succeeded goes away. The library styles the field and states no name for it β€” an ordinary-sounding one is the whole point, and honeypot in the markup is a giveaway.