Client Interactions
Fetch HTML from the server and inject it into the page — no JavaScript required. What handles partial loading, SPA navigation, loading states, and confirmations through HTML attributes.
Fetching HTML
Use w-get or w-post to fetch HTML from a URL and inject the response into a target element. The w-target attribute specifies where the content goes.
<button w-get="/w-partial/items" w-target="#list">Load Items</button>
<div id="list"></div>
When the button is clicked, What fetches /w-partial/items via GET and injects the response HTML into the #list element.
Use w-post for POST requests:
<button w-post="/w-action/archive" w-target="#status">Archive</button>
<span id="status"></span>
Targeting Elements
The w-target attribute accepts any CSS selector. The response HTML is injected into the first matching element.
<!-- Target by ID -->
<button w-get="/w-partial/stats" w-target="#stats-panel">Refresh</button>
<!-- Target by class -->
<button w-get="/w-partial/alerts" w-target=".alert-container">Check</button>
w-target attribute is required for w-get and w-post. Without it, the fetch is skipped and a warning is logged to the console.
Swap Modes
The w-swap attribute controls how fetched content is injected into the target. The default is innerHTML, which replaces the target's inner content.
| Mode | Behavior |
|---|---|
innerHTML | Replace the target's inner content (default) |
replace | Same as innerHTML |
outerHTML | Replace the target element entirely (not just its content) |
prepend | Insert before the target's first child (alias: afterbegin) |
append | Insert after the target's last child (alias: beforeend) |
before | Insert before the target element itself (alias: beforebegin) |
after | Insert after the target element itself (alias: afterend) |
none | Fetch but don't inject — useful for side effects |
<!-- Append new items to a list -->
<button w-get="/w-partial/more-items" w-target="#feed" w-swap="append">
Load More
</button>
<div id="feed">
<!-- existing items -->
</div>
<!-- Prepend a notification -->
<button w-post="/w-action/notify" w-target="#notifications" w-swap="prepend">
Send Alert
</button>
Triggers
By default, w-get and w-post fire on click. Use w-trigger to change the triggering event.
| Value | Fires when |
|---|---|
click | Element is clicked (default) |
change | Input value changes |
submit | Form is submitted |
<!-- Filter on dropdown change -->
<select w-get="/w-partial/products" w-target="#product-list" w-trigger="change">
<option value="all">All Categories</option>
<option value="electronics">Electronics</option>
<option value="books">Books</option>
</select>
<div id="product-list"></div>
Sending Parameters
Use w-params to send additional parameters with the request. The value is a JSON object.
<button
w-get="/w-partial/items"
w-target="#list"
w-params='{"page": "2", "sort": "newest"}'
>
Page 2
</button>
For GET requests, parameters are appended as query string values. For POST requests, they are sent as form data.
Including Form Values
Use w-include to send values from a form alongside the request. The value is a CSS selector pointing to a form element.
<form id="search-form">
<input type="text" name="q" placeholder="Search...">
<select name="category">
<option value="all">All</option>
<option value="posts">Posts</option>
</select>
</form>
<button
w-get="/w-partial/search-results"
w-target="#results"
w-include="#search-form"
>
Search
</button>
<div id="results"></div>
All named inputs inside the referenced form are serialized and sent with the request.
SPA Navigation
What automatically intercepts internal links for instant, client-side navigation. All links with href starting with / are boosted by default — no full page reload, with browser history support.
<!-- These navigate via client-side fetch automatically -->
<a href="/about">About</a>
<a href="/blog/my-post">Read Post</a>
The page content is fetched, the body is swapped, styles are updated, and the URL changes in the browser — all without a full reload. The back/forward buttons work as expected.
Opting out
To disable boosting on a specific link, set w-boost="false":
<!-- Force a full page reload -->
<a href="/download/report.pdf" w-boost="false">Download PDF</a>
<!-- External links are never boosted -->
<a href="https://example.com">External Site</a>
Links to other domains, links with target="_blank", and links with download attributes are never intercepted.
Form boosting
Forms with w-boost or actions starting with /w-action are also handled via AJAX. If the server redirects, the client follows the redirect with a client-side navigation.
<form action="/w-action/create-post" method="post" w-boost>
<input type="text" name="title" placeholder="Post title">
<button type="submit" class="btn btn-primary">Create</button>
</form>
Loading States
Use w-loading to add a CSS class to the element while a request is in progress. The class is removed when the response arrives.
<button
w-get="/w-partial/data"
w-target="#output"
w-loading="is-loading"
>
Fetch Data
</button>
If you omit w-loading, the default class w-loading is added automatically. The loading class is applied to both the triggering element and the target element.
/* Dim content while loading */
.is-loading {
opacity: 0.5;
pointer-events: none;
transition: opacity 200ms ease;
}
/* Spinner on buttons */
button.is-loading::after {
content: "";
display: inline-block;
width: 0.875em;
height: 0.875em;
margin-left: 0.5em;
border: 2px solid transparent;
border-top-color: currentColor;
border-radius: 50%;
animation: spin 600ms linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }
For form submissions, the submit button is automatically disabled and receives the btn-loading class while the request is in flight.
btn-loading class in what.css adds a spinner animation to buttons. Use it with w-loading="btn-loading" or let form submissions apply it automatically.
Confirmation Dialogs
Add w-confirm to show a browser confirmation dialog before the request fires. If the user cancels, the action is aborted.
<button
w-post="/w-action/delete-account"
w-target="#result"
w-confirm="Are you sure? This cannot be undone."
class="btn btn-danger"
>
Delete Account
</button>
This works on any element with w-get, w-post, or w-trigger actions.
Combining Attributes
All interaction attributes can be combined on a single element. Here is a complete example showing a search interface:
<form id="filters">
<input type="text" name="q" class="form-input" placeholder="Search...">
<select name="sort" class="form-select">
<option value="recent">Most Recent</option>
<option value="popular">Most Popular</option>
</select>
</form>
<button
w-get="/w-partial/results"
w-target="#results"
w-swap="innerHTML"
w-include="#filters"
w-loading="is-loading"
class="btn btn-primary"
>
Search
</button>
<button
w-get="/w-partial/results"
w-target="#results"
w-swap="append"
w-params='{"page": "2"}'
w-include="#filters"
w-loading="is-loading"
class="btn btn-outline"
>
Load More
</button>
<div id="results"></div>
Attribute Reference
| Attribute | Value | Description |
|---|---|---|
w-get | URL | Fetch HTML via GET |
w-post | URL | Fetch HTML via POST |
w-target | CSS selector | Where to inject the response (default: the element itself) |
w-swap | Mode string | How to inject: innerHTML, replace, prepend, append, before, after, none |
w-trigger | Trigger list | When to fetch: click (default), load, revealed, poll 5s — comma-combinable (see Lazy Loading & Polling) |
w-params | JSON object | Extra parameters to send |
w-include | CSS selector | Include form values from another element |
w-boost | true / false | Enable or disable SPA navigation on a link or form |
w-loading | CSS class | Class added during request (default: w-loading) |
w-confirm | Message string | Show confirmation dialog before action (works on boosted links too) |
w-clipboard / w-clipboard-from | Text / CSS selector | Copy to clipboard on click (see Zero JavaScript) |
w-theme-toggle | — | Toggle dark/light theme, persisted (see Zero JavaScript) |
w-set | Mutation expression | Mutate session/app state on the server (see Sessions) |
w-bind / w-watch | State path | Live-bind and react to wired state (see Wired State) |
Tabs (zero JavaScript)
Tabs are pure CSS — hidden radio inputs drive which panel is visible. No script, no attributes to wire up, and it survives SPA navigation because there's nothing to initialize.
<div class="tab-group">
<div class="tab-list">
<label class="tab"><input type="radio" name="tabs" checked> Overview</label>
<label class="tab"><input type="radio" name="tabs"> Features</label>
<label class="tab"><input type="radio" name="tabs"> Settings</label>
</div>
<div class="tab-panels">
<div class="tab-panel">Overview content</div>
<div class="tab-panel">Features content</div>
<div class="tab-panel">Settings content</div>
</div>
</div>
Each .tab wraps a radio in the same group; the checked radio reveals the matching .tab-panel by position — the first tab maps to the first panel, the second to the second, and so on, so keep the two lists in the same order. Add the tabs-pills modifier to .tab-group for the pill style. See it live on the Navigation demo.
Disclosure & Accordions (zero JavaScript)
Use the native <details> element for collapsible sections. Give several the same name and the browser keeps only one open — a real accordion, no script. The <what-accordion> component wraps this pattern with built-in styling.
<details class="w-accordion" name="faq" open>
<summary class="w-accordion-header">First question</summary>
<div class="w-accordion-content">Answer one.</div>
</details>
<details class="w-accordion" name="faq">
<summary class="w-accordion-header">Second question</summary>
<div class="w-accordion-content">Answer two.</div>
</details>
Copy to clipboard
Declarative, like everything else — the button flashes a w-copied class and can swap its label while copied:
<what-clipboard value="cargo install run-what">Copy</what-clipboard>
<!-- copy from an element: anchor → href, input → value, else text -->
<what-clipboard from="#share-url" copied-label="Copied!">Copy link</what-clipboard>
Live:
Load lazily & poll
Regions that fill themselves — on render, on scroll into view, or on a timer — are one tag: <what-fetch>. See Lazy Loading & Polling.
w-get/w-post. Plain <script> tags remain available as the escape hatch for third-party embeds (see Zero JavaScript).