Edit this page

API Closing overlays

This guide explains the many different ways to close an overlay in Unpoly.

In addition to making the overlay disappear, you may also communicate a result value back to the parent layer. This is useful to branch off a complex user interaction into an overlay, then resume the original scenario when you're done.

Distinguishing close intents

When closing an overlay, Unpoly distinguishes between two kinds close intents:

  1. Accepting an overlay (user picks a value, confirms with "OK", etc.), optionally with a value
  2. Dismissing an overlay (user clicks "Cancel", "X", presses ESC, clicks on the background)

Different ways to close a modal overlay

Accepting an overlay will usually continue a larger interaction in the parent layer. Dismissal usually means cancellation. When you're waiting for a subinteraction to finish successfully you're interested layer acceptance, but not dismissal.

Running code when an overlay closes

When opening a layer you may pass separate { onAccepted } and { onDismissed } callbacks to handle both close intents:

up.layer.open({
  url: '/users/new',
  onAccepted: (event) => console.log('User was created'),
  onDismissed: (event) => console.log('User creation was canceled')
})

Both callbacks are optional.

In HTML you may use [up-on-accepted] and [up-on-dismissed] attributes for the same purpose:

<a href="/select-user"
  up-layer="new"
  up-on-accepted="console.log('User was created')"
  up-on-dismissed="console.log('User creation was canceled')">>
  ...
</a>

Overlay result values

Overlays in Unpoly may be closed with an optional result value. The result value is communicated back to the layer that opened the overlay. Result values can be used to augment a select or navigate to another screen.

Acceptance values

E.g. if the user selects a value, creates a record or confirms an action, we consider the overlay to be accepted with that value.

You can access the acceptance value from an { onAccepted } callback:

up.layer.open({
  url: '/select-user',
  onAccepted: (event) => console.log('Got user', event.value) // mark: onAccepted
})

The acceptance value may also be accessed from an [up-on-accepted] attribute when you're opening layers from HTML:

<a href="/select-user"
  up-layer="new"
  up-on-accepted="console.log('Got user', value)">
  ...
</a>

Dismissal reasons

When an overlay is dimissed, the result value can indicate the reason for dismissal. For instance, closing an overlay by clicking on the × symbol will dismiss with the value ":button".

The reason can be accessed from an { onDismissed } callback or [up-on-dismissed] attribute:

<a href="/select-user"
  up-layer="new"
  up-on-dismissed="console.log('Dismissal reason is', value)">
  ...
</a>

You can provide a dismissal reason when closing an overlay using up.layer.dismiss() or [up-dismiss].

If the overlay is closed using a standard dismiss control, the following default reasons are set:

User interaction Dismissal reason
User presses the Escape key ":key"
User clicks on the background ("light dismiss") ":outside"
User clicks on the × button in the overlay corner ":button"

Close conditions

When opening an overlay, you may define a condition when the overlay interaction ends. When the condition occurs, the overlay is automatically closed and a callback is run.

It is recommend to use close conditions instead of closing with explicit commands like up.layer.accept() or X-Up-Accept-Layer. By defining a close condition, the overlay content does not need to be aware that it's running in an overlay. The overlay interaction is decoupled from the interaction in the parent layer.

Closing when a location is reached

To open an overlay that closes once a URL like /companies/123 is reached, set an [up-accept-location] attribute with a URL pattern:

<a href="/companies/new"
  up-layer="new"
  up-accept-location="/companies/$id"
  up-on-accepted="alert('New company with ID ' + value.id)">
  New company
</a>

Named segments captured by the URL pattern ($id) will become the overlay's acceptance value.

To dismiss an overlay once a given location is reached, use [up-dismiss-location] and [up-on-dismissed] in the same fashion.

Closing when an event is emitted

To open an overlay that closes once a given event is observed on the overlay, set an [up-accept-event] attribute:

<a href="/users/new"
  up-layer="new"
  up-accept-event="user:created"
  up-on-accepted="alert('Hello user #' + value.id)">
  Add a user
</a>

When the user:created event is observed within the new overlay, the event's default action is prevented and the overlay is closed. The event object becomes the overlay's acceptance value.

To dismiss an overlay once a given event is observed, use the [up-dismiss-event] and [up-on-dismissed] attributes in the same fashion.

To emit an event, use one of the following methods:

Method Description
up.emit() JavaScript function to emit an event on any element
up.layer.emit() JavaScript function to emit an event on the current layer
[up-emit] HTML attribute to emit an event on click
X-Up-Events HTTP header sent from the server
Element#dispatchEvent() Standard DOM API to emit an event on an element

When an event causes an overlay to close, its default is prevented. You can use [up-emit] with a fallback URL to make a link that emits a closing event in an overlay, but navigates to a different page on the root layer.

Rendering discarded notification flashes

When an overlay closes in reaction to a server response, the content from that response is discarded. Any confirmation flashes would be lost.

The [up-flashes] element addresses this by picking up flashes from a closing overlay and rendering them into the parent layer.

Using the discarded response

When a server response causes an overlay to closes, no content from that response is rendered. Sometimes you need to access the discarded response, e.g. to render its content in another layer.

One way to achieve this is to use an [up-hungry] element with an [up-if-layer=subtree] attribute on a parent layer. A matching element would be updated with the discarded response of a closing overlay.

If you need more control, you may also access response via the { response } property of the up:layer:accepted and up:layer:dismissed events.

For example, the link link opens an overlay with a form to create a new company (/companies/new). After successful creation the form redirects to the list of companies (/companies). In that case we can use the HTML from the response and render it into the parent layer:

