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.
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



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), plusautocomplete="email"for the email keyboard on mobile and autofill. - Error tied to the field:
aria-invalid="true"andaria-describedbypointing at the message; that message lives in anaria-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-visiblering), 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.
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):
<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 / property | Default | Effect |
|---|---|---|
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
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.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..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.