Forms✨ Premium

Shake Validation

The email field shakes and turns red when the address is malformed; the message stays until it is fixed and the form is not sent.

JSAnimationValidation

Updated

This effect is part of Effect.Labs — 840 vanilla effects, some free, some premium. Forms has 20 effects, including 2 free. Explore the category →

3 usage examples

Receipt by email — the customer-facing screen of a bakery till — Shake Validation example 1

① Receipt by email — the customer-facing screen of a bakery till

WhenAfter paying, the till screen offers to email the receipt. The customer types her address and taps "Send my receipt": without a full domain (camille@courriel), the field shakes and turns red; once ".fr" is added, the border turns green and the screen confirms it was sent.
WhyAt the counter, nobody rereads their address: a typo means a lost receipt and a customer coming back for it. The shake is visible from a distance on a large screen, and the message stays up while they fix it.
SettingsDefault stacked layout inside a <form>, --shkv-width:300px, --shkv-bg:transparent, --shkv-min-height:0, orange button --shkv-btn:#ca4c16, focus --shkv-focus:#f5a35c. In the capture, typing and the click are driven by the scene script.
Inviting a teammate — one row under the team table — Shake Validation example 2

② Inviting a teammate — one row under the team table

WhenThe admin types a colleague's address and presses Enter. ".io" is missing: the field shakes; once fixed it turns green and the person joins the list tagged "Pending".
WhyIn a management tool, inviting is a quick, secondary action: the inline variant fits on one row and the error shows under the field, with no dialog and no reload.
Settingsshkv-inline class on the root (field and button side by side), light theme: --shkv-text:#0f172a, --shkv-field:#fff, --shkv-border:#cbd5e1, --shkv-btn:#4f46e5, --shkv-error:#dc2626, --shkv-ok:#059669, --shkv-font-size:.8125rem. The Enter key is played with form.requestSubmit().
Forgotten password — bottom sheet on a phone — Shake Validation example 3

③ Forgotten password — bottom sheet on a phone

