Handle errors gracefully, show loading states, and control concurrent requests.
Error Boundaries
Use data-error-target to show a hidden element when a request fails:
<button data-href="/api/save" data-target="#result" data-error-target="#error-panel"> Save</button> <div id="result"></div><div id="error-panel" hidden> Something went wrong. <button data-retry>Try Again</button></div>
When the request fails:
- The error target element is shown (
hiddenattribute removed) data-erroranddata-error-messageattributes are set on the trigger element- An
htmlstar:request-errorevent is dispatched
When a new request starts, the error target is automatically hidden again.
Retry Buttons
Add data-retry to any element inside the error target to create a retry button:
<div id="error" hidden> <p>Failed to load data.</p> <button data-retry>Retry</button></div>
Clicking a data-retry button:
- Hides the error target
- Re-executes the original failed request
Retry uses event delegation, so you can add retry buttons dynamically.
Loading Indicators (Visibility)
Use data-loading-target to show a hidden element during requests:
<button data-href="/api/report" data-target="#report" data-loading-target="#spinner"> Generate Report</button> <div id="spinner" hidden> <img src="/spinner.svg" alt="Loading..."></div> <div id="report"></div>
The loading target's hidden attribute is removed when the request starts and restored when it completes (success or failure).
Loading Indicators (CSS Class)
Use data-loading-class to add a CSS class during requests:
<button data-href="/api/save" data-target="#result" data-loading-class="is-loading"> Save</button>
The class is applied to both the trigger element and the target element during the request. Style it with CSS:
.is-loading { opacity: 0.5; pointer-events: none;}
Combining Error and Loading
Use all three together for a complete UX:
<form action="/api/submit" data-target="#result" data-error-target="#form-error" data-loading-target="#form-spinner" data-loading-class="is-submitting"> <input name="email" required> <button>Submit</button></form> <div id="form-spinner" hidden>Submitting...</div><div id="form-error" hidden> Submission failed. <button data-retry>Retry</button></div><div id="result"></div>
.is-submitting button { opacity: 0.6; cursor: wait;}
Queue Control
Use data-queue to control how concurrent requests are handled:
<!-- Default: abort previous, run latest --><input data-href="/search" data-trigger="input" data-target="#results" data-queue="last"> <!-- Ignore new requests while one is in-flight --><button data-href="/api/submit" data-target="#result" data-queue="first"> Submit</button> <!-- Run all requests concurrently --><div data-href="/api/log" data-trigger="click" data-queue="all"> Log Event</div>
| Mode | Behavior | Use Case |
|---|---|---|
last | Abort previous request, run the new one | Search, filters (default) |
first | Ignore new requests while one is in-flight | Form submission, payments |
all | Run all concurrently | Fire-and-forget, logging |
The default is last, which is ideal for search inputs where only the latest query matters.
Built-in State Attributes
html★ automatically sets these attributes during requests for CSS styling:
/* Style the trigger during loading */[data-loading] { opacity: 0.7;} /* Style on error */[data-error] { border-color: red;} /* Show error message via CSS */[data-error]::after { content: attr(data-error-message); color: red;}
| Attribute | Set On | When |
|---|---|---|
data-loading | Trigger element | Request in progress |
data-error | Trigger + target | Request failed |
data-error-message | Trigger element | Request failed (contains error text) |
These are removed automatically when state changes.
Examples
Dashboard with Loading States
<div data-loading-class="panel-loading"> <button data-href="/api/stats" data-target="#stats" data-loading-target="#stats-spinner" data-error-target="#stats-error"> Refresh Stats </button> <div id="stats-spinner" hidden>Loading...</div> <div id="stats-error" hidden> Failed to load. <button data-retry>Retry</button> </div> <div id="stats"></div></div>
Form Submission with Error Recovery
<form action="/api/contact" data-target="#thank-you" data-error-target="#contact-error" data-loading-target="#contact-loading" data-queue="first"> <input name="name" required> <input name="email" type="email" required> <textarea name="message" required></textarea> <button>Send Message</button></form> <div id="contact-loading" hidden>Sending your message...</div><div id="contact-error" hidden> <p>Could not send your message.</p> <button data-retry>Try Again</button></div><div id="thank-you"></div>
Using data-queue="first" prevents duplicate submissions if the user clicks multiple times.