Submit Button
A submit button that morphs into a circular spinner during loading, then into a confirmation checkmark — three sequential shape states in CSS and JavaScript, with no page reload.
Updated
This effect is part of Effect.Labs — 840 vanilla effects, some free, some premium. Hover has 48 effects, including 5 free. Explore the category →
3 usage examples



How it works
The .fx-0363-root root (250 px tall, rgb(26, 26, 36) background) centers a .mi-submit-btn button (200×52 px). The button holds three stacked children: .mi-submit-text (the Send label), .mi-submit-loader (spinner container), and .mi-submit-check (checkmark SVG). In CSS, the .loading and .success states on the button drive three simultaneous changes: width (200 px → 52 px), border-radius (14 px → 26 px to form a circle), and each layer’s opacity.
The .mi-submit-spinner is a 24 px div with a border whose quarter is colored and the rest transparent; it rotates via @keyframes miSpinnerRot (0.8 s linear infinite). The checkmark is an SVG polyline whose path goes from 0% to 100% via stroke-dashoffset and @keyframes miCheckDraw (0.3 s). The shape transition lasts 0.4 s with ease-in-out; the checkmark transition starts 100 ms later so the morphing is complete when the check appears.
The script fires on button click, checks the button is not already loading or successful, adds .loading plus aria-busy="true" and aria-disabled="true". After 1 500 ms it removes .loading and adds .success. After another 1 800 ms it removes .success and clears the ARIA attributes. In a real integration, replace the fixed delays with your server call’s response.
Accessibility
- Keyboard: the component is a real
<button>; Tab reaches it, Space or Enter fires it. The loading state disables the button viaaria-disabledto prevent double submissions. - Loading state announced:
aria-busy="true"is added during loading. Screen readers that honor this attribute on a button announce that the action is in progress; test with NVDA and VoiceOver as implementations vary. - Reduced motion: under
prefers-reduced-motion: reduce, the shape morphing (widthandborder-radiuschanges) is removed, the spinner animation stops, and the checkmark draw is instant. Loading and success states remain indicated by color changes and the spinner/checkmark appearing. - Touch: the button responds normally to touch events. Its 200×52 px target is wide and easy to hit with a finger.
Browser compatibility
CSS (transitions on width, border-radius, opacity, spinner and checkmark animations) + 0.5 KB of JavaScript (classList, setAttribute, setTimeout), no dependency. Verified in Chromium 151: 3-state morphing, aria-busy updated, two independent copies, reduced-motion without morphing, 0 errors. The preview captures show the animation auto-replayed by the scene code: this behaviour is not included in the sold code.
Without JavaScript, the button is clickable but does not change state. Without CSS transitions, the three layers appear and disappear instantly with no morphing.
The code
HTML structure to paste into your page (the CSS + JS come with your purchase):
<div class="fx-0363-root">
<div class="mi-submit-scene">
<button class="mi-submit-btn">
<span class="mi-submit-text">Send</span>
<span class="mi-submit-loader">
<div class="mi-submit-spinner"></div>
</span>
<span class="mi-submit-check">
<svg viewBox="0 0 24 24">
<polyline points="20 6 9 17 4 12"></polyline>
</svg>
</span>
</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 |
|---|---|---|
Button color at rest (CSS — background on .mi-submit-btn) |
Violet #6366f1 |
Adapt to your brand’s primary color. The success color (.mi-submit-btn.success) and loading color (.mi-submit-btn.loading) can be changed independently. |
Simulated loading duration (JS — first setTimeout) |
1 500 ms | In a real integration, replace the setTimeout with your network promise resolution: call the transition to .success in the .then() or success callback. |
Button label (HTML — text in .mi-submit-text) |
Send | Replace with Submit, Sign up, Confirm, etc. The morphing to a circle works regardless of label length since the starting width is fixed (200 px). |
Checkmark display duration (JS — second setTimeout) |
1 800 ms | Reduce to 1 000 ms for a quick turnaround; increase to 3 000 ms to give the user time to read a confirmation message shown nearby. |
FAQ
setTimeout calls and fire your request (fetch, XMLHttpRequest). In the success callback, transition to .success; in the error callback, remove .loading and restore the button to its initial state, clearing aria-busy and aria-disabled too..fx-0363-root independently. Each button has its own state cycle. Triggering a submission on one does not affect the others..loading or .success: if so, the click is ignored. Combined with aria-disabled="true", this prevents multiple submissions during processing.