WhenOn mobile, the user enters the account email and taps "Send the link". The incomplete address is refused on the spot; once fixed it passes and the sheet says the link was sent.
WhyOn a small screen, the native bubble quickly hides under the keyboard. The red border and the message stuck to the field stay visible right where the thumb is.
SettingsFull width (--shkv-width:100%) in a sheet about 165 px wide, --shkv-gap:30px so the message never touches the button, dark bluish tones (--shkv-field:#1e2433, --shkv-btn:#0b7cb0, --shkv-error:#f87171). The circle marking the tap belongs to the scene, not to the effect.

How it works

On submit (button click, Enter key, or submission of the <form> around the field), the script tests the address against the regex ^[^\s@]+@[^\s@]+\.[^\s@]+$, then against the field's native rules: required, type="email" and any pattern. A rejected address puts is-invalid and is-shaking on .shkv-group: red border, 0.5 s horizontal shake (@keyframes shkv-shake, 4 px swing) and an "Invalid email" message under the field.

The message no longer vanishes after half a second: it stays until the next keystroke. An accepted address turns the border green (is-valid), also until the next keystroke, so a wrong address typed afterwards does go back to red. Every failed attempt replays the shake from the start.

Inside a form, the button is a real type="submit". Wrong address: sending is blocked and the shake replaces the browser's error bubble. Correct address: the form goes out with the name="email" field. Outside a form, the button and the Enter key run the same check without sending anything.

Each .fx-0284-root is set up on its own: two fields on one page never drive each other, and nothing is declared on window.

Accessibility

  • Real label: "Email address" in a visually hidden <label> (.shkv-sr), plus autocomplete="email" for the email keyboard on mobile and autofill.
  • Error tied to the field: aria-invalid="true" and aria-describedby pointing at the message; that message lives in an aria-live="assertive" region and is read out on every failed attempt, not only the first.
  • Keyboard and touch: Tab reaches the field then the button (:focus-visible ring), Enter runs the check; with a finger, the button behaves as with a click.
  • Reduced motion: under prefers-reduced-motion: reduce, no shake and no sliding message; the red border, the message and the blocked submission remain.
  • Limit: success is only shown by the green border. If your page must confirm the sign-up, display a text after sending.
  • Button contrast: 13 px white text on red #dc2626, 4.8:1, and 6.5:1 on hover over #b91c1c. Measured in Chromium, Firefox and WebKit. If you change --shkv-btn, keep at least 4.5:1 with the white text.

Browser compatibility

Dependency-free JavaScript (about 3 KB) and CSS variables. Measured on 08/11/2026 in Chromium, Firefox and WebKit (Playwright): two independent copies, submission blocked then accepted in a real <form>, Enter key, touch at 390 px, reduced motion, no errors. Versions taken from @mdn/browser-compat-data: the most recent feature used is :focus-visible.

Chrome 86+✓ Full (measured)
Firefox 85+✓ Full (measured)
Safari 15.4+✓ Full (measured in WebKit)
Edge 86+✓ Full (Chromium engine)
iOS Safari 15.4+✓ Not checked on a real device
Android Chrome 86+✓ Full (Chromium engine)

Older browsers (from Chrome 51, Firefox 50, Safari 10, the floor set by NodeList.forEach): everything works except the button's focus ring. Without JavaScript, the field stays a required type="email": the browser shows its own bubble and blocks sending.

The code

HTML structure to paste into your page (the CSS + JS come with your purchase):

index.html — structure
<div class="fx-0284-root">
  <div class="shkv-box">
    <div class="shkv-group">
      <label class="shkv-field">
        <span class="shkv-sr">Email address</span>
        <input class="shkv-input" type="email" name="email" autocomplete="email" placeholder="Enter a valid email" required>
      </label>
      <span class="shkv-msg" aria-live="assertive"><span class="shkv-msg-text" hidden>Invalid email</span></span>
    </div>
    <button class="shkv-btn" type="submit">Submit</button>
  </div>
</div>

One-time payment, no subscription — the code is yours forever

Customize

Options passed to the API or data-* attributes:

Option / propertyDefaultEffect
State colours (CSS — --shkv-error, --shkv-ok, --shkv-focus on .fx-0284-root) #ef4444 · #10b981 · #6366f1 Red for a refused address (border and message), green for an accepted one, outline of the active field.
Button (CSS — --shkv-btn, --shkv-btn-hover, --shkv-btn-font-size) #dc2626 · #b91c1c · .8333rem (about 13 px) Button background, hover background, label size. The label itself is edited in the HTML.
Background, text and field (CSS — --shkv-bg, --shkv-text, --shkv-field, --shkv-border) #0a0a0f · #fff · 5% white · 10% white Light page: --shkv-bg:transparent; --shkv-text:#0f172a; --shkv-field:#fff; --shkv-border:#cbd5e1.
Width (CSS — --shkv-width) 280px Maximum width of the block; it narrows when its container is smaller. 100% to fill the column.
Frame height (CSS — --shkv-min-height) 300px Height of the demo area. Set 0 in a real form.
Typed text size (CSS — --shkv-font-size) 1rem Below 1rem, Safari on iPhone zooms in on focus: keep 1rem for a mobile form.
Field-to-button gap (CSS — --shkv-gap) 16px At 30 px the error message sits under the field without touching the button: handy when the block is narrower than 260 px.
Inline layout (shkv-inline class on .fx-0284-root) absent: button centred under the field Field and button on one row, the button takes the field's height.
Validation rule (JS — EMAIL and field attributes) regex ^[^\s@]+@[^\s@]+\.[^\s@]+$ + required A pattern set on the field (e.g. pattern=".+@mycompany\.com") is checked on top of the regex. The error text is edited in .shkv-msg-text.

FAQ

No. The script listens for submit in the capture phase on the parent form: for a refused address it calls preventDefault() then stopImmediatePropagation(), so your own submit listeners are not called. If you registered one in the capture phase before the script, check event.defaultPrevented or the field's aria-invalid attribute.
Native type="email" validation allows a domain without a dot (internal networks). The effect's regex requires a dot after the at sign to catch the most common slip: a missing ".com" or ".fr". For an intranet, remove EMAIL.test(...) in isValid() and keep input.validity.valid.
The script sets up the .fx-0284-root elements present when it runs. For a block injected later, run it again after inserting it: roots already set up carry data-shkv-ready and are skipped, so nothing is wired twice.