what

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>
Note: The 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.

ModeBehavior
innerHTMLReplace the target's inner content (default)
replaceSame as innerHTML
outerHTMLReplace the target element entirely (not just its content)
prependInsert before the target's first child (alias: afterbegin)
appendInsert after the target's last child (alias: beforeend)
beforeInsert before the target element itself (alias: beforebegin)
afterInsert after the target element itself (alias: afterend)
noneFetch 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.

ValueFires when
clickElement is clicked (default)
changeInput value changes
submitForm 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.

static/styles.css
/* 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.

Tip: The built-in 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:

site/search.html
<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

AttributeValueDescription
w-getURLFetch HTML via GET
w-postURLFetch HTML via POST
w-targetCSS selectorWhere to inject the response (default: the element itself)
w-swapMode stringHow to inject: innerHTML, replace, prepend, append, before, after, none
w-triggerTrigger listWhen to fetch: click (default), load, revealed, poll 5s — comma-combinable (see Lazy Loading & Polling)
w-paramsJSON objectExtra parameters to send
w-includeCSS selectorInclude form values from another element
w-boosttrue / falseEnable or disable SPA navigation on a link or form
w-loadingCSS classClass added during request (default: w-loading)
w-confirmMessage stringShow confirmation dialog before action (works on boosted links too)
w-clipboard / w-clipboard-fromText / CSS selectorCopy to clipboard on click (see Zero JavaScript)
w-theme-toggleToggle dark/light theme, persisted (see Zero JavaScript)
w-setMutation expressionMutate session/app state on the server (see Sessions)
w-bind / w-watchState pathLive-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.

Philosophy: there is no DOM utility library and you shouldn't need one. If behavior isn't covered by a tag or attribute above, that logic probably belongs on the server — render it as HTML and fetch it with w-get/w-post. Plain <script> tags remain available as the escape hatch for third-party embeds (see Zero JavaScript).