Instead of updating the current page, any link or form can open content in a new overlay, like a modal or a drawer. The linked screen stays a regular page on your server, and the page behind the overlay keeps its state.
To open a link's destination in a modal overlay, set an [up-layer=new] attribute:
<a href="/menu" up-layer="new">Open menu</a> <!-- mark: up-layer="new" -->
When the user clicks, Unpoly requests /menu and shows the response's main fragment
in the overlay. Your server renders the same full page it would for a regular page load,
so the link keeps working in a new tab or without JavaScript.
Often the page's main fragment works well in an overlay as it is. When it doesn't, use the first of these options that fits:
Mark a different main fragment for overlays.
Set [up-main=overlay] for all overlays, or name a mode like [up-main=modal]:
<body>
<div class="layout" up-main="root"> <!-- mark: up-main="root" -->
<nav class="layout--side">...</nav>
<div class="layout--content" up-main="overlay"> <!-- mark: up-main="overlay" -->
...
</div>
</div>
</body>
The root layer keeps updating the whole .layout, while overlays only show .layout--content.
Choose a fragment for a single link with an [up-target] attribute:
<a href="/menu" up-layer="new" up-target=".menu-items">Open menu</a> <!-- mark: up-target=".menu-items" -->
Render something different on the server. Requests for an overlay carry an
X-Up-Mode header with the overlay's mode.
When the server responds with an error code, the error page is rendered in
the link's own layer, not in an overlay. To show errors in an overlay, set
[up-fail-layer=new].
When the user clicks a second overlay link while the first is still loading, only the second overlay opens. See aborting rules in layers.
By default the overlay opens as a modal. To use a different mode,
like a popup or drawer, set an [up-mode] attribute:
<a href="/menu" up-layer="new" up-mode="drawer">Open menu</a> <!-- mark: up-mode="drawer" -->
As a shorthand, you can append the mode to the [up-layer=new] attribute:
<a href="/menu" up-layer="new drawer">Open menu</a> <!-- mark: up-layer="new drawer" -->
These modes are available:
| Mode | Description |
|---|---|
modal |
A box centered over a dimmed backdrop |
drawer |
A drawer sliding in from the side |
popup |
A popup menu anchored to a link |
cover |
An overlay covering the entire screen |
You can change the default mode in up.layer.config.mode.
To show the response to a successful form submission in an overlay, set an [up-layer=new] attribute on the form:
<form method="post" action="/subscribers" up-layer="new"> <!-- mark: up-layer="new" -->
...
</form>
To only use an overlay for error messages from a failed submission,
set an [up-fail-layer=new] attribute instead:
<form method="post" action="/subscribers" up-fail-layer="new"> <!-- mark: up-fail-layer="new" -->
...
</form>
The [up-content] attribute opens an overlay without going through the server:
<a up-layer="new popup" up-content="<p>Helpful instructions here</p>"> <!-- mark: up-content="<p>Helpful instructions here</p>" -->
Help
</a>
Since no [up-target] selector is given, the content is wrapped in a container matching the overlay's main target.
Instead of embedding an HTML string, you can also refer to a template:
<a up-layer="new popup" up-content="#help"> <!-- mark: up-content="#help" -->
Help
</a>
<template id="help"> <!-- mark: id="help" -->
<p>Helpful instructions here</p>
</template>
When rendering from JavaScript, pass { layer: 'new' } to open the fragment in a new overlay:
up.navigate({ url: '/users/new', layer: 'new' }) // mark: layer: 'new'
Instead of up.navigate() or up.render() you can also call up.layer.open().
It returns a promise for the new up.Layer instance:
let layer = await up.layer.open({ url: '/users/new' })
Choose a different layer mode by passing a { mode } option:
up.layer.open({ url: '/menu', mode: 'drawer' }) // mark: mode: 'drawer'
To get a promise for the overlay's result value instead,
use up.layer.ask():
let address = await up.layer.ask({ url: '/address-picker' })
The promise fulfills when the overlay is accepted, and rejects when it is dismissed. See Subinteractions: Awaiting subinteractions from JavaScript.
The server can force its response to open an overlay by sending an X-Up-Open-Layer: {} response header.
This opens an overlay with the default mode, selects a main target and uses default navigation options:
Content-Type: text/html
X-Up-Open-Layer: {}
<html>
<main>
Overlay content
</main>
</html>
To customize the target and appearance of the new overlay, set the header value to a relaxed JSON string with render options:
Content-Type: text/html
X-Up-Open-Layer: { target: '#menu', mode: 'drawer', animation: 'fade-in' }
<div id="menu">
Overlay content
</div>
Many options from up.layer.open() are supported. Callbacks can be passed as strings.
By default the new overlay is stacked on top of the current layer. There is no limit to the number of stacked layers.
To replace existing overlays instead, set [up-layer="swap"] or [up-layer="shatter"]:
| Option | Description |
|---|---|
new |
Stacks a new overlay over the current layer. |
swap |
Replaces the current overlay. If no overlay is open, opens an overlay. |
shatter |
Closes all overlays and opens a new overlay. |
Tip Your coding agent can search these docs locally with the Unpoly agent skill