Odo Dialog

Responsive window dialog component focused on a11y and extendibility


IE9+ with polyfills.

To support IE<=11, you will need to add classList and Element#closest polyfills.


Odo Helpers


This component has required css - css/odo-dialog.css.

Default Dialogs

By default, open dialogs have all the content centered within the screen, as well as have a fade animation for opening/closing.

The default, unstyled dialog

A styled dialog

See the demo.css for the styles.

<button data-odo-dialog-open="default">Open the default dialog</button>

<!-- Could be elsewhere in your markup -->
<div class="odo-dialog" id="default" role="dialog" aria-labelledby="default-title" aria-hidden="true">
  <div class="odo-dialog__content" role="document">
    <div class="odo-dialog__inner">
      <h2 id="default-title">Cat Ipsum</h2>
      <p>Generate some placeholder text with <a href="http://www.catipsum.com/" target="_blank" rel="noopener">Cat Ipsum</a>!</p>
      <p>Hunt by meowing loudly at 5am next to human slave food dispenser scream at teh bath. Friends are not food. Mew love to play with owner's hair tie brown cats with pink ears wake up human for food at 4am. Sit in window and stare ooo, a bird! </p>
      <button type="button" class="odo-dialog__close" data-odo-dialog-close aria-label="Close this dialog window">&times;</button>
const defaultDialog = new OdoDialog(document.getElementById('default'));

Public Events

The opened event is triggered when the content has finished its transition in, while the closed event triggers once the dialog has completely transitioned out. In case you need to retrieve some information from the button which triggered the dialog to open, you can subscribe to the TRIGGER_CLICKED event.

defaultDialog.on(OdoDialog.EventType.OPENED, function () {
  console.log('default dialog opened');

defaultDialog.on(OdoDialog.EventType.CLOSED, function () {
  console.log('default dialog closed');

defaultDialog.on(OdoDialog.EventType.TRIGGER_CLICKED, function (triggerElement) {
  console.log('dialog about to open because you clicked:', triggerElement);

Public Methods

defaultDialog.open() // Open the dialog
defaultDialog.close() // Close the dialog
defaultDialog.dispose() // Close the dialog, remove event listeners and element references
defaultDialog.getByClass(className) // Find a single element by class inside the dialog

Public Properties

defaultDialog.element // Base element
defaultDialog.options // options object
defaultDialog.id // Unique id for the dialog. Same as the id attribute of the base element
defaultDialog.isOpen // Whether the dialog is currently open

Protected Methods & Properties

If you create a subclass of OdoDialog, you will have access to these as well*.

defaultDialog.onClick(evt) // Delegated click handler on the dialog.
defaultDialog.onKeyPress(evt) // Listens for the ESC key and traps the TAB key.
defaultDialog.onResize(viewportHeight) // Sets the height of the dialog on viewport resize.
defaultDialog.content // Dialog content (role=document)
defaultDialog.backdrop // The background behind the dialog
defaultDialog.isAnimating // Whether the dialog is currently animating (opening/closing)

*Because this is JavaScript, you have access to them already.

Changing Transitions

In addition to the default fade/scale transition, OdoDialog comes with two other transitions: odo-dialog--fade and odo-dialog--zoom-in. The other options in the <select> below are custom transitions added to this page. See the next section for details.

Add Custom Transitions

You can add your own transitions too.

OdoDialog follows a similar transition sequence to Vue.js transition effects.

  1. .odo-dialog element is display: none
  2. Something calls open() (either programatically or clicking a trigger)
  3. The odo-dialog--open class is added to remove display: none and the odo-dialog--enter class is added.
  4. On the next frame, odo-dialog--enter is removed and odo-dialog--enter-active is added.
  5. When the transition on the dialog finishes, odo-dialog--enter-active is removed.

When the dialog closes, these steps are performed [mostly] in reverse order.

.odo-dialog--fall-through {
  transition-timing-function: cubic-bezier(0.19, 1, 0.22, 1);

.odo-dialog--fall-through.odo-dialog--leave-active {
  opacity: 0;

.odo-dialog--fall-through.odo-dialog--enter {
  transform: translateY(-30vh);

.odo-dialog--fall-through.odo-dialog--leave-active {
  transform: translateY(30vh);


You can create a dialog which is not dismissable with the dismissable: false option. When the user presses the ESC key, nothing will happen. You should also remove data-odo-dialog-close attribute from the backdrop and don't add a close button.

const modal = new OdoDialog(document.getElementById('modal-dialog'), {
  dismissable: false,

const button = modal.element.querySelector('button');
button.addEventListener('click', () => {

Using a different scrollable section

OdoDialog internally uses a component called ScrollFix. This component prevents mobile browsers from scrolling the page behind the dialog when the scrollable region is at the top or bottom of the scrollable area. In order to make a different element scrollable, the ScrollFix must be applied to that new region instead of the original main element.

You can choose a different region to be scrollable with the scrollableElement option. Give it a selector, like: scrollableElement: '.my-list'. Setting scrollableElement to null will disable ScrollFix completely.


Add the .odo-dialog--full class to the main element to make it take up the whole screen. It sets the width and height of .odo-dialog__content and .odo-dialog__inner elements to inherit.

<div class="odo-dialog odo-dialog--full odo-dialog--fade" id="fullscreen" aria-hidden="true">…</div>

Global Dialog Instantiation

On a project, you may have different dialogs, but they are all of the same type. Instead of initializing each one individually, you can run the initializeAll() method, which accepts the same options as instantiating a dialog individually, but applies them to all dialogs. It will return an array of OdoDialog instances.


Dialog Positioning

The dialog element is display: flex and the content element has margin: auto. This centers the content element within the viewport.

Unfortunately, the auto margin does not work in IE11 and below because the content element does have an explicit height set on it. In this case, a class of odo-dialog--no-auto-margin is added to the main element and OdoDialog.SUPPORTS_AUTO_MARGINS will be false. If you need to provide a fallback for centering in IE11, you can use either of those hooks.

This gist could provide you with a starting point.

Extending OdoDialog

OdoDialog is built to be extended. Let's create a dialog which closes after scrolling it mostly out of view. Check the demo.js for the compiled code or GitHub for the original ES6 version.

Dialogs inside dialogs

Dialogs can have buttons inside them which open another dialog without closing the current one.