Jolty home Jolty Documentation

Dialog (Modal, Popup)

Create alert dialogs, prompt messages, design your own templates, and add animations with a Dialog component.
<button type="button" data-ui-toggle="my-dialog" class="btn btn--md btn--primary">
  Edit address
</button>
 
<dialog class="dialog" data-ui-dialog id="my-dialog" hidden>
  <div data-ui-dialog-backdrop class="dialog-backdrop"></div>
  <div data-ui-dialog-content class="dialog-content">
    <button type="button" class="btn btn--close" data-ui-dismiss aria-label="Close">
      <svg
        xmlns="http://www.w3.org/2000/svg"
        viewBox="0 0 24 24"
        width="24"
        height="24"
        stroke-width="2"
        stroke="currentColor"
      >
        <path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12"></path>
      </svg>
    </button>
    <div data-ui-dialog-title class="dialog-title">Edit address</div>
    <div data-ui-dialog-description class="dialog-description">
      You can change your address here
    </div>
    ...
  </div>
</dialog>

Getting started

To begin, create a <dialog> with the attribute data-ui-dialog containing nested elements as shown in the example below, and a <button> with the data-ui-toggle attribute, specifying the dialog’s id.

html
<button data-ui-toggle="my-dialog">Open modal</button>
 
<dialog data-ui-dialog id="my-dialog" hidden>
  <div data-ui-dialog-backdrop></div>
  <div data-ui-dialog-content>
    <button data-ui-dismiss aria-label="Close"></button>
    <h2 data-ui-dialog-title>My title</h2>
    <div data-ui-dialog-description>My description</div>
  </div>
</dialog>

Then, add your styles for the dialog to ensure it is correctly positioned.

css
.dialog {
  position: fixed;
  inset: 0;
  z-index: 999;
  display: flex; /* Required property with any value other than display: none; */
}
.dialog-backdrop {
  position: absolute;
  inset: 0;
  z-index: -1;
  background: rgba(0, 0, 0, 0.5);
}
.dialog-content {
  margin: auto;
}

Then, initialize all elements with the data-ui-dialog attribute.

js
import { Dialog } from "jolty";
Dialog.initAll();

Animation

To animate the dialog, you can use special CSS classes added during the entering and leaving state for the data-ui-dialog-content element. For the data-ui-dialog-backdrop element, the CSS class .ui-active is added.

css
.dialog-backdrop {
  transition: 200ms ease-in-out;
  &:not(.ui-active) {
    opacity: 0;
    visibility: hidden;
  }
}
.dialog-content {
  &.ui-enter-active,
  &.ui-leave-active {
    transition: 200ms ease-in-out;
  }
  &.ui-enter-from {
    opacity: 0;
    transform: translateY(1.5rem);
  }
  &.ui-leave-to {
    opacity: 0;
    transform: translateY(-1.5rem);
  }
}

Hash navigation

If you want the dialog to open when the URL contains a hash with its id, you can enable the hashNavigation option.

Also, add the appear option so that it opens with an animation.

html
<dialog data-ui-dialog data-ui-hash-navigation data-ui-appear id="my-dialog" hidden>...</dialog>

Varying content

Let’s create two buttons. When clicked, we’ll fetch data from their attributes and pass it to the dialog.

html
<button data-whatever="one" data-ui-toggle="my-dialog">Open for one</button>
<button data-whatever="two" data-ui-toggle="my-dialog">Open for two</button>
 
<dialog data-ui-dialog id="my-dialog" hidden>
  <div data-ui-dialog-backdrop></div>
  <div data-ui-dialog-content>
    <button data-ui-dismiss aria-label="Close"></button>
    <h2 data-ui-dialog-title>You've clicked</h2>
    <div data-output></div>
  </div>
</dialog>

We will do this via the 'ui-dialog:show' event, where detail passes an Array with an instance and an object {event, trigger}. The event value contains the native button click event, and trigger refers to the button that invoked it.

js
const dialogElem = document.getElementById("my-dialog");
dialogElem.addEventListener("ui-dialog:show", ({ detail: [instance, { trigger }] }) => {
  dialogElem.querySelector("[data-output]").textContent = trigger.dataset.whatever;
});

Alternatively, you can listen to the event on the document. This can be turned off by setting {eventBubble: false}.