<a href="/companies/new"
   up-layer="new"
   up-accept-location="/companies"
   up-on-accepted="up.render('.companies', { response: event.response })"> <!-- mark: event.response -->
  New company
</a>

The { response } property is available whenever a server response causes an overlay to close:

Closing from the server

If you don't want to use close conditions, the server may explicitly close an overlay by sending an X-Up-Accept-Layer or X-Up-Dismiss-Layer header. Optionally the header may transport a value.

When you're using the unpoly-rails gem, you may produce these headers with up.layer.accept(value) or up.layer.dismiss(value).

The server may also test if the fragment change is targeting an overlay by looking at the X-Up-Mode header.

When an overlay closes in reaction to a server response, you can access the discarded response.

Closing from JavaScript

If for some reason you cannot use a close condition, you may call up.layer.accept() to explicitly accept a layer from JavaScript:

up.layer.accept()

To accept with a result value, pass it as an argument:

up.layer.accept({ name: 'Anna', email: 'anna@domain.tld' })

To dismiss an overlay from JavaScript, use the up.layer.dismiss() function in the same fashion.

Closing when a button is clicked

To close the current layer when a button is clicked, use an [up-accept] or [up-dismiss] attribute:

<button up-accept>Close overlay</button> <!-- mark: up-accept -->

To close with a result value, set a relaxed JSON on the attribute value:

<button up-dismiss="{ id: 5 }">Choose user #5</button> <!-- mark: { id: 5 } -->

Closing when a link is followed

When reusing a page within an overlay it can be useful to have an element that closes the overlay when clicked, but navigates somewhere else on the root layer.

In this case you can use a hyperlink (<a>)with a fallback URL in its [href] attribute:

<a href="/list" up-accept>Finish</a>

Unpoly will only navigate to /list when this link is clicked in the root layer. In an overlay the click event is prevented and the overlay is accepted.

Closing when a form is submitted

To close an overlay when a form is submitted, set an [up-accept] or [up-dismiss] attribute on the <form> element. This will immediately close the overlay on submission, without making a network request:

<form up-accept> <!-- mark: up-accept -->
  ...
</form>

Accessing the form params

The form's field values will become the result value of the closed overlay.

For example, this form has two fields named foo and bar:

<form up-accept>
  <input name="foo" value="1"> <!-- mark: foo -->
  <input name="bar" value="2"> <!-- mark: bar -->
</form>

The values are provided as an up.Params object that can be accessed from an { onAccepted } or { onDismissed } handler:

up.layer.open({
  url: '/form',
  onAccepted: ({ value }) => {
    console.log(value.get('foo')) // result: "1"
    console.log(value.get('bar')) // result: "2"
  }
})

Regular submission on the root layer

When a form with [up-accept] or [up-dismiss] is submitted from within an overlay, the submit event is prevented and no request is made.

When the same form is submitted from the root layer, there is no overlay to close. In this case a regular form submission (e.g. POST request) is made. This can be useful when reusing an existing form within an overlay.

Closing after the submission request

When you do want the form to submit a request before the overlay closes, do not set an [up-accept] or [up-dismiss] attribute. Instead make a regular form that is handled by Unpoly:

<form up-submit> <!-- mark: up-submit -->
  ...
</form>

After the server has processed the request, it can use any of the following techniques to close the overlay:

Closing by targeting the parent layer

When a link or form targets a parent layer, the current layer will dismiss when the parent layer is updated. This behavior is called peeling.

The example below uses an [up-layer] attribute to update the parent layer after a successful form submission:

<form method="post" action="/users" up-layer="parent">
  <input type="text" name="email">
  <button type="submit">Create user</button>
</form>

A successful submission will now dismiss the form's own overlay with a dismissal value of ":peel".

Note

The form will still update its own layer when the server responds with an error code due to a validation error. To target another layer in this case, set an [up-fail-layer] attribute.

Customizing dismiss controls

By default the user can dismiss an overlay user by pressing Escape, by clicking outside the overlay box or by pressing an × icon in the top-right corner.

You may customize the dismiss methods available to the user by passing a { dismissable } option or [up-dismissable] attribute when opening an overlay. For example, the link below will open an overlay that has a close button (×), but cannot dismissed by pressing Escape or clicking on the background:

<a href="/terms" up-layer="new" up-dismissable="button">Show terms</a>

The following control names are available:

Control name Effect Dismiss value
key Enables dimissing with Escape key ":key"
outside Enables dismissing by clicking on the background ":outside"
button Adds a close button (×) to the layer ":button"

To enable multiple dismiss controls, separate their name by a comma or space character:

<a href="/terms" up-layer="new" up-dismissable="button, key">Show terms</a>

Regardless of which dismiss controls are enabled, an overlay may always be dismissed by using the up.layer.dismiss() method or [up-dismiss] attribute.

Customizing the dismiss icon

Most overlay modes have an button (×) in the top-right corner that dismisses the dialog.

You may change the symbol and accessibility label for that icon:

up.layer.config.overlay.dismissLabel // result: '×'
up.layer.config.overlay.dismissARIALabel // result: 'Dismiss dialog'

Close animation

The overlay element's disappeared will be animated using the layer mode's configured { closeAnimation }.

To use a different animation, pass an { animation } option to the various close methods.

Using overlays as promises

Instead of using up.layer.open() and passing callbacks, you may use up.layer.ask(). up.layer.ask() returns a promise for the acceptance value, which you can await:

let user = await up.layer.ask({ url: '/users/new' })
console.log('Got user:', user)

If the overlay is dismissed instead of accepted, the promise will be rejected with the dismissal value:

try {
  await up.layer.ask({ url: '/users/new' })
} catch (reason) {
  console.log('Overlay was dismissed:', reason)
}