Read more

Unpoly 3.5.0 released

Henning Koch
November 09, 2023Software engineer at makandra GmbH

Unpoly Show snapshot 3.5 brings major quality-of-life improvements and addresses numerous edge cases in existing functionality.

Notification flashes

Illustration book lover

Growing Rails Applications in Practice

Check out our e-book. Learn to structure large Ruby on Rails codebases with the tools you already know and love.

  • Introduce design conventions for controllers and user-facing models
  • Create a system for growth
  • Build applications to last
Read more Show snapshot

You can now use an [up-flashes] Show snapshot element to render confirmations, alerts or warnings.

To render a flash message, include an [up-flashes] Show snapshot element in your response.
The element's content should be the messages you want to render:

<div up-flashes>
  <strong>User was updated!</strong>

  Main response content ...

An [up-flashes] Show snapshot element comes with useful default behavior for rendering notifications:

See notification flashes Show snapshot for more details and examples.

Detection of changed scripts and styles

Unpoly now detects changes in your JavaScripts and stylesheets after deploying a new version of your application.
While rendering new content, Unpoly compares script and style elements in the <head> and emits an up:assets:changed Show snapshot event if anything changed.

It is up to you to handle new frontend code revisions, e.g. by loading new assets Show snapshot or notifying the user Show snapshot .

See handling asset changes Show snapshot for more details and examples.