js
document.addEventListener("ui-dialog:show", ({ detail: [{ id, dialog }, { trigger }] }) => {
  if (id === "my-dialog") {
    dialog.querySelector("[data-output]").textContent = trigger.dataset.whatever;
  }
});

Data

When we have many dialogs that behave similarly, and we don’t want to configure each one separately, we can set common settings for them using the static method Dialog.data().

html
<dialog data-ui-dialog="feedback" data-ui-hash-navigation data-ui-appear id="my-dialog" hidden>
  ...
</dialog>
js
Dialog.data("feedback", (popoverElem) => {
  return {
    hashNavigation: true,
    appear: true,
  };
});
 
Dialog.initAll();

If the group is not declared through Dialog.data(), then the dialog won’t be initialized after calling Dialog.initAll().

Also, if an empty string '' is passed instead of a name or skip it, then the provided settings will apply to all dialogs with the data-ui-dialog attribute where no name is specified.

js
Dialog.data({});
// or
Dialog.data((dialogElem) => ({}));

Prevent close

Let’s examine another example where we need to prevent a window from closing if certain conditions aren’t met.

For this, create a dialog with two response buttons to a question, and if the user chooses the incorrect answer, we won’t close the dialog, adding a slight animation instead.

html
<button data-ui-toggle="my-dialog">Open modal</button>
 
<dialog data-ui-dialog="question" id="my-dialog" hidden>
  <div data-ui-dialog-backdrop></div>
  <div data-ui-dialog-content>
    <div data-ui-dialog-title class="font-medium">Choose the correct answer</div>
    <div data-ui-dialog-description>How much is 2 + 3 = ?</div>
    <button data-answer="5" data-ui-dismiss>5</button>
    <button data-asnwer="8" data-ui-dismiss>8</button>
  </div>
</dialog>

For this this, we need to use the preventHide option. Let’s assign it a function, which will receive an instance and an object {event, trigger}. The event value contains the native button click event, and trigger refers to the button that invoked it.

If our condition is met, return true, which means the dialog can be closed; if not, animate the content element.

js
function shake(elem) {
  const keyframes = [
    { translate: 0 },
    { translate: "-.5rem" },
    { translate: ".5rem" },
    { translate: "-.5rem" },
    { translate: ".5rem" },
    { translate: 0 },
  ];
  const options = { duration: 400, easing: "ease" };
  return elem.animate(keyframes, options);
}
 
Dialog.data("question", {
  preventHide(instance, { trigger }) {
    if (trigger?.dataset?.answer == 5) {
      return true;
    } else {
      shake(instance.content);
    }
  },
});
 
Dialog.initAll();

Group

Let’s expand our example by adding another dialog that will open when the correct answer is chosen and will close the previous dialog.

To do this, assign the same group and backdrop to them, after first moving the backdrop to the <body>.

When the backdrop is not inside the dialog, you don’t need to use the data-ui-backdrop attribute.

html
<div id="question-backdrop"></div>
 
<dialog
  data-ui-dialog
  data-ui-group="question-group"
  data-ui-backdrop="#question-backdrop"
  id="correct-answer-dialog"
  hidden
>
  <div data-ui-dialog-content>
    <div data-ui-dialog-title>Correct!</div>
    <div data-ui-dialog-description>You are a very smart person</div>
    <button data-ui-dismiss>Yes, I knew it!</button>
  </div>
</dialog>

Now, let’s trigger a dialog after choosing the correct answer.

js
Dialog.data("question", {
  group: "question-group",
  backdrop: "#question-backdrop",
  preventHide(instance, { trigger }) {
    if (trigger?.dataset?.answer == 5) {
      const dialogCorrect = Dialog.get("correct-answer-dialog");
      dialogCorrect.show();
      dialogCorrect.returnFocusElem = dialog.returnFocusElem;
      return true;
    } else {
      shake(instance.content);
    }
  },
});

Confirm

Certain types of dialogs, such as alert, confirm, prompt, etc., that expect user interaction are more conveniently generated on-the-fly. For this, we pass a template as the first argument to the constructor.

Let’s create a function dialogConfirm() that will return a Promise with the result of the user’s action.

Also, enable the autodestroy option, so the dialog is removed after closing.

