---
area: "Learn"
parent: "Learn"
url: "https://unpoly.com/learn/advanced-rendering"
---
<nav aria-label="Unpoly docs">[All docs](https://unpoly.com/index.md) · [Learn](https://unpoly.com/learn.md)</nav>

# Advanced rendering

Every link, form, overlay or poll that Unpoly handles ends in the same render pass, and every render pass takes the same options. This chapter covers those options: which fragments are updated, what else happens on a navigation, what to do when the server reports an error, and how your code can hook into the process.

<nav aria-label="Contents">

#### Contents

- [One function behind every update](#one-function-behind-every-update)
- [Targeting fragments](#targeting-fragments)
- [Navigation defaults](#navigation-defaults)
- [Handling error responses](#handling-error-responses)
- [Keeping elements](#keeping-elements)
- [Hooking into the render lifecycle](#hooking-into-the-render-lifecycle)
- [See also](#see-also)

</nav>

## One function behind every update {#one-function-behind-every-update}

When the user clicks this link, Unpoly fetches `/posts/5` and swaps the `.post` element with its counterpart from the response:

```html
<a href="/posts/5" up-target=".post">Read post</a>
```

Internally the link becomes a call to [`up.render()`](https://unpoly.com/api/up.render.md). Your own code can make that call directly:

```js
up.render({ target: '.post', url: '/posts/5' })
```

The _target_ is a CSS selector that must match in both the current page and the server response. The link itself is the _origin_ of the update: the element the user interacted with. Everything else that a render pass does, from caching to scrolling, is a _render option_. An option is available in three forms: as an `[up-*]` attribute on a link or form, as a camel-cased option for a function like [`up.render()`](https://unpoly.com/api/up.render.md), and as a global default in a config object.

<nav aria-label="Read more">

- [More: Attributes and options](https://unpoly.com/learn/attributes-and-options.md)

</nav>

## Targeting fragments {#targeting-fragments}

A target can express more than one element. Separate selectors with a comma to update several fragments from one request, and suffix a selector with `:after` to append to a fragment instead of replacing it:

```html
<ul class="tasks">
  <li>Wash car</li>
  <li>Fix tent</li>
</ul>

<a href="/tasks?page=2" class="next-page" up-target=".tasks:after, .next-page">
  Load more tasks
</a>
```

Clicking the link appends the next page's `<li>` children to the list and replaces the link. When a selector like `.card` matches several elements, Unpoly prefers the match closest to the origin, so a link inside the second card updates the second card.

<nav aria-label="Read more">

- [More: Targeting fragments](https://unpoly.com/learn/targeting-fragments.md)

</nav>

## Navigation defaults {#navigation-defaults}

Following a link or submitting a form is a _navigation_. A navigation adds a preset of render options that make the update feel like a full page load: responses are cached, and when the main fragment changes, the URL updates, the page scrolls to the top and focus moves to the new content. A plain [`up.render()`](https://unpoly.com/api/up.render.md) call has none of these defaults.

Each default can be turned off per element, and your JavaScript can opt in:

```html
<a href="/posts/5" up-target=".post" up-navigate="false">Read post</a>
```

```js
up.render({ target: '.post', url: '/posts/5', navigate: true })
```

To change what navigation means for your entire app, set the defaults in [`up.fragment.config.navigateOptions`](https://unpoly.com/api/up.fragment.config.md#config.navigateOptions).

<nav aria-label="Read more">

- [More: Navigation defaults](https://unpoly.com/learn/navigation-defaults.md)

</nav>

## Handling error responses {#handling-error-responses}

When the server responds with an error status, a different fragment often needs to change. A failed form submission should re-render the form instead of showing the next screen. Prefix any render option with `fail` to apply it only to error responses:

```js
up.render({
  url: '/users',
  method: 'post',
  target: '.content', // when the submission succeeds
  failTarget: 'form',
})
```

A successful response updates `.content`. An error response updates the `<form>` instead, so the user sees the validation errors next to their input. In HTML the same option is `[up-fail-target]`. For forms, Unpoly already defaults to re-rendering the form itself.

<nav aria-label="Read more">

- [More: Handling error responses](https://unpoly.com/learn/error-responses.md)

</nav>

## Keeping elements {#keeping-elements}

Some elements carry state that a swap would lose, like a playing video or a map with its pan and zoom. Mark such an element with [`[up-keep]`](https://unpoly.com/api/up-keep.md), and it survives when the fragment around it is replaced:

```html
<div id="article">
  <p>Article 1</p>
  <audio id="player" up-keep src="song1.mp3"></audio>
</div>
```

When the response contains a matching `#player`, Unpoly moves the existing element into the new fragment, so the song keeps playing. You can also keep an element only until its HTML or data changes.

<nav aria-label="Read more">

- [More: Keeping elements](https://unpoly.com/learn/keeping-elements.md)

</nav>

## Hooking into the render lifecycle {#hooking-into-the-render-lifecycle}

Rendering functions return a promise that fulfills with the inserted fragments:

```js
let result = await up.render({ target: '.post', url: '/posts/5' })
console.log('Updated fragment:', result.fragment)
```

Before a loaded response is rendered, the [`up:fragment:loaded`](https://unpoly.com/api/up:fragment:loaded.md) event lets you inspect it and change the render options, or prevent the update altogether. When a maintenance page should never be rendered as a fragment, make a full page load instead:

```js
up.on('up:fragment:loaded', function(event) {
  if (event.response.header('X-Maintenance')) {
    event.preventDefault()
    event.request.loadPage()
  }
})
```

<nav aria-label="Read more">

- [More: Render lifecycle](https://unpoly.com/learn/render-lifecycle.md)

</nav>

## See also {#see-also}

When Unpoly updates an element without being given a selector, it guesses one from the element's `[id]`, `[name]` or classes. To tune that guess, see [Target derivation](https://unpoly.com/learn/target-derivation.md).

To render HTML that you already have, without a request to the server, see [Providing HTML to render](https://unpoly.com/learn/providing-html.md).

<nav aria-label="In this chapter">

## In this chapter {#children-index-title}

- [Attributes and options](https://unpoly.com/learn/attributes-and-options.md)
- [Navigation defaults](https://unpoly.com/learn/navigation-defaults.md)
- [Targeting fragments](https://unpoly.com/learn/targeting-fragments.md)
- [Target derivation](https://unpoly.com/learn/target-derivation.md)
- [Handling error responses](https://unpoly.com/learn/error-responses.md)
- [Keeping elements](https://unpoly.com/learn/keeping-elements.md)
- [Providing HTML to render](https://unpoly.com/learn/providing-html.md)
- [Render lifecycle](https://unpoly.com/learn/render-lifecycle.md)

</nav>

<nav aria-label="Reading order">

- [Previous: Server protocol](https://unpoly.com/learn/server-protocol.md)
- [Next: Attributes and options](https://unpoly.com/learn/attributes-and-options.md)

</nav>

