---
area: "Learn"
parent: "Scrolling & focus chapter"
url: "https://unpoly.com/learn/focus"
---
<nav aria-label="Unpoly docs">[All docs](https://unpoly.com/index.md) · [Scrolling & focus chapter](https://unpoly.com/learn/scrolling-and-focus.md)</nav>

# Controlling focus

When updating a fragment, you control where Unpoly places the [focus](https://en.wikipedia.org/wiki/Focus_(computing)) by setting an [`[up-focus]`](https://unpoly.com/api/up-follow.md#up-focus) attribute or by passing a [`{ focus }`](https://unpoly.com/api/up.render.md#options.focus) option. By default, following a link focuses the new content like a full page load would, and a minor update keeps the focus where it is.

Placing the focus matters for accessibility. A screen reader starts reading from the focused position, and keyboard users continue tabbing from there. Without focus management, a user who activated a link would be left somewhere in the old content, unaware that the page has changed.

<nav aria-label="Contents">

#### Contents

- [Default focus strategy](#auto)
- [Focusing the new fragment](#target)
- [Focusing another element](#selector)
- [Focusing the URL's `#hash` target](#hash)
- [Focusing the main fragment](#main)
- [Focusing the layer](#layer)
- [Preserving focus](#keep)
- [Restoring focus](#restore)
- [Don't focus](#false)
- [Trying multiple strategies](#multiple-strategies)
- [Conditional focusing](#condition)
- [Custom focus logic](#function)
- [Focusing without a render pass](#js)
- [Focus in overlays](#overlays)

</nav>

## Default focus strategy {#auto}

When [navigating](https://unpoly.com/learn/navigation-defaults.md), Unpoly tries a sequence of focus strategies that works for most cases:

1. If the URL has a `#hash`, focus the element matching that hash.
2. Focus an `[autofocus]` element in the new fragment.
3. If updating a [main target](https://unpoly.com/api/up-main.md), focus the main fragment.
4. If focus was lost with the old fragment, re-focus a [similar](https://unpoly.com/learn/target-derivation.md) element in the new content.
5. If focus was lost with the old fragment, and no similar element was found, focus the new fragment.

You can change this sequence in [`up.fragment.config.autoFocus`](https://unpoly.com/api/up.fragment.config.md#config.autoFocus).

Following links and submitting forms are considered [navigation](https://unpoly.com/learn/navigation-defaults.md), so the default strategy is applied without further configuration. You can also enable it explicitly by setting `[up-focus="auto"]`:

```html
<a href="/details" up-follow up-focus="auto">Show more</a>
```

From JavaScript, pass `{ focus: 'auto' }`:

```js
up.render({ url: '/path', focus: 'auto' })
```

When rendering without navigation, e.g. with [`up.render()`](https://unpoly.com/api/up.render.md), focus is not changed by default. [`up.reload()`](https://unpoly.com/api/up.reload.md) and [`[up-validate]`](https://unpoly.com/api/up-validate.md) [keep the current focus](#keep).

## Focusing the new fragment {#target}

To focus the updated fragment, set `[up-focus="target"]`:

```html
<a href="/details" up-target="#details" up-focus="target">Show more</a>
```

If the fragment isn't focusable by itself, Unpoly gives it a `[tabindex="-1"]` attribute. This makes it focusable from JavaScript without adding it to the keyboard's tab sequence.

Focusing an element does not scroll it into view. To also reveal the fragment, set [`[up-scroll="target"]`](https://unpoly.com/learn/scrolling.md#target).

## Focusing another element {#selector}

To focus a matching element, set `[up-focus]` to any CSS selector string:

```html
<a href="/advanced-search" up-target=".content" up-focus="input[type=search]">Advanced search</a>
```

Unpoly prefers a match within the updated fragment, but the selector may match anywhere in the fragment's layer.

From JavaScript you can also pass the [`Element`](https://developer.mozilla.org/en-US/docs/Web/API/Element) object that should be focused:

```js
up.render({ target: '.content', url: '/advanced-search', focus: searchField })
```

## Focusing the URL's `#hash` target {#hash}

To focus the element matching the `#hash` in the updated URL, set `[up-focus="hash"]`:

```html
<a href="/product/12#features" up-follow up-focus="hash">Show features</a>
```

When the URL has no hash, or no element matches it, focus is not changed.

## Focusing the main fragment {#main}

To focus the updated layer's [main fragment](https://unpoly.com/api/up-main.md), set `[up-focus="main"]`:

```html
<a href="/contact" up-follow up-focus="main">Contact us</a>
```

## Focusing the layer {#layer}

To focus the container element of the updated [layer](https://unpoly.com/api/up.layer.md), set `[up-focus="layer"]`:

```html
<a href="/details" up-follow up-focus="layer">Show more</a>
```

In an [overlay](https://unpoly.com/learn/overlays.md), this focuses the overlay element, which screen readers announce as a dialog.

## Preserving focus {#keep}

To preserve focus-related state through a fragment update, set `[up-focus="keep"]`:

```html
<a href="/list" up-follow up-focus="keep">Reload list</a>
```

For instance, when a focused `<input>` element is swapped out with a new fragment, `[up-focus="keep"]` causes the same input to be focused after the update. The following properties are preserved:

- Which element is focused.
- The cursor position within a focused input element.
- The selection range within a focused input element.
- The scroll position within a focused input element.

Focus can only be preserved when the focused element has a [derivable target selector](https://unpoly.com/learn/target-derivation.md), e.g. by setting an `[id]` or `[name]` attribute.

[`up.reload()`](https://unpoly.com/api/up.reload.md) uses this strategy by default.

## Restoring focus {#restore}

When revisiting an earlier page, you may want to restore the focus from that visit. Set `[up-focus="restore"]` to restore the last known focus state for the updated URL:

```html
<a href="/list" up-follow up-focus="restore">Back to list</a>
```

Before every fragment update, Unpoly [saves focus-related state](https://unpoly.com/api/up.viewport.saveFocus.md) for the current URL.
You can prevent this by setting [`[up-save-focus="false"]`](https://unpoly.com/api/up-follow.md#up-save-focus).

Focus state is remembered for each layer until the next full page load. When nothing is known for the URL, focus is not changed. Add a fallback with `[up-focus="restore, target"]`.

When the user presses the back button, Unpoly restores focus the same way. See [Restoring history](https://unpoly.com/learn/restoring-history.md).

## Don't focus {#false}

If you don't want Unpoly to touch the focus, set `[up-focus="false"]`. For example, this link opts out of the [default focus strategy](#auto) that [navigation](https://unpoly.com/learn/navigation-defaults.md) would apply:

```html
<a href="/details" up-follow up-focus="false">Show more</a>
```

Note that even with `[up-focus="false"]` the focus may change during a fragment update. For instance, when a fragment contains focus and is then swapped, focus reverts to the `<body>` element.

To actively preserve focus, use [`[up-focus="keep"]`](#keep) instead.

## Trying multiple strategies {#multiple-strategies}

To attempt multiple focus strategies, separate options with a comma. Unpoly uses the first strategy that finds an element to focus:

```html
<a href="/path#section" up-follow up-focus="hash, target">Link label</a>
```

This first tries to focus an element matching the URL's `#hash`. If the URL has no hash (or if no element matches), the new fragment is focused.

In JavaScript you can pass an array of options:

```js
up.render({ url: '/path#section', focus: ['hash', 'target'] })
```

The [default strategy](#auto) is such a sequence.

## Conditional focusing {#condition}

To only focus when a [main target](https://unpoly.com/api/up-main.md) is updated, append `-if-main` to any string option on this page:

```html
<a href="/details" up-follow up-focus="target-if-main">Show more</a>
```

To only focus when the focus was lost with the old fragment, append `-if-lost` instead:

```html
<a href="/list" up-follow up-focus="target-if-lost">Reload list</a>
```

This focuses the new fragment, but only if the update removed the previously focused element. When the user is typing in a field outside the updated fragment, their focus is left alone.

To implement other conditions, [pass a function](#function) instead.

## Custom focus logic {#function}

To implement your own focus logic, pass a function as `{ focus }` option.

The function is called with the updated fragment and an options object. The function is expected to either:

- Focus an element. Focusing must not change scroll positions, since scrolling is governed by a separate [`{ scroll }` option](https://unpoly.com/learn/scrolling.md). Both [`Element#focus()`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/focus) and [`up.focus()`](https://unpoly.com/api/up.focus.md) accept a `{ preventScroll: true }` option to prevent scrolling.
- Return one of the focus options on this page.
- Do nothing.

Here is a focus function that focuses the first field in the new fragment:

```js
up.render({
  target: 'form',
  url: '/users/new',
  focus: (fragment) => {
    let field = fragment.querySelector('input')
    if (field) up.focus(field, { preventScroll: true })
  }
})
```

## Focusing without a render pass {#js}

Outside a fragment update, call [`up.focus()`](https://unpoly.com/api/up.focus.md) to focus any element. Unlike `Element#focus()`, it also [reveals](https://unpoly.com/learn/scrolling.md#target) the element, honoring [fixed layout elements](https://unpoly.com/learn/scroll-tuning.md#fixed-layout-elements-obstructing-the-viewport), and assigns the [focus visibility classes](https://unpoly.com/learn/focus-visibility.md) that your CSS can style:

```js
let field = document.querySelector('input[type=search]')
up.focus(field)
```

## Focus in overlays {#overlays}

### When an overlay is opened {#when-an-overlay-is-opened}

When [opening an overlay](https://unpoly.com/learn/opening-overlays.md), the first applicable strategy is used:

- Focus an `[autofocus]` element in the new overlay.
- Focus the overlay element.

To focus something else, set an `[up-focus]` attribute on the opening link:

```html
<a href="/users/new" up-layer="new" up-focus="input[name=email]">Add user</a>
```

### Focus is trapped within an overlay {#overlay-focus-trap}

By default focus is trapped for all overlays except [popups](https://unpoly.com/learn/overlays.md#layer-modes). Moving a trapped focus outside the overlay will immediately re-focus it. This prevents keyboard users from accidentally tabbing into content from other layers.

Occasionally the focus trap conflicts with overlays opened by other JavaScript libraries. You can address this by configuring [`up.layer.config.foreignOverlaySelectors`](https://unpoly.com/api/up.layer.config.md#config.foreignOverlaySelectors).

To disable focus trapping entirely, configure `up.layer.config.overlay.trapFocus = false` or open the overlay with `{ trapFocus: false }`.

### When an overlay is updated {#when-an-overlay-is-updated}

When updating a fragment within an existing overlay, all focus strategies on this page can be used.

### When an overlay is closed {#when-an-overlay-is-closed}

When a closed overlay had focus, the link that originally opened the overlay is re-focused.

If the overlay was [opened programmatically](https://unpoly.com/api/up.layer.open.md) without an `{ origin }` option, the opening link is unknown. In that case, the parent layer is focused.

<nav aria-label="Reading order">

- [Previous: Tuning the scroll behavior](https://unpoly.com/learn/scroll-tuning.md)
- [Next: Focus ring visibility](https://unpoly.com/learn/focus-visibility.md)

</nav>

