A lot has changed for everyone who builds software since our last release. Many of us now write code together with AI agents.
This release makes Unpoly work better for this new way of building software. A completely rewritten documentation site Show archive.org snapshot helps humans and agents learn Unpoly best practices and look up API details. It is available as HTML, as Markdown and as an offline skill. A new contributor guide helps both humans and agents contribute to the project. We believe that hypermedia-driven apps (HDAs) fit this way of working well. They need less code, fewer dependencies and fewer moving parts, which makes them straightforward to understand and change for humans and agents alike.
We also made some changes to Unpoly itself. A major new feature is the ability to implement custom form fields as web components ("form-associated custom elements"). We fixed a ton of minor bugs and inconsistencies that had accumulated over Unpoly's 10+ years, and meaningfully improved our test coverage.
Changes that may need your attention are marked with 🧩 when
unpoly-migrate.js
Show archive.org snapshot
polyfills them and logs where you use the old API, with ⚠️ when they are not polyfilled but rare or minor, and with ❌ when you should check your code in any case.
A relaunched documentation site
You can now learn Unpoly on unpoly.com from start to finish, and look up details when you need them.
- The new Learn Show archive.org snapshot area covers all of Unpoly with best practices and examples, for beginners and pros alike.
- Getting started Show archive.org snapshot is a new guide that takes you from how Unpoly works to your first links, forms, overlays and compilers.
- We checked the code examples against the source code and fixed the ones that did not work. We split long guides into focused pages, merged guides that overlapped, and added pages for missing topics.
- The new search looks through the full text of every page. It ranks the feature you most likely mean first and favors matches in titles. Each result shows whether it is a guide, an attribute, a function or an event, and which chapter or module it belongs to. It also lists the sections that matched, so you can jump to the right heading. Press
/to search from any page. Search also works on phones. - The API reference Show archive.org snapshot is now separate from the Learn guides. Every API page links to the guide that explains its topic.
Old URLs redirect to the new pages, so your links and bookmarks keep working.
Docs for coding agents
An
agent skill
Show archive.org snapshot
gives your coding agent the same documentation as you: every guide and the full API reference, with a search script that works offline.
With it, your agent follows Unpoly's common patterns for links, forms and overlays, and can look up the exact attributes, options and edge cases.
The skill also contains the release notes of every version, for when your agent upgrades an app.
Install the skill with the skills CLI Show archive.org snapshot :
npx skills add --global https://unpoly.com
If you use Claude Code, you can also install the skill as a plugin Show archive.org snapshot .
Agents can also read the docs without the skill:
- Every page is available as Markdown. Append
.mdto its URL, e.g.https://unpoly.com/api/up.render.mdShow archive.org snapshot . Agents that ask fortext/markdownfirst in theirAcceptheader get Markdown at the page's own URL. -
/llms.txtShow archive.org snapshot lists all guides and API modules, with links an agent can follow. - To give a chat a single page, use the copy button next to a page's title. It copies the page as Markdown.
Upgrades without human help
Coding agents can now upgrade Unpoly without human help. Give your agent the Unpoly skill Show archive.org snapshot and ask it to upgrade your app. The skill contains the upgrade guide Show archive.org snapshot and the release notes of every version.
To make sure this runs smoothly, we improved the release notes and unpoly-migrate.js, so your agent finds the changes your app needs. See
unpoly-migrate.js
Show archive.org snapshot
for the details.
A guide for contributors
If you want to change Unpoly itself, start with
CONTRIBUTING.md
Show archive.org snapshot
.
It links 13 guides, written for people and for coding agents. They explain how to set up a development environment, where code lives, and how we test, document and release Unpoly. Two deep-dives explain the rendering core and the selector engine.
A walkthrough takes you through a first change: a new [up-*] attribute, its spec, its documentation and its commit.
Coding agents find the guide on their own, because AGENTS.md and CLAUDE.md point to the same file.
The guides come with new tools:
-
bin/devstarts a build watcher, a test server and the documentation site with one command. -
bin/testruns the specs in a real browser from the terminal, andbin/find-specfinds the specs relevant to your change, so you don't need to run the whole suite. - A scratch server shows your change on a few real pages, for things a spec cannot show, like how an overlay looks.
Custom form fields
Custom controls like date pickers or tag editors can now take part in Unpoly forms like a native <input>.
Unpoly now builds request params with the browser's own form-data algorithm, so a
form-associated custom element
Show archive.org snapshot
is submitted without any configuration.
When it also has a value property, Unpoly can
watch
Show archive.org snapshot
,
validate
Show archive.org snapshot
and
switch
Show archive.org snapshot
it like any other field, and
[up-disable]
Show archive.org snapshot
disables it while the form is submitting.
Here is a minimal date picker that reports its value to the form:
class DatePicker extends HTMLElement {
static formAssociated = true // mark: formAssociated
#internals = this.attachInternals()
#value = ''
get value() {
return this.#value
}
set value(newValue) {
this.#value = newValue
this.#internals.setFormValue(newValue) // mark: setFormValue
}
// Called when the element is inserted into the page
connectedCallback() {
// Render a calendar with a button for each day
this.innerHTML = `
<button type="button" data-day="2026-11-01">1</button>
<button type="button" data-day="2026-11-02">2</button>
…
`
this.addEventListener('click', (event) => {
let dayButton = event.target.closest('[data-day]')
if (dayButton) this.onDayPicked(dayButton.dataset.day)
})
}
// Called when the user clicks a day
onDayPicked(day) {
this.value = day
this.dispatchEvent(new Event('change', { bubbles: true }))
}
}
customElements.define('date-picker', DatePicker)
You can use it in a form like a native field, including with
[up-validate]
Show archive.org snapshot
:
<form method="post" action="/events" up-submit up-validate>
<date-picker name="starts_at"></date-picker> <!-- mark: date-picker -->
<input type="text" name="title">
</form>
An element that is defined after the form was rendered, e.g. by a lazily loaded component, is picked up as soon as it is defined.
Values appended by a
formdata
Show archive.org snapshot
listener are now sent as well. See
Adding values with a formdata listener
Show archive.org snapshot
.
The new guide
Custom form fields
Show archive.org snapshot
compares four ways to build a custom control, from a hidden native field to a plain <div> that you register as a field.
Thanks to @kmmbvnr for contributing form-associated custom elements and formdata support.
Quality-of-life improvements
Some smaller changes in this release remove friction you may have worked around:
- To reload an element after an overlay closes, set the new experimental
[up-on-accepted-reload]Show archive.org snapshot attribute to its selector. You no longer need a JavaScript callback or a CSP nonce. See Overlays Show archive.org snapshot . - When Unpoly boots, it logs an error for a misspelled or renamed
up.*.configsetting, instead of ignoring it. See Mistakes are easier to find Show archive.org snapshot . - Pages with many watched forms no longer freeze for a moment after each fragment update. See Forms Show archive.org snapshot .
- Test runners, CI and coding agents now read Unpoly's log as plain text. See Mistakes are easier to find Show archive.org snapshot .
Mistakes are easier to find
When something is wrong, Unpoly now says so earlier and more clearly.
This also helps coding agents: clear errors and plain-text logs in automated browsers let an agent notice a mistake and fix it on its own.
- ⚠️ When Unpoly boots, it now logs an error for every setting that doesn't exist, like a misspelled key or one that was renamed in an earlier version. It checks the top-level keys of every
up.*.configobject, but not nested objects likeup.layer.config.modalShow archive.org snapshot , and only once, right after booting. If your tests fail on console errors, fix or remove these settings. - ⚠️ In a browser controlled by automation software, like Selenium, Playwright or Puppeteer, Unpoly now logs plain text without CSS formatting. Test runners, CI and coding agents that read the browser console get readable lines instead of
%cplaceholders and style strings. Normal browsers still format the log. To always format, setup.log.config.format = trueShow archive.org snapshot . - Error messages, warnings and
unformatted log output
Show archive.org snapshot
now print objects more readably. A plain object prints as
{ foo: "bar" }, a request asup.Request(GET /users/5)Show archive.org snapshot and an error asup.CannotMatch(message). A circular reference prints(circular)where the cycle closes, instead of replacing the whole value. - Fix: When an app's bundler minified
unpoly.js, errors could lose their names. For example, anup.CannotMatcherror was namedup.. Errors now keep their names under any minifier.
Enlarged click areas
A click on an
[up-expand]
Show archive.org snapshot
area now does exactly what a click on its link does:
- When Unpoly follows the link, it follows the link itself. The link's attributes, events and
origin
Show archive.org snapshot
all apply, including your own event listeners on the link and Rails'
[data-method]and[data-confirm]. - A link with
[download]downloads its file, and a link with[target=_blank]opens a new tab. - A link that Unpoly doesn't follow makes a full page load, as it would when clicked directly.
- After an overlay opened from the area closes, focus goes back to the link.
Fields in an expanded area keep handling their own clicks, and buttons, labels and <summary> elements now do too, instead of following the link.
Many apps we looked at had worked around the old limitations. You can now remove these workarounds. See Custom links and buttons Show archive.org snapshot for examples.
Form submissions now match the browser
Unpoly now uses the browser's form-data algorithm, so edge cases behave like a native form submission.
These changes cannot be polyfilled. If your server or your tests depend on the exact params of a submission, review them:
- ⚠️ A form with an
<input type="file">is now submitted asmultipart/form-data, even when no file is selected. - ⚠️ An
<input type="image">used as the submit button now sends itsname.xandname.ycoordinates instead of its[value]. - ⚠️ A
<button type="reset">or<input type="reset">with a[name]no longer sends a param. It is no longer watched or validated, but[up-disable]Show archive.org snapshot still disables it. - ⚠️ A field inside a
<fieldset disabled>is no longer submitted or validated. This includes custom elements inup.form.config.fieldSelectorsShow archive.org snapshot . Watchers Show archive.org snapshot and[up-switch]Show archive.org snapshot still see the field. - ⚠️ A
[form]attribute now takes precedence over a surrounding<form>, like in the browser. A field or button within one form that is associated with another form no longer belongs to the surrounding form. It is no longer watched, validated or disabled Show archive.org snapshot with that form, and it is no longer that form's default submit button. - A form-associated custom element is only submitted when it calls
setFormValue(), even when it is listed inup.form.config.fieldSelectorsShow archive.org snapshot . - ⚠️ Params now arrive in the order the browser produces them: the form's fields in document order, then values from
formdatalisteners, then custom elements registered inup.form.config.fieldSelectorsShow archive.org snapshot . A submit button's[name]and[value]now appear at the button's position, not after the form's[up-params]. - ⚠️ A
validation
Show archive.org snapshot
request no longer sends the params of the form's first submit button, and no longer uses that button's
[formaction],[formmethod],[up-params]or[up-headers]. Nobody pressed the button, so only the<form>decides where a validation goes. - ⚠️
up.Params.fromForm()Show archive.org snapshot now requires a<form>element, as documented. To read params from any other container, useup.Params.fromContainer()Show archive.org snapshot . - ⚠️
up.Params.fromForm()Show archive.org snapshot now includes the[name]and[value]of the form's first submit button, like a submission would. This shows most when you build a URL from the params that users see in the address bar, e.g. for a search. Pass{ submitButton: false }to only include fields, as before, or pass a different button element. - ⚠️
up.submit()Show archive.org snapshot andup.Params.fromForm()Show archive.org snapshot now throw an error when the{ submitButton }option is a button that doesn't belong to the form, as the browser'sFormDataShow archive.org snapshot does. Pass{ submitButton: false }to read a form without a button. - ⚠️ When a form with
[up-accept]or[up-dismiss]Show archive.org snapshot closes an overlay, the result value now also contains the[name]and[value]of the submit button the user pressed. - ⚠️
up.form.config.submitButtonSelectorsShow archive.org snapshot may only match native submit buttons. A custom element or a<button type="button">matched as a submit button now makes submitting throw an error.
There is one exception: unlike the browser, a form only reads controls from its own layer. See Forms stay within their layer Show archive.org snapshot .
Forms stay within their layer
A form and its fields and buttons now always belong to one
layer
Show archive.org snapshot
. Before, some features looked across layers: a form read the fields of a popup that was attached inside it, and a button in an overlay could submit a form below it. To let an overlay affect a form in another layer, have the overlay
return a value
Show archive.org snapshot
when it closes, and let the layer below submit or update its form in an
{ onAccepted }
Show archive.org snapshot
callback.
- ⚠️
up.form.fields()Show archive.org snapshot andup.form.submitButtons()Show archive.org snapshot now ignore elements in another layer Show archive.org snapshot and elements that are being destroyed Show archive.org snapshot , like otherup.fragmentShow archive.org snapshot lookups. A field in a popup that is attached inside a form is no longer watched by that form or submitted with it. - ⚠️ When a submit button or a focused field has a
[form]attribute that points to a form in another layer, submitting now throws an error: "Prevented … from submitting a form in another layer". The browser would submit the other layer's form, e.g. when the same form ID exists in the layer below an overlay that shows the same page. To fix this, keep a form and its[form]controls in the same layer, and use form IDs that are unique across layers. - Fix: While an overlay was closing, an
[up-switch]Show archive.org snapshot field in it could show or hide elements in the layer behind it. This happened for a field outside a<form>, and for an[up-switch-region]Show archive.org snapshot that also matched in that layer.
A clear order of render steps
A render pass now inserts all new content first, then runs compilers, then updates focus and scroll position, and starts animations last. This order now holds for every placement, and when a render pass updates multiple fragments Show archive.org snapshot . A compiler can now see the new version of every other fragment updated in the same layer, including hungry elements Show archive.org snapshot . See Compilers see the DOM after the change Show archive.org snapshot .
- ⚠️ Fragments are now inserted and compiled in the order of the target selector, followed by hungry elements. Since
Unpoly 3.0
Show archive.org snapshot
they were compiled in reverse order, which the 3.0 notes did not mention until now.
up:fragment:insertedShow archive.org snapshot andup:fragment:destroyedShow archive.org snapshot are now also emitted in target order. When fragments are swapped without a transition, all their destructors now run before the first compiler, andup:fragment:destroyedShow archive.org snapshot is emitted after the last compiler. - ⚠️ When
:content,:beforeor:afteris updated with a transition or animation, focus now moves right away, like when swapping a fragment. It used to move when the animation ended. With:afterand{ focus: 'target' }, Unpoly now focuses the first appended element. It used to focus the container's first child, which could be an existing element. - ⚠️ When an error is thrown after new fragments were inserted, e.g. for an invalid
{ focus }or{ scroll }selector,up.render()Show archive.org snapshot now fulfills andup.render().finishedrejects. This was already the case when swapping a fragment, but:before,:afterand fully kept Show archive.org snapshot fragments rejectedup.render()Show archive.org snapshot . - Fix: When multiple fragments played a transition and the render pass scrolled, only the first old element kept its position on the screen.
Security note for apps on Unpoly 3.14
The 3.14.0 release notes named the setting for callback nonces up.script.config.callbackPolicy. The correct name is
up.script.config.evalCallbackPolicy
Show archive.org snapshot
. The error was only in the release notes. The API reference and the guides always used the correct name.
If you copied the wrong name, Unpoly ignored your setting and used its default policy. Without a <meta name="csp-nonce"> tag, the default policy runs callbacks without checking their nonces. Your Content Security Policy then decides what runs. Injected HTML can run code through Unpoly callbacks if your CSP allows 'unsafe-eval', if it uses 'strict-dynamic' (which trusts every script that Unpoly inserts), or if you have no CSP.
To fix this, rename the setting to evalCallbackPolicy, then check that your callbacks still run. You don't need to upgrade to 3.15 for this.
Starting with 3.15, Unpoly logs an error when it boots for every setting that doesn't exist, so a misspelled or renamed setting no longer goes unnoticed. See Mistakes are easier to find Show archive.org snapshot .
Forms
- ⚠️ While a
validation
Show archive.org snapshot
request is loading,
feedback classes
Show archive.org snapshot
like
.up-loadingShow archive.org snapshot are now set by default, as for every other request. To turn them off, set[up-watch-feedback="false"]or pass{ feedback: false }. - A custom control now only needs a
valueproperty to work as a custom form field Show archive.org snapshot . It used to also neednameanddisabledproperties. Unpoly now falls back to the[name]and[disabled]attributes. - A
formdatalistener changes what Unpoly sends, not what it watches. See Adding values with aformdatalistener Show archive.org snapshot . - New experimental config
up.form.config.noFieldSelectorsShow archive.org snapshot excludes elements from being treated as fields, e.g. a form-associated custom element that renders a button. - When Unpoly disables a custom control that has no
disabledproperty, it now sets the[disabled]attribute. - When Unpoly
focuses
Show archive.org snapshot
a form-associated custom element, it now sets
.up-focus-visibleShow archive.org snapshot , even after a mouse or touch interaction. Such an element is now a field, andup.viewport.config.autoFocusVisibleShow archive.org snapshot shows a focus ring Show archive.org snapshot for every field. - A custom control with a
[name]but no readable value no longer reports anundefinedvalue to watchers. - Pages with many watched forms are much faster. Before, every
[up-autosubmit]Show archive.org snapshot ,[up-validate]Show archive.org snapshot ,[up-switch]Show archive.org snapshot orup.watch()Show archive.org snapshot looked at every field on the page after each fragment update. A page with hundreds of small forms could freeze for a second after each update. - ⚠️
[up-switch]Show archive.org snapshot now switches immediately, ignoringup.form.config.watchInputDelayand[up-watch-delay]. Switching effects are client-side and don't benefit from debouncing. In a form that also autosubmits Show archive.org snapshot with a delay, switching now settles before the submission reads the form. If you set[up-watch-delay]on a switching field only to make it react faster, you can remove it. Keep it when the same field also validates or autosubmits. - Fix: When a form was submitted or validated again while a request had disabled its fields, e.g. with
[up-disable]Show archive.org snapshot , the second request sent no params for the disabled fields. Fields and buttons that a preview Show archive.org snapshot disabled now still count as part of the form when Unpoly reads params or watches values. - ⚠️ Since Unpoly 3.12, submit buttons honor a
[data-confirm]attribute, like links and forms do. It is an alias of[up-confirm]Show archive.org snapshot , so Rails helpers likelink_toorbutton_towork without changes. If your app handles[data-confirm]itself, users see two dialogs, and specs may fail with anUnexpectedAlertOpenError. Have your handler use another attribute instead. Unpoly only confirms for forms it submits, so buttons in an[up-submit=false]form or in a form that Unpoly doesn't handle still need your handler. - A nested
[up-autosubmit]Show archive.org snapshot , e.g. on a field inside a form that already has[up-autosubmit], is now ignored when it belongs to the same form. It used to submit the form a second time for the same change. - New experimental class
.up-bootedShow archive.org snapshot on the<html>element after Unpoly has booted. It lets CSS hide switched elements until Unpoly has processed them Show archive.org snapshot . - Fix: When a focused field or button had no
form group
Show archive.org snapshot
that Unpoly could target, disabling it with
[up-disable]Show archive.org snapshot threw aTypeError. This happened e.g. for a submit button that shares its[name]with another button, in a form without an[id]. - ⚠️
up.form.group()Show archive.org snapshot now returns the closest group, even when Unpoly cannot derive a target selector for it. To only get a group that Unpoly can target, pass the new{ targetable: true }option. - 🧩
up.form.config.genericButtonSelectorswas renamed toup.form.config.anyButtonSelectorsShow archive.org snapshot . It now matches every kind of button, including submit and reset buttons. The old name is polyfilled byunpoly-migrate.js. - Fix: After clicking a submit button that Unpoly cannot
target
Show archive.org snapshot
, e.g. a button without
[id]or[name], an error response Show archive.org snapshot moved the focus to the<body>. The re-rendered form now receives the focus, like when other focused elements are replaced. The same happened in other render passes that replaced a focused element without a derivable target.
Overlays
- New experimental
[up-on-accepted-reload]Show archive.org snapshot and[up-on-dismissed-reload]Show archive.org snapshot attributes reload an element in the parent layer after an overlay closes, e.g.up-on-accepted-reload=".company-list". Unlikeup-on-accepted="up.reload('.company-list')", they run no JavaScript, so they need no CSP nonce. The matching options{ onAcceptedReload }Show archive.org snapshot and{ onDismissedReload }Show archive.org snapshot also work in anX-Up-Open-LayerShow archive.org snapshot header. See Reloading on acceptance Show archive.org snapshot . - New
[up-on-accept]Show archive.org snapshot and[up-on-dismiss]Show archive.org snapshot attributes run code before an overlay closes, and can keep it open withevent.preventDefault(). The matching{ onAccept }Show archive.org snapshot and{ onDismiss }Show archive.org snapshot options are now documented. - When an overlay that was opened without an
{ origin }closes, focus now moves to the parent layer, as described in focus in overlays Show archive.org snapshot . It used to fall back to the<body>. - Fix:
[up-focus-visible]Show archive.org snapshot and the{ focusVisible }option were ignored when opening an overlay. - Fix: Pressing Escape dismissed an overlay even when an element in it had already handled the key with
event.preventDefault(), e.g. a dropdown closing its list. A prevented Escape keydown now keeps the overlay open. - ⚠️
Aborting
Show archive.org snapshot
the root layer, e.g. with
up.fragment.abort({ layer: 'root' })Show archive.org snapshot or a render pass with{ abort: 'layer' }, no longer stops polling Show archive.org snapshot , pending validations Show archive.org snapshot or a scheduled preload Show archive.org snapshot in an open overlay. Requests in overlays were already kept.up.fragment.onAborted()Show archive.org snapshot andup.fragment.onKept()Show archive.org snapshot now only react to ancestors in the same layer. To do the same in your own code, pass the new{ layer }option toup.event.onClosest()Show archive.org snapshot . - Fix: The
{ duration }and{ easing }options ofup.layer.open()Show archive.org snapshot were ignored for the opening animation. This also affected[up-duration]and[up-easing]on a link that opens an overlay. Onlyup.layer.configShow archive.org snapshot was used. - Fix: Double-clicking a link that opens an overlay right away, e.g. a styled confirmation dialog Show archive.org snapshot or an overlay with preloaded Show archive.org snapshot content, dismissed the new overlay at once. The second click landed outside the overlay. Double-clicking a link that opens a popup opened and closed the popup. The overlay now stays open.
- Fix: Callbacks like
onAcceptedoronDismissedpassed toup.layer.ask()Show archive.org snapshot were ignored. They now run when the overlay closes, before the promise settles.
Caching
- 🧩 New config
up.network.config.cacheEnabledShow archive.org snapshot turns off the cache Show archive.org snapshot entirely, e.g. for debugging. When it isfalse, no response is read from or written to the cache, and preloading Show archive.org snapshot is disabled. If you usedup.network.config.cacheSize = 0to turn off caching,unpoly-migrate.jsShow archive.org snapshot now disables the cache and tells you to use this setting instead. It used to throw an error. - An
X-Up-Expire-Cache: falseShow archive.org snapshot response header now expires no cache entries, likeX-Up-Evict-Cache: falseShow archive.org snapshot . It used to be treated as a URL pattern matching/false. - Fix: When a compiler requested the URL of a request that had just been aborted or had just finished, it sometimes got the old request from the cache. It now makes a new request.
Rendering
- Fix: The
{ onFailFinished }callback and[up-on-fail-finished]attribute are now called after an error response Show archive.org snapshot has finished rendering. They used to never be called. - Fix:
[up-reveal-padding]Show archive.org snapshot and the{ revealPadding }render option were ignored. Onlyup.viewport.config.revealPaddingwas used. - Fix:
Target derivation
Show archive.org snapshot
for an SVG element with a camelCase tag name, like
<clipPath>, now produces a selector that matches the element. - Fix: While an overlay was open,
up.fragment.get(root, selector)Show archive.org snapshot could return an element outside the given root. - Fix: When an error was thrown after a render pass had started to change the page, Unpoly could render a fallback target Show archive.org snapshot on top of the update. The render promise now rejects with that error.
- ⚠️ When the server responds to
polling
Show archive.org snapshot
or a
deferred
Show archive.org snapshot
placeholder with an error code, Unpoly no longer throws an uncaught
up.CannotMatcherror. The response is not rendered: polling tries again after the next interval, and a deferred placeholder keeps its fallback content. A promise returned byup.reload()Show archive.org snapshot orup.deferred.load()Show archive.org snapshot still rejects, now with anup.RenderResultShow archive.org snapshot . To render error responses anyway, set[up-fail=false]Show archive.org snapshot on the[up-poll]or[up-defer]element, or render them into another fragment with[up-fail-target]Show archive.org snapshot . - ⚠️ With
up.history.config.enabled = falseShow archive.org snapshot , a render pass no longer changes the window title, meta tags orhtml[lang]. Before, only the browser URL stayed unchanged, so the page could show the title of one page with the URL of another. To still update some aspects, setup.history.config.enabledto an array of the history aspects Show archive.org snapshot Unpoly may update:'location','title','metaTags'or'lang'. For example,up.history.config.enabled = ['lang']never changes the browser URL, but still updateshtml[lang]. When an aspect is turned off, layers report their rendered value for it, so[up-nav]Show archive.org snapshot follows the content. Closing an overlay no longer restores a turned-off aspect, andup.history.push()andup.history.replace()do nothing without'location'. When the initial page was loaded with aPOSTrequest and'location'is turned off, Unpoly no longer falls back to full page loads, no longer disables preloading, and no longer warns at boot. - 🧩
up.history.config.updateMetaTagsShow archive.org snapshot is deprecated. If you set it totrue, remove the line. To keep meta tags unchanged, omit'metaTags'from an array of aspects inup.history.config.enabledShow archive.org snapshot .unpoly-migrate.jspolyfills the old setting. It also no longer warns at boot when you haven't setup.history.config.updateMetaTags. - Fix: An explicit list of elements passed as
{ metaTags }Show archive.org snapshot was replaced by the meta tags from the response. - Fix:
[up-scroll-behavior]Show archive.org snapshot and the{ scrollBehavior }option were ignored when scrolling to thetoporbottom, to a pixel position, to a#hashor when restoring a scroll position. Only revealing an element honored them. - Fix: When the server sent an
X-Up-LocationShow archive.org snapshot header with a#hash, Unpoly updated the browser URL but did not reveal the matching element. When the requested URL also had a#hash, the browser URL ended up with both. - Fix: A
combined transition
Show archive.org snapshot
like
move-to-bottom/noneswapped the elements instantly. When one half isnone, the other half is now animated. - Fix: With animations disabled Show archive.org snapshot , animations and transitions no longer wait for the next animation frame before they finish. This bug was introduced in Unpoly 3.14.
Links and URLs
- ⚠️ An
[up-on-offline]Show archive.org snapshot callback now receives theup:fragment:offlineevent asevent, so it can callevent.retry(). It used to receive the event aserror. -
URL patterns
Show archive.org snapshot
now match full URLs on the page's own origin, like
https://example.com/users/*. Such patterns never matched before. - Fix: Pressing the Space key didn't activate a
faux button
Show archive.org snapshot
, e.g. an element with
[up-emit]Show archive.org snapshot or[up-clickable]Show archive.org snapshot . Only the Enter key worked, although Space was meant to work for buttons too. - Fix: When the user pressed the mouse button on a preloading Show archive.org snapshot link while its preload request was still loading after hovering, Unpoly aborted the request and sent the same request again. This bug was introduced in Unpoly 3.8.
- Fix: When the user clicked a preloading Show archive.org snapshot link while its preload request was still loading, a newer render pass for the same fragment could not abort Show archive.org snapshot the click. Once the preload responded, the click rendered the old response over the newer content, and aborted the newer render pass. The click can now be aborted like any other request, while the preload keeps loading for the cache.
- ⚠️ A click on an
[up-expand]Show archive.org snapshot area now does what a click on its link does. When Unpoly follows the link, it now follows the link itself, not the area: events likeup:link:followare emitted on the link, the link is the origin Show archive.org snapshot , and its[data-method]and[data-confirm]attributes apply. When Unpoly doesn't follow the link, the area clicks it, and the browser does what the link does: a[download]link downloads its file, a[target=_blank]link opens a new tab, and a link without[up-follow]makes a full page load. To keep updating a fragment, give the link an[up-follow]attribute or follow all links Show archive.org snapshot . - Fix: Clicking a button, a
<label>or a<summary>inside an[up-expand]Show archive.org snapshot area, or inside another followable element that isn't a link, followed the link. A submit button didn't submit its form, a label didn't toggle its checkbox, and a<details>element didn't open. These elements now handle their own clicks, like links and fields already did. Inside an<a>element, buttons and labels still belong to the link. - ⚠️ Clicks made by JavaScript, like
element.click(), now always emitup:clickShow archive.org snapshot . When a script clicks a link, Unpoly now follows it, also while handling another click or within a popup. To have the browser follow the link instead, set[up-follow=false]on it. - Fix: Clicking a form field inside an
[up-instant]Show archive.org snapshot link or area, e.g. an[up-expand]Show archive.org snapshot area around an[up-instant]link, did nothing. Unpoly didn't follow the link, but also prevented the click, so a checkbox didn't toggle. Fields now handle their own clicks. - Fix: An
[up-instant]Show archive.org snapshot link did nothing when another script prevented itsmousedownevent, e.g. to keep the focus in a search field. Unpoly now follows such a link onclick, and emitsup:clickShow archive.org snapshot onclickinstead ofmousedown.
unpoly-migrate.js
- ⚠️
unpoly-migrate.jsShow archive.org snapshot now logs deprecations as errors, so test suites that fail on console errors find them without extra configuration. To log warnings as before, setup.migrate.config.logLevel = 'warn'. -
unpoly-migrate.jswarnings about renamed render, request or layer options now name where the option was used, like the origin element or URL, instead of printing the options object. Each place is reported once. - The
unpoly-migrate.jswarning for the deprecated origin shorthand&now names the target and the origin element. When the origin is a button or field in a form, it also reminds you that a form submission's:originShow archive.org snapshot is now the button or changed field, not the form, and thatform:has(:origin)targets the form. - Fix:
unpoly-migrate.jslogged deprecations that the app never caused when a script enumerated an object with deprecated properties, e.g. an analytics script walkingwindow.up, or another deprecation message printing a config object. - Fix: The
unpoly-migrate.jspolyfills forup.popup.attach()Show archive.org snapshot ,up.modal.follow()Show archive.org snapshot andup.modal.extract()Show archive.org snapshot now open a new overlay again. - Fix: The
unpoly-migrate.jspolyfill forup.network.shouldReduceRequests()now logs a deprecation message instead of throwing an error. - Fix: The
unpoly-migrate.jspolyfill forup.util.microtask()Show archive.org snapshot threw an error. This bug was introduced in Unpoly 3.8. - Fix:
unpoly-migrate.jsignored renamed layer keys likeclosable,dismissable,closeLabelordismissAriaLabelwhen they were set inup.layer.configShow archive.org snapshot . They are now renamed, like when passed as options. - Fix: With
unpoly-migrate.js, old layer attributes like[up-flavor],[up-closable]or[up-history-visible]are now also translated on forms that open an overlay. - Fix: With
unpoly-migrate.js, a popup position like'bottom-right'is now split into{ position: 'bottom', align: 'right' }before the overlay opens. Before,up.Layer#positionShow archive.org snapshot and the<up-popup>attributes showed the combined value. - Fix: With
unpoly-migrate.js, a link with only an[up-href]Show archive.org snapshot attribute is now given[up-follow]Show archive.org snapshot . Before,unpoly-migrate.jsand Unpoly could disagree about whether the link was followable. It also no longer warns about a link that another attribute already makes followable, like<a up-href="/path" up-target=".target">.
Smaller fixes and changes
- ❌ The
3.14.0 release notes
Show archive.org snapshot
named the setting for callback nonces
up.script.config.callbackPolicy. The correct name isup.script.config.evalCallbackPolicyShow archive.org snapshot . If you copied the wrong name, Unpoly ignored your setting and used the default policy'auto'. Search your code forcallbackPolicyand rename it. Renaming makes your policy Show archive.org snapshot take effect. Check that your callbacks still run.unpoly-migrate.jsnow forwards the wrong name and logs a deprecation message. Withoutunpoly-migrate.js, Unpoly now logs an error for the wrong name when it boots, like for any other setting that doesn't exist. See the security note Show archive.org snapshot . - ⚠️
unpoly.jsis now compiled using ES2024 (up from ES2021). It needs Chrome 119, Firefox 121 or Safari 17.4, all released by March 2024. The ES6 build Show archive.org snapshot for legacy browsers remains available. - ⚠️ A
clickevent dispatched by a script is no longer passed toup.on()Show archive.org snapshot callbacks when the element has a[disabled]attribute, even for elements that browsers cannot disable. Before, only adisabledproperty had this effect. - The npm package now also includes source maps for the unminified files, like
unpoly.js.map. -
up.template.clone()Show archive.org snapshot with an{ origin }now also finds a template in an ancestor layer, as described in template lookup Show archive.org snapshot . It used to only look in the origin's layer. - Fix:
up.viewport.restoreFocus()Show archive.org snapshot threw an error when called without a{ layer }option, or with a layer name like{ layer: 'root' }. - Fix:
up.viewport.restoreScroll()Show archive.org snapshot ignored its{ location }option. It always restored the positions saved for the layer's current location. - Fix:
up.fragment.matches()Show archive.org snapshot with{ layer: 'new', mode }options expanded a selector like:mainShow archive.org snapshot for the root layer, or threw an error while an overlay was open.
Note
Our sponsor makandra funded this release ❤️
You can hire makandra for Unpoly support Show archive.org snapshot .