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

# Navigation defaults

Following a link or submitting a form is a _navigation_. 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 only swaps the fragment, and you opt into each side effect on your own.

<nav aria-label="Contents">

#### Contents

- [What counts as navigation](#navigating-features)
- [The defaults](#navigation-defaults)
- [Opting out of navigation](#opting-out)
- [Opting into navigation](#opting-in)
- [Customizing defaults](#customizing-defaults)
- [Defaults that depend on the origin](#origin-dependent-defaults)

</nav>

## What counts as navigation {#navigating-features}

A user who clicks a standard hyperlink or submits a form expects what browsers have always done: the address bar shows the new location, the Back button returns to the previous screen, the new page starts at the top, and focus is not stranded on an element that no longer exists. When an Unpoly feature _navigates_, it mimics all of this.

Following a link, submitting a form or opening an overlay is considered navigation by default. Other features only render a fragment: a validation request re-renders a form field, and a polling fragment refreshes itself in the background. Neither should move the user's scroll position or add a history entry.

| Feature | Navigates by default? |
| --- | --- |
| [`[up-follow]`](https://unpoly.com/api/up-follow.md) | yes |
| [`up.follow()`](https://unpoly.com/api/up.follow.md) | yes |
| [`up.navigate()`](https://unpoly.com/api/up.navigate.md) | always |
| [`up.visit()`](https://unpoly.com/api/up.visit.md) | always |
| [`[up-layer=new]`](https://unpoly.com/api/up-layer-new.md) | yes |
| [`up.layer.open()`](https://unpoly.com/api/up.layer.open.md) | yes |
| [`[up-submit]`](https://unpoly.com/api/up-submit.md) | yes |
| [`up.submit()`](https://unpoly.com/api/up.submit.md) | yes |
| [`up.render()`](https://unpoly.com/api/up.render.md) | no |
| [`up.reload()`](https://unpoly.com/api/up.reload.md) | no |
| [`[up-validate]`](https://unpoly.com/api/up-validate.md) | no |
| [`up.validate()`](https://unpoly.com/api/up.validate.md) | no |
| [`[up-poll]`](https://unpoly.com/api/up-poll.md) | no |

## The defaults {#navigation-defaults}

When navigating, Unpoly applies the following render options:

| Option | Effect |
| --- | --- |
| `{ history: 'auto' }` | [Update history](https://unpoly.com/learn/updating-history.md) if rendering a [main target](https://unpoly.com/api/main.md) |
| `{ scroll: 'auto' }` | [Reset scroll positions](https://unpoly.com/learn/scrolling.md#auto) if rendering a main target |
| `{ focus: 'auto' }` | [Focus](https://unpoly.com/learn/focus.md#auto) the new fragment if rendering a main target |
| `{ fallback: true }` | Render a main target if the response doesn't contain the given [target](https://unpoly.com/learn/targeting-fragments.md#missing-targets) |
| `{ cache: 'auto' }` | [Cache](https://unpoly.com/learn/caching.md) responses to `GET` requests |
| `{ revalidate: 'auto' }` | Reload [expired](https://unpoly.com/learn/caching.md#revalidation) cache entries after rendering |
| `{ peel: 'dismiss' }` | Dismiss overlays when [targeting a background layer](https://unpoly.com/learn/closing-overlays.md#peeling) |

Most of these options have an `'auto'` value, meaning the effect only applies when the update is significant. A link that swaps the layer's main fragment gets a new history entry and scrolls to the top. A link that only updates a sidebar keeps the URL and the scroll position, even though it navigates.

The same defaults also apply when the server responds with an [error code](https://unpoly.com/learn/error-responses.md), so a failed form submission gets the same `'auto'` scroll and focus behavior.

## Opting out of navigation {#opting-out}

To follow a link or submit a form without navigation defaults, set an `[up-navigate=false]` attribute:

```html
<a href="/users?sort=name" up-target=".users" up-navigate="false">Sort by name</a>
```

Clicking the link updates the `.users` list and nothing else. The URL stays the same, the scroll position is kept and the response is not cached. The link also no longer falls back to the main fragment. When the server responds with an error, nothing is rendered unless you set an [`[up-fail-target]`](https://unpoly.com/learn/error-responses.md#fail-options).

With `[up-navigate=false]` you can still enable individual effects, like `[up-history=true]` or `[up-scroll=top]`. See the attributes for [links](https://unpoly.com/api/up-follow.md) and [forms](https://unpoly.com/api/up-submit.md) for all options.

## Opting into navigation {#opting-in}

Rendering functions like [`up.render()`](https://unpoly.com/api/up.render.md) or [`up.reload()`](https://unpoly.com/api/up.reload.md) don't navigate by default. To update a fragment with navigation defaults, pass a `{ navigate: true }` option:

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

The [`up.navigate()`](https://unpoly.com/api/up.navigate.md) function does the same with less typing:

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

Explicit options always win over the defaults, so you can navigate and still keep the scroll position:

```js
up.navigate({ target: '.content', url: '/posts/5', scroll: 'keep' })
```

## Customizing defaults {#customizing-defaults}

The navigation defaults live in [`up.fragment.config.navigateOptions`](https://unpoly.com/api/up.fragment.config.md#config.navigateOptions). Change an option there to change it for every navigation in your app. For instance, this animates every navigation with a cross-fade:

```js
up.fragment.config.navigateOptions.transition = 'cross-fade'
```

Links and forms can still override a configured default with their own attributes, like `[up-transition=move-left]` or `[up-transition=false]`.

## Defaults that depend on the origin {#origin-dependent-defaults}

Sometimes a default should depend on the link or form that was activated.

Events like [`up:link:follow`](https://unpoly.com/api/up:link:follow.md) or [`up:form:submit`](https://unpoly.com/api/up:form:submit.md) have a `{ renderOptions }` property that lets you change the render options for the coming fragment update.

The code below keeps the scroll position when the user switches tabs, even though each tab swaps the main fragment:

```js
up.on('up:link:follow', '.tabs a', function(event) {
  event.renderOptions.scroll = false
})
```

See [changing options before rendering](https://unpoly.com/learn/render-lifecycle.md#changing-options-before-rendering) for more.

<nav aria-label="Reading order">

- [Previous: Attributes and options](https://unpoly.com/learn/attributes-and-options.md)
- [Next: Targeting fragments](https://unpoly.com/learn/targeting-fragments.md)

</nav>