js
function dialogConfirm({ title }) {
  const template = `
    <dialog>
      <div data-ui-dialog-backdrop></div>
      <div data-ui-dialog-content>
          <div data-ui-dialog-title>${title}</div>
          <button data-ui-confirm>Confirm</button>
          <button data-ui-dismiss>Cancel</button>
      </div>
    </dialog>
  `;
 
  return new Promise((resolve) => {
    new Dialog(template, {
      autodestroy: true,
      on: {
        confirm(instance) {
          resolve(true);
          instance.hide();
        },
        cancel(instance) {
          resolve(false);
          instance.hide();
        },
      },
    });
  });
}

Now, when clicking on a button, we create a dialog and await the user’s action. After that, we display the result in the adjacent element.

js
const toggler = document.querySelector("#some-button");
toggler.addEventListener("click", async ({ target }) => {
  let result = await dialogConfirm({ title: "Confirm" });
  target.nextElementSibling.textContent = "Confirmed: " + result;
});

Options

NameTypeDefaultDescription
initBooleantrueShould the instance be automatically initialized when an instance is created? If false, you’ll need to manually initiate it by calling dialog.init().
destroyBooleanfalseAllows you to destroy an instance at a specific breakpoint.
dataString''Allows you to use the default options that have been added through the Dialog.data() static method.
onObjectnullUsed to register event handlers.
appearBooleannullIf you want to apply a transition upon initialization as well, you can set this option to true. Attribute: data-ui-appear
eventPrefixString'ui-dialog:'Prefix for events dispatched on the dialog element.
eventDispatchBoolean, EventNametrueDefines if events are dispatched or used only within options.
eventBubbleBoolean, EventNametrueDefines if events should bubble up the DOM.
breakpointsObjectnullDefines custom options for specific breakpoints.
shownBooleannullDefines if the dialog is shown after initialization. By default, it’s null, which means it checks the hidden attribute or another attribute, depending on hideMode.
escapeHideBooleantrueDefines whether the element should close when the Esc key is pressed.
backdropHideBoolean, ObjecttrueDefines whether the element should close when the backdrop is clicked. You can also pass an object with the option {rightClick: false}, which will disable closing the dialog when the right mouse button is clicked on the backdrop.
returnFocusBooleantrueDefines whether focus should return to the element that triggered it. You can also pass an object with the option {await: true}, which indicates that it should wait for the end of the transition to return focus.
preventHideBoolean, FunctionfalseAllows you to prevent the dialog from closing, can take a boolean value or an asynchronous function, into which the instance is passed at the moment of closure and the second option is an object {trigger, event}.
preventScrollBooleantrueDefines whether the CSS class .ui-prevent-scroll should be added to the <html> element when the dialog is opened.
Use thee --ui-root-scrollbar-width CSS variable to prevent content shifts when the scrollbar disappears with overflow: hidden. Attribute: data-ui-prevent-scroll
confirmCSSSelector, Element, Element'[data-ui-confirm]'Sets the element, clicking on which triggers the confirm event.
titleCSSSelector, Element'[data-ui-dialog-title]'Sets the element that will be linked to the dialog via the aria-labelledby attribute.
descriptionCSSSelector, Element'[data-ui-dialog-description]'Sets the element that will be linked to the dialog via the aria-describedby attribute.
contentCSSSelector, Element'[data-ui-dialog-content]'Sets the element that can be animated through Transition and outside whose click the dialog closes if the backdropHide: true option is set.
backdropCSSSelector, Element'[data-ui-dialog-backdrop]'Searches for a CSS Selector within dialog that can be used as a backdrop, if you pass '#id', it will return the element with the corresponding id, regardless of whether it is inside or outside the dialog. Attribute: data-ui-backdrop
modalBooleantrueOnly works when the component has a <dialog> tag, opens it in the modal mode and allows to trap focus within the dialog. Attribute: data-ui-modal
dialogClassActiveString'ui-active'CSS class for the dialog element when it’s open.
contentClassActiveString'ui-active'CSS class for the content element when the dialog is open.
backdropClassActiveString'ui-active'CSS class for the backdrop element when the dialog is open.
awaitAnimationBooleanfalseCan the dialog state be switched if it’s in the process of animation?
focusTrapBooleantrueCreates a focus trap within the dialog when modal is set to false or when it’s not a <dialog> element. In this case, the focus is not as strict as with modal and only affects keyboard navigation with Tab and Shift+Tab.
togglerBoolean, CSSSelector, ElementtrueDefines the toggler. By default it’s true which calls ({id}) => '[data-ui-toggle~="id"],[href="#id"]'.
hashNavigationBooleantrueDefines whether to show the dialog during initialization if the URL hash is equal to the id. Attribute: data-ui-hash-navigation
dismissBoolean, CSSSelectortrueAllows the dialog to be hide when clicked on the button. By default, it’s true, meaning the hide() method will be called when '[data-ui-dismiss=""],[data-ui-dismiss="dialog"]' is clicked.
autofocusBoolean, CSSSelector, ElementtrueDefines the element that should be focused when the dialog is opened. By default it’s true, meaning the '[autofocus],[data-ui-autofocus]' element will be focused.
autodestroyBooleanfalseDefines whether the instance should be destroyed after closing. Attribute: data-ui-autodestroy
topLayerBooleantrueDetermines whether the dialog should be displayed in the top layer, Attribute: data-ui-top-layer
rootElement, CSSSelector'body'Defines the root element for the dialog.
moveToRootBooleantrueMoves the dialog to <body> when it’s opened. Attribute: data-ui-move-to-root
a11yBooleantrueIf the component doesn’t have a <dialog> tag, attributes role="dialog" and tabindex="-1" will be added.
hideModeString'hidden'Accepts one of the next values 'hidden','hidden-until-found','inert','class-hidden','class-shown','remove' Attribute: data-ui-hide-mode
keepPlaceBooleantrueDetermines if the component’s space is preserved when removed by hideMode: 'remove'.