Automatic update of meta tags {#meta-tags}

Render passes that update history now synchronize meta tags Show snapshot in the <head>, such as meta[name=description] or link[rel=canonical].

In the document below, the highlighted elements will be updated when history is changed, in additional to the location URL:

  <title>AcmeCorp</title> <!-- will update -->
  <link rel="canonical" href=""> <!-- will update -->
  <meta name="description" content="About the AcmeCorp team"> <!-- will update -->
  <meta prop="og:image" content=""> <!-- will update -->
  <script src="/assets/app.js"></script>
  <link rel="stylesheet" href="/assets/app.css">  

The linked JavaScript and stylesheet are not part of history state and will not be updated.

Consistent behavior in overlays

Overlays with history Show snapshot now update meta tags when opening. When the overlay closes the parent layer's meta tags are restored.

Deprecating [up-hungry] Show snapshot in the <head>

Existing solutions using [up-hungry] Show snapshot to update meta tags can be removed from your application code.

Other than [up-hungry] Show snapshot the new implementation can deal with meta tags that only exist on some pages.

Opting in or out

See [up-meta] Show snapshot for ways to include or exclude head elements from synchronization.

You can disable the synchronization of meta tags globally Show snapshot or per render pass Show snapshot :

up.render('.element', { url: '/path', history: true, metaTags: false })

Forgiving error handling

In earlier versions, errors in user code would often crash Unpoly. This would sometimes leave the page in a corrupted state. For example,
a render pass would only update some fragments, fail to scroll, or fail to run destuctors.

This version changes how Unpoly handles exceptions thrown from user code, like compilers, transition functions or callbacks like { onAccepted }.

User errors are no longer thrown

Starting with this version, Unpoly functions generally succeed despite exceptions from user code.

The code below will successfully compile Show snapshot an element despite a broken compiler Show snapshot :

up.compiler('.element', () => { throw new Error('broken compiler') })
let element = up.element.affix(document.body, '.element')
up.hello(element) // no error is thrown

Instead an error event on window Show snapshot is emitted:

window.addEventListener('error', function(event) {
  alert("Got an error " +

This behavior is consistent with how the web platform handles errors in event listeners
and custom elements.

Debugging and testing

Exceptions in user code are also logged to the browser's error console Show snapshot .
This way you can still access the stack trace or detect JavaScript errors in E2E tests.

Some test runners like Jasmine Show snapshot already listen to the error event and fail your test if any uncaught exception is observed.
In Jasmine you may use jasmine.spyOnGlobalErrorsAsync() to make assertions on the unhandled error.

Hungry elements

Element with an [up-hungry] attribute are updated whenever the server
sends a matching element, even if the element isn't targeted Show snapshot .
This release addresses many issues and requests concerning hungry elements:

Conflict resolution

There is now defined behavior when multiple targets want to render the same new fragments from a server response:

  • When both a target selector Show snapshot and a hungry elements target the same fragment in the response, only the direct render target will be updated.
  • Hungry elements can be be nested. The outer element will be updated. Note that we recommend to not over-use the hungry mechanism, and prefer to explicit render targets instead.

Rendering in multiple layers

Many edge cases have been addressed for render passes that affect multiple layers:

More control over updates

You can now freely control when an hungry element is updated:

  • Before a hungry element is added to a render pass, a new event up:fragment:hungry is now emitted on the element.
    The event has properties for the old and new element, and information about the current render pass.

    You may prevent this event to exclude the hungry element from the render pass. Use this to define arbitrary conditions
    for when an hungry element should be updated:

    element.addEventListener('up:fragment:hungry', function(event) {
      if (event.newFragment.classList.contains('is-empty')) {
        console.log('Ignoring a fragment with an .is-empty class')
  • Hungry elements can now set an [up-on-hungry] attribute. It contains a code snippet that receives an up:fragment:hungry Show snapshot event.
    Calling event.preventDefault() will prevent the hungry fragment from being updated.

  • Deprecated the [up-if-history] modifier for hungry elements.

    This functionality is now covered by the more generic [up-on-hungry] attribute. Also its main use case was synchronizing meta tags,
    and that is now supported out of the box Show snapshot .


Some improvements have been to hungry elements with animated transitions Show snapshot :


This release ships many improvements for the [up-poll] attribute.

Pausing and resuming

Unpoly has always paused polling Show snapshot when the user minimizes the window or switches to another tab.
This behavior has been improved by the following:

  • When at least one poll interval was spent paused in the background and the user then returns to the tab, Unpoly will now immediately reload the fragment.

    You can use this to load recent data when the user returns to your app after working on something else for a while. For example, the following
    would reload your main Show snapshot element after an absence of 5 minutes or more:

    <main up-poll up-interval="300_000">
  • Polling now unschedules all JavaScript timers while polling is paused. This allows browser to keep the inactive window suspended, saving battery life.

Unpoly also pauses polling Show snapshot for fragments that are covered by an overlay. This behavior has been improved by the following:

Disabling polling


Unpoly's rendering engine has been reworked to address many edge cases found in production use.

More practical callback order

Matching in destroyed elements

This release addresses many many errors when matching fragments in closed layers, detached elements or destroyed elements in their exit animation:

  • Rendering successful responses no longer crashes if a { failTarget } or { failLayer } cannot be resolved.
  • up.fragment.toTarget() no longer crashes when deriving targets for destroyed elements that are still in their exit animation.
  • Fragment lookup functions now crash with a better error message when the given { layer } does not exist or has been closed.
  • Revalidation Show snapshot now succeeds when the { failLayer } is no longer open.

General improvements

Network quality is no longer measured

Previous versions of Unpoly adapted the behavior some features when it detected high latency or low network throughput.
Due to cross-browser support for the Network Information API Show snapshot ,
measuring of network quality was removed:

  • Unpoly no longer doubles poll Show snapshot intervals on slow connections. The configuration was removed.

  • Unpoly no longer prevents preloading Show snapshot on slow connections. The configuration = 'auto' was removed.

    To disable preloading based on your own metrics, you can still prevent the up:link:preload event.

  • The configuration was removed.

  • The configuration was removed.

  • The function was removed.

Unpoly retains all other functionality for dealing with network issues Show snapshot .

Fragment API

More control over region-aware fragment matching

When targeting fragments Show snapshot , Unpoly will prefer to
match fragments in the region of the user interaction Show snapshot . For example, when
a link's [up-target] could match multiple fragments, the fragment closest to the link is updated.
In cases where you don't want this behavior, you now have more options:

  • You can now disable region-aware fragment matching for individual function calls or elements:
    • Pass a { match: 'first' } option to any function that matches or renders a fragment.
    • Set an [up-match=first] option on a link or form that matches or renders a fragment.
  • The boolean configuration up.fragment.config.matchAroundOrigin has been replaced by up.fragment.config.match. Its values are 'region' (default) and 'first'.

General improvements

  • New experimental function up.fragment.contains(). It returns whether the given root matches or contains the given selector or element.

    Other than Element#contains() it only matches fragments on the same layer. It also ignores destroyed fragments in an exit animation.

  • The event up:fragment:keep received a new property { renderOptions }. It contains the render options for the current render pass.

  • The event up:fragment:aborted received new experimental property { newLayer }. It returns whether the fragment was aborted by a new overlay opening Show snapshot .

  • Many functions in the fragment API now also support a Document as the search root:

    • up.fragment.get()
    • up.fragment.all()
    • up.fragment.contains()
  • Passing an element to up.fragment.get() now returns that element unchanged.


  • Destructors are now called with the element being destroyed.

    This allows you to reuse the same destructor function Show snapshot for multiple elements:

    let fn = (element) => console.log('Element %o was destroyed', element)
    for (let element of document.querySelector('div')) {
      up.destructor(element, fn)
  • Unpoly 3.0.0 introduced a third meta argument for compilers Show snapshot
    containing information about the current render pass:

    up.compiler('.user', function(element, data, meta) {
      console.log(meta.response.text.length)        // => 160232
      console.log(meta.response.header('X-Course')) // => "advanced-ruby"
      console.log(meta.layer.mode)                  // => "root"
      console.log(meta.revalidating)                // => boolean

    Unfortunately we realized that access to the response this would to bad patterns where fragments would compile
    differently for the initial page load vs. subsequent fragment updates.

    In Unpoly 3.5 compilers can no longer access the current response via the { response } of that meta argument.
    The { layer } and { revalidating } property remains available.

  • The up.syntax package has been renamed to up.script Show snapshot .


  • The up:link:preload event received a new property { renderOptions }. It contains the render options for the current render pass.
  • The [up-on-offline] Show snapshot attribute now supports a CSP nonce Show snapshot .
  • The function now takes an Object as a second argument. It will override any options parsed from the link attributes.
  • The configuration was deprecated. To disable preloading, prevent up:link:preload.

DOM helpers

  • A new experimental function up.element.isEmpty() was added. It returns whether an element has neither child elements nor non-whitespace text.


  • Renamed configuration up.viewport.config.anchoredRight to up.viewport.config.anchoredRightSelectors
  • Renamed configuration up.viewport.config.fixedTop to up.viewport.config.fixedTopSelectors
  • Renamed configuration up.viewport.config.fixedBottom to up.viewport.config.fixedBottomSelectors


  • The polyfills for the up.element.isAttached() and up.element.isDetached() functions were changed so they behave
    like their implementation in Unpoly 2.x. In particular the functions now only consider attachment in window.document, but not to other Document instances.


Henning Koch
November 09, 2023Software engineer at makandra GmbH
Posted by Henning Koch to makandra dev (2023-11-09 14:52)