Jolty UI Jolty UI Documentation Icons Colors

Dialog (Modal, Popup)

Create customizable alert dialogs and prompt messages. Design templates and add animations for confirmations, alerts, and form submissions.

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">Show dialog</button>
 
<dialog data-ui-dialog class="ui-dialog" id="my-dialog" hidden>
  <div data-ui-dialog-title class="ui-dialog-title">Dialog title</div>
  <div data-ui-dialog-description>Some contents...</div>
  <button data-ui-dismiss class="ui-btn-close ui-dialog-close" aria-label="Close"></button>
  ...
</dialog>

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

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

By default, the Dialog has an option modal:true, which opens the <dialog> element in modal mode, meaning it does not allow interaction with content outside its boundaries.

From an accessibility standpoint, this is the correct behavior. However, if some Third-Party Solutions are placed outside of such a dialog, for example in the <body>, they will be non-interactive.

To fix this, use a .dialog-root wrapper with a <div> tag and transfer all necessary attributes to it, and add the data-ui-dialog-content attribute to .dialog.

Also, add the data-ui-dialog-backdrop element, which will replace the native ::backdrop pseudoclass.

html
<div class="ui-dialog-root" data-ui-dialog id="my-dialog" hidden>
  <div class="ui-dialog-backdrop" data-ui-dialog-backdrop></div>
  <div class="ui-dialog" data-ui-dialog-content>
    <div data-ui-dialog-title class="ui-dialog-title">Dialog title</div>
    <div data-ui-dialog-description class="ui-dialog-description">Some contents...</div>
    <button data-ui-dismiss class="ui-btn-close ui-dialog-close" aria-label="Close"></button>
    ...
  </div>
</div>

Non-modal

If you don’t want the dialog to block user interaction outside of it, set the modal:false option or use the data-ui-modal="false" attribute.

html
<dialog data-ui-dialog data-ui-modal="false" hidden>...</dialog>

Disabling this option deactivates other options if their value is set to 'auto', such as lightDismiss, backDismiss, focusTrap, and preventScroll.

Size

By default, the dialog has a width that depends on the content width, you can change it as you like, or use one of the three predefined sizes:

Class nameDescriptionDemo
Sets the width to fit-content.
.dialog--xsSets the width to 16rem.
.dialog--smSets the width to 20rem.
.dialog--mdSets the width to 25rem.
.dialog--lgSets the width to 30rem.
.dialog--full-widthSets the width to 100%.
.dialog--full-heightSets the height to 100%.
.dialog--fullscreenSets the width to 100% and height to 100%.
<div class="ui-dialog-root" data-ui-dialog id="my-dialog" hidden>
  <div class="ui-dialog-backdrop" data-ui-dialog-backdrop></div>
  <div class="ui-dialog ui-dialog--md" data-ui-dialog-content>
    <!-- Content -->
  </div>
</div>

Placement

By default, the dialog is placed in the center of the screen. To change this, use the following classes:

Class nameDescriptionDemo
By default places the dialog at the center of the screen.
.dialog--startPlaces the dialog at the start (left) of the screen.
.dialog--endPlaces the dialog at the end (right) of the screen.
.dialog--topPlaces the dialog at the top of the screen.
.dialog--top-startPlaces the dialog at the top start (left) of the screen.
.dialog--top-endPlaces the dialog at the top end (right) of the screen.
.dialog--bottomPlaces the dialog at the bottom of the screen.
.dialog--bottom-startPlaces the dialog at the bottom start (left) of the screen.
.dialog--bottom-endPlaces the dialog at the bottom end (right) of the screen.

Drawer

To display the dialog as a sliding panel, add the .dialog--drawer class. Example

Class nameDescriptionDemo
.dialog--drawer.dialog--startPlaces the dialog at the start (left) of the screen.
.dialog--drawer.dialog--endPlaces the dialog at the end (right) of the screen.
.dialog--drawer.dialog--topPlaces the dialog at the top of the screen.
.dialog--drawer.dialog--bottomPlaces the dialog at the bottom of the screen.

Transition

By default, the dialog does not have an animation, but you can add it by the data-ui-transition attribute.