Hide mode

The dialog can be hidden in several ways using the hideMode option.

ModeHidden stateDescription
'hidden'[hidden]Default mode, hides through display: none;
'hidden-until-found'[hidden="until-found"]The element will show when a match is found in the site search. Read more
'inert'[inert]Makes the element non-interactive, but remains visible
'class-hidden'.ui-hiddenThe element remains fully interactive,
adds the .ui-hidden class when hidden
'class-shown'-The element remains fully interactive,
adds the .ui-shown class when shown
'remove'-The element is removed from the DOM.
Add the hidden attribute to hide by default

Group

NameTypeDefaultDescription
groupString, GroupOptions''Object with group options or string to set the name option for the group. Attribute: data-ui-group
nameString''Defines the name for the group.
awaitPreviousBooleantrueDefines whether a dialog in the same group should wait for the previous one to close before opening.
hidePreviousBooleantrueShould the previous dialog be closed if it is in the same group?

Transition

AttributeTypeDefaultDescription
transitionString, Boolean, TransitionOptionstrueObject with transition options or string to set the name option for the transition.
nameString'ui'Defines the name of the transition that will be used to apply CSS rules. Attribute: data-ui-transition-name
cssBooleantrueDefines if the transition should be applied using CSS rules.
cssVariablesBooleantrueAdds special CSS variables during the transition to the content element and removes them after completion.
enterFunctionnullAccepts a function enter(el, done){}. call the done() callback to indicate transition end
enterActiveString, Object''CSS classes or styles applied during the entire entering phase. Attribute: data-ui-enter-active
enterFromString, Object''CSS classes or styles applied at the start of the entering phase. Attribute: data-ui-enter-from
enterToString, Object''CSS classes or styles applied at the end of the entering phase. Attribute: data-ui-enter-to
leaveFunctionnullAccepts a function leave(el, done){}. call the done() callback to indicate transition end
leaveActiveString, Object''CSS classes or styles applied during the entire leaving phase. Attribute: data-ui-leave-active
leaveFromString, Object''CSS classes or styles applied at the start of the leaving phase. Attribute: data-ui-leave-from
leaveToString, Object''CSS classes or styles applied at the end of the leaving phase. Attribute: data-ui-leave-to
durationNumbernullDefines the duration of the transition. Should be used only when the animation occurs on child elements.

If the cssVariables option is enabled, special CSS variables are added during the transition to content element and are removed after its completion.

NameTypeDescription
--ui-transition-widthNumberThe width of the content element at the start of the transition.
--ui-transition-heightNumberThe height of the content element at the start of the transition.

Methods

NameReturnDescription
init(options)instanceInitializes the component.
destroy(params)instanceDestroys the component and accepts an object as a option { remove: false, keepInstance: false, keepState: false }.
update(options)instanceAccepts options as an argument and updates the component.
toggle(params, force)promiseToggles the component’s visibility state between shown and hidden. Accepts true or false as a option, which sets the animated option or an object { animated: true, silent: false }.
show(params)promiseOpens the component and accepts the same options as the toggle() method.
hide(params)promiseCloses the component and accepts the same options as the toggle() method.

Class Methods

NameReturnDescription
toggle(id, params, force)promiseSearches for an instance by id and calls its toggle() method with the specified options.
show(id, params)promiseSearches for an instance by id and calls its show() method with the specified options.
hide(id, params)promiseSearches for an instance by id and calls its hide() method with the specified options.
data(name, options)ClassSets default options for components that have the property data:'name' or through the attribute data-ui-dialog="name".
updateDefault(options)defaultUpdates default options.
initAll(root)instanceSearches for elements in root, which defaults to document with the attribute data-ui-dialog and initializes them.
get(id or elem)instanceSearches for an instance by id or base element.
getOrCreate(id or elem, options)instanceSearches for an instance by id or base element, if not found - creates a new instance with specified options.

Properties

NameTypeDescription
idStringThe id of the base / dialog element.
isInitBooleanIndicates whether the instance is already initialized.
optsObjectContains the currently applied options for the current breakpoint.
baseOptsObjectContains all options, including the breakpoints property.
base, dialogElementAll components have a base property, which refers to the element through which the component is initialized and where events are fired. dialog is a synonym for base.
contentElementRefers to the element, which is set in the content option.
backdropElementRefers to the element, which is set in the backdrop option.
titleElementRefers to the element, which is set in the title option.
descriptionElementRefers to the element, which is set in the description option.
returnFocusElemElementRefers to the element, to which the focus will be returned after closing the dialog.
groupDialogsDialogInstance[]Array of all dialogs in the same group. See group option.

Class properties

NameTypeDescription
DefaultObjectContains the default options for the component.
DefaultGroupObjectContains the default options for the group option.
instancesMapContains all instances of the component.

Events

By default, events that are listened to directly through an element have the prefix 'ui-dialog:'. This can be changed through the eventPrefix property. Additionally, you can disable the bubbling of these events using eventBubble or turn them off entirely via eventDispatch.

Dialog.data("my-dialog", {
  on: {
    shown(instance, { trigger, event }) {
      // do something...
    },
    any(eventName, instance, { trigger, event }) {
      if (eventName === "shown") {
        // do something...
      }
    },
  },
});
 
// or directly on the dialog element
const dialogElem = document.querySelector(".my-dialog");
dialogElem.addEventListener("ui-dialog:show", (e) => {
  const [instance, { trigger, event }] = e.detail;
  // do something...
});
 
// or on the document
document.addEventListener("ui-dialog:show", (e) => {
  const [instance, { trigger, event }] = e.detail;
  // do something...
});
NameArgumentsDescription
beforeInitinstanceEvent will fired right before initialization.
initinstanceFires when initialization has been completed.
beforeShowinstance, {trigger, event}Fires immediately when the show() method is called.
showinstance, {trigger, event}Fires when the element becomes visible, but the CSS transition hasn’t started yet.
showninstance, {trigger, event}Fires when the CSS transition hasn’t been completed.
confirminstance, {trigger, event}Fires when the confirm button is clicked.
cancelinstance, {trigger, event}Fires when the dialog was closed by user.
beforeHideinstance, {trigger, event}Fires immediately when the hide() method is called.
hideinstance, {trigger, event}Fires just before the CSS transition starts.
hidePreventedinstance, {trigger, event}Fires when the dialog is prevented from closing.
hiddeninstance, {trigger, event}Fires when the CSS transition has been completed.
beforeDestroyinstanceFires before the instance is destroyed.
destroyinstanceFires immediately when the destroy() method is called.
breakpointinstance, breakpoint, prevBreakpointFires when the breakpoint has been changed.
anyeventName, instanceFires on any event occurrence. The first argument contains the name of the event.
2023 © A Project by Jolty Labs 🇪🇸 🇺🇦