Welcome to Vanilla Breeze
This bell pulls live notifications from /go/notify/messages — the same contract documented at /docs/concepts/service-contracts/. Static articles like this one are the no-JS / no-backend fallback.
This bell pulls live notifications from /go/notify/messages — the same contract documented at /docs/concepts/service-contracts/. Static articles like this one are the no-JS / no-backend fallback.
Button loading state with spinner, aria-busy, double-submit prevention, and auto-revert. Upscales the native disabled attribute for async actions.
The data-loading attribute on a <button> upscales the native disabled state into a full loading experience: a spinner replaces the button text, aria-busy is set for screen readers, and clicks are blocked to prevent double-submit. For submit buttons inside a form, the loading state activates automatically on form submission.
<form> <button type="submit" data-loading="Saving...">Save Changes</button></form>
When activated, the enhancement:
aria-busy="true" and disabled.| Attribute | Value | Description |
|---|---|---|
data-loading |
empty or text | Enable loading enhancement. Optional value is the loading text (e.g. "Saving..."). Empty shows spinner only. |
data-loading-timeout |
number | Auto-revert after N milliseconds. Default: 10000 (10 seconds). |
Submit buttons inside a <form> automatically activate on form submission. When paired with the form coordinator (data-submit="fetch"), the button auto-reverts when the submission completes.
<form action="/api/save" method="post" data-submit="fetch"> <form-field> <label for="name">Name</label> <input type="text" id="name" name="name" required> </form-field> <button type="submit" data-loading="Saving...">Save</button></form>
For native form submissions (which navigate away), the loading state persists until the page navigates — which is the correct behavior.
For buttons outside forms or async actions, control the loading state via the JavaScript API:
import { activate, deactivate } from 'vanilla-breeze/utils/loading-button-init.js'; const btn = document.getElementById('my-btn'); // Start loadingactivate(btn); // After async work completesawait doAsyncWork();deactivate(btn);
You can also toggle via the data-loading-active attribute:
The spinner inherits currentColor, so it works with all button variants:
<button type="submit" data-loading>Primary</button><button type="submit" data-loading class="secondary">Secondary</button><button type="submit" data-loading class="ghost">Ghost</button>
During loading:
aria-busy="true" tells screen readers the button action is in progress.disabled prevents keyboard and pointer activation.aria-hidden="true" so it doesn’t interfere with screen reader output.prefers-reduced-motion.The data-loading attribute serves two purposes depending on the element:
| Element | Visual | Behavior |
|---|---|---|
Container (<section>, <div>) |
Shimmer overlay, skeleton | CSS-only, no JS needed |
Button (<button>) |
Inline spinner, disabled state | JS-enhanced with auto-revert |