Transition nameDescriptionDemo
By default, adds scale-up and fade animations, if the dialog is displayed as a drawer, then slide-{dir} and fade are used.
'fade'Applies a fade transition to the dialog.
'scale-down-fade'Applies a scale-down and fade transition to the dialog.
'scale-down-up-fade'Applies a scale-down when it’s entering, and slide-up when it’s leaving with fade transition to the dialog.
'scale-up-fade'Applies a scale-up and 'fade' transition to the dialog.
'scale-up-down-fade'Applies a scale-up when it’s entering, and scale-down when it’s leaving with fade transition to the dialog.
'slide-down-fade'Applies a slide-down and fade transition to the dialog.
'slide-down-up-fade'Applies a slide-down when it’s entering, and slide-up when it’s leaving with fade transition to the dialog.
'slide-up-fade'Applies a slide-up and fade transition to the dialog.
'slide-up-down-fade'Applies a slide-up when it’s entering, and slide-down when it’s leaving with fade transition to the dialog.

Top layer

By default, all dialogs open in the top layer if they have the topLayer:true option, i.e., they will always be above other elements, even if they were created earlier.

This is achieved by opening the <dialog> element in modal mode or by the Popover API.

To avoid unwanted inheritance of styles, the moveToRoot:true option is used, which moves the dialog to <body>.

html
<dialog data-ui-dialog data-ui-top-layer="false" data-ui-move-to-root="false" hidden>...</dialog>

We also added the .dialog-root--absolute class, so the window stays within its parent.

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="🐈" data-ui-toggle="dialog-cats-or-dogs">Cats 🐈</button>
<button data-whatever="🐕" data-ui-toggle="dialog-cats-or-dogs">Dogs 🐕</button>
 
<dialog data-ui-dialog id="dialog-cats-or-dogs" hidden>
  <button data-ui-dismiss aria-label="Close"></button>
  <h2 data-ui-dialog-title>You like more</h2>
  <div data-output></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("dialog-cats-or-dogs");
dialogElem.addEventListener("ui-dialog:show", ({ detail: [instance, { trigger }] }) => {
  dialogElem.querySelector("[data-output]").textContent = trigger.dataset.whatever;
});
I like more

Light and Back Dismiss

Dialog supports lightDismiss, which closes the dialog when clicking on any element outside of it, and backDismiss, which closes the dialog when clicking on the Esc or Back button on Android.

You can disable them by setting the value to false or use the preventDismiss option, in which case the closure will not be performed, an animation will be added and the dismissPrevent event will be triggered.

html
<dialog data-ui-dialog data-ui-prevent-dismiss hidden>...</dialog>

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">Show dialog</button>
 
<dialog data-ui-dialog="question" id="my-dialog" hidden>
  <div data-ui-dialog-title>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>
</dialog>

For 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.

We also added the .dialog--animate-prevent-hide class, which adds an animation when the dismissPrevent event is triggered.

js
Dialog.data("question", {
  preventDismiss: true,
  preventHide(instance, { trigger }) {
    if (trigger?.dataset?.answer == 5) {
      return true;
    }
  },
});
 
Dialog.initAll();

Toggle between dialogs

In some cases, we need to hide one dialog and open another, for example, when the user successfully submits a form or chooses the correct answer.

For such cases, you can assign them the same group using the group option.

Also, to avoid showing multiple backdrops, you can use one for all dialogs, specifying it in the backdrop option.

html
<div class="ui-dialog-backdrop" id="dialog-backdrop"></div>
 
<dialog
  data-ui-dialog
  data-ui-group="question"
  data-ui-backdrop="#dialog-backdrop"
  id="group-dialog-1"
  hidden
></dialog>
 
<dialog
  data-ui-dialog
  data-ui-group="question"
  data-ui-backdrop="#dialog-backdrop"
  id="group-dialog-2"
  hidden
></dialog>

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>
      <button aria-label="Close" data-ui-dismiss></button>
      <div data-ui-dialog-title>${title}</div>
      <button data-ui-dismiss>Cancel</button>
      <button data-ui-confirm>Confirm</button>
    </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;
});

Styles

css
.dialog {
  --ui-dialog-color: var(--ui-panel-color);
  --ui-dialog-bg: var(--ui-panel-bg);
  --ui-dialog-border-style: var(--ui-panel-border-style);
  --ui-dialog-border-width: var(--ui-panel-border-width);
  --ui-dialog-border-color: var(--ui-panel-border-color);
  --ui-dialog-outline: var(--ui-panel-outline);
  --ui-dialog-outline-offset: var(--ui-panel-outline-offset);
 
  --ui-dialog-shadow: var(--shadow-lg);
 
  --ui-dialog-padding-x: 1.75rem;
  --ui-dialog-padding-y: 1.5rem;
 
  --ui-dialog-size: fit-content;
  --ui-dialog-size-xs: 16rem;
  --ui-dialog-size-sm: 20rem;
  --ui-dialog-size-md: 28rem;
  --ui-dialog-size-lg: 36rem;
 
  --ui-dialog-border-radius: var(--radius-lg);
 
  --ui-dialog-boundary-offset-top: var(--ui-boundary-offset-top);
  --ui-dialog-boundary-offset-end: var(--ui-boundary-offset-end);
  --ui-dialog-boundary-offset-bottom: var(--ui-boundary-offset-bottom);
  --ui-dialog-boundary-offset-start: var(--ui-boundary-offset-start);
 
  --ui-dialog-title-font-size: var(--text-md);
  --ui-dialog-title-font-weight: 600;
  --ui-dialog-title-margin-bottom: 0.5rem;
 
  --ui-dialog-description-font-size: var(--text-sm);
 
  --ui-dialog-btns-margin-top: 1rem;
  --ui-dialog-btns-gap: 0.5rem;
 
  --ui-dialog-header-gap: 0.75rem;
  --ui-dialog-header-padding-bottom: 0.5rem;
  --ui-dialog-footer-padding-top: 1rem;
 
  --ui-dialog-close-offset-top: 0.75rem;
  --ui-dialog-close-offset-end: 0.75rem;
 
  --ui-dialog-slide-size: 2rem;
  --ui-dialog-drawer-slide-size: 50%;
}
.dialog-backdrop,
.dialog-root::backdrop,
.dialog::backdrop {
  background: var(--ui-backdrop-bg);
  transition: var(--ui-backdrop-transition);
}

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 by 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 / CSS class, depending on stateMode.
returnFocusBoolean, ObjecttrueDefines 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}.
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 by the lightDismiss: true option.
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
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?
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.
modalBooleantrueIf the <dialog> tag is used, then it opens in modal mode. Also, the properties focusTrap, backDismiss, lightDismiss, preventScroll, set to 'auto' are triggered Attribute: data-ui-modal
focusTrapBoolean, String'auto'Creates a focus trap within the dialog when modal is set to true and when it’s not a <dialog> element. In this case, the focus is not as strict as with modal mode and only affects keyboard navigation with Tab and Shift+Tab.
backDismissBoolean, String'auto'Defines whether the element should close when the Esc key is pressed. Attribute: data-ui-back-dismiss
lightDismissBoolean, String, Object'auto'Defines whether the element should close when the user clicks outside of it. You can also pass an object with the option {contextMenuClick: false}, which will disable closing the dialog when the right mouse button is clicked outside of it. Attribute: data-ui-light-dismiss
preventScrollBoolean, String'auto'Defines 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
hashNavigationBooleanfalseDefines whether to show the dialog during initialization if the URL hash is equal to the id. Attribute: data-ui-hash-navigation
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.
stateModeString'hidden'Accepts one of the next values 'hidden','hidden-until-found','inert','class-hidden','class-shown','remove' Attribute: data-ui-hide-mode
keepPlaceBooleantrueDefines if the component’s space is preserved when removed by stateMode: 'remove'.

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?

Hide mode

For more details on how to use, read the State mode section.

Transition

For more details on how to use, read the Transition section.

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.
on(name, handler)instanceAllows you to register event handler.
off(name, handler)instanceAllows you to remove event handler.
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 by 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

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.
dismissPreventinstance, {trigger, event}Fires when the the option preventDismiss is set to true and the user clicks outside of the dialog or presses Esc.
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.
2024 © A Project by Anatolii Moldovanov