Jolty home Jolty Documentation

Popover

Show your content atop another element. Features focus trap and positioning using CSS variables.
<button data-ui-toggle="my-popover" class="btn btn--md btn--primary btn--toggler">
  Transform
  <svg
    xmlns="http://www.w3.org/2000/svg"
    width="24"
    height="24"
    viewBox="0 0 24 24"
    stroke-width="2.5"
    stroke="currentColor"
  >
    <path stroke-linecap="round" stroke-linejoin="round" d="M19.5 8.25l-7.5 7.5-7.5-7.5"></path>
  </svg>
</button>
 
<div data-ui-popover data-ui-mode="modal" id="my-popover" class="popover" hidden>
  <div data-ui-popover-arrow class="popover-arrow"></div>
  <button type="button" class="btn btn--close" data-ui-dismiss="popover" 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>

Getting started

First, create two elements: <button> and <div>, and assign a unique id to it.

html
<button>Click me <span></span></button>
<div id="my-popover">My popover</div>
My popover content

Next, add the data-ui-popover attribute to the <div> and the data-ui-toggle attribute to the <button>, referencing the id we assigned to the popover.

Then, include the hidden attribute to hide the popover by default.

html
<button data-ui-toggle="my-popover">Click me <span></span></button>
<div data-ui-popover id="my-popover" hidden>My popover</div>

Next, initialize all components with the data-ui-popover attribute.

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

Also, let’s toggle the arrow’s state using the CSS class .ui-active.

css
.popover-toggler.ui-active span {
  transform: scaleY(-1);
}

Placement

You might have noticed that there is no gap between the trigger and the popover. To introduce an offset, use the CSS variable --ui-popover-offset. To align it to the bottom-right corner, use --ui-popover-placement.

css
.popover {
  --ui-popover-offset: 0.25rem;
  --ui-popover-placement: bottom-start;
}
@screen (min-width:1024px) {
  .popover {
    --ui-popover-offset: 0.5rem;
  }
}

Additionally, the placement can be set using the data-ui-popover-placement, but it won’t be responsive in this case.

Arrow

Often, along with a popover, an arrow (or sometimes referred to as a triangle or caret) is used, which points towards the button. To create this, use the data-ui-popover-arrow attribute.

html
<div data-ui-popover id="my-popover" hidden>
  <div data-ui-popover-arrow></div>
  ...
</div>

Next, we’ll set the offset of the popover through the CSS variable --ui-popover-arrow-offset and the margin from the edge using --ui-popover-arrow-padding.

By default, the arrow extends beyond the bounds of the popover by 50% of its size, hence --ui-popover-arrow-offset is set to 0, and there’s no need to specify it.

css
.popover {
  --ui-popover-arrow-offset: 0;
  --ui-popover-arrow-padding: 0.75rem;
}
.popover-arrow {
  position: absolute;
  top: 0;
  left: 0;
  translate: var(--ui-popover-arrow-left) var(--ui-popover-arrow-top);
  background-color: inherit;
  width: 0.4rem;
  aspect-ratio: 1;
  rotate: 45deg;
}

Backdrop

Given that we’re using the Popover API and the Dialog element, the popover supports the native backdrop through the ::backdrop pseudo-element wrapped within data-ui-floating. This means we can employ the :has() pseudo-class to determine the specific content within the popover and decide whether to display the backdrop.

css
[data-ui-floating="popover"]::backdrop {
  background-color: rgb(17 24 38 / 0.2);
  opacity: 1;
}

You can also add a class to the wrapper using the floatingClass option.

html
<div data-ui-popover data-ui-floating-class="backdrop:bg-black/20" id="my-popover" hidden>...</div>

Animation

For animating the popover, we can use specific CSS classes added during the opening and closing process of the popover. It’s also recommended to use the CSS variable --ui-popover-transform-origin so that the popover’s transformation is relative to its placement.

css
.popover {
  &.ui-enter-active,
  &.ui-leave-active {
    transition-duration: 200ms;
    transition-property: opacity, scale;
    transition-timing-function: ease-in-out;
    transform-origin: var(--ui-popover-transform-origin);
  }
  &.ui-enter-from,
  &.ui-leave-to {
    opacity: 0;
    scale: 0.95;
  }
}

Animating the backdrop is a bit more complicated. Since it’s not a child element of popover, we need to animate its pseudo-element ::backdrop.

css
[data-ui-floating="popover"]::backdrop {
  transition: all 200ms ease-in-out;
  background-color: rgb(17 24 38 / 0.2);
  opacity: 0;
}
[data-ui-floating="popover"]:has(> .ui-active)::backdrop {
  opacity: 1;
}

Trigger

Now, let’s provide the ability to open the popover through 'hover' and 'focus' by adding the attribute data-ui-trigger.

html
<button data-ui-toggle="my-popover">hover or focus <span></span></button>
<div data-ui-popover data-ui-trigger="hover focus" id="my-popover" hidden>My popover</div>

For 'hover', it has a delay of 200ms to prevent unintentional opening and closing of the popover. However, this can be modified using the delay.

Mode

The popover can be opened in three modes. This can be achieved using the data-ui-popover-mode attribute.

ModeTagAutofocusFocus TrapTop Layer
false<div>NoNoYes
'dialog'<dialog>Always yes (native)NoYes
'modal'<dialog>Always yes (native)Always yes (native)Always yes (native)

Let’s create a popover that expects user action confirmation. For this, we will change the mode to 'modal'. We’ll add buttons for both confirmation data-ui-confirm and cancellation data-ui-dismiss, giving the latter an autofocus through the data-ui-autofocus attribute.

html
<button data-ui-toggle="my-popover">Click me <span></span></button>
<div data-ui-popover data-ui-popover-mode="modal" id="my-popover" hidden>
  Confirm
  <button data-ui-confirm>Yes</button>
  <button data-ui-dismiss data-ui-autofocus>No</button>
</div>

Events

Upon clicking the data-ui-confirm button, an event 'ui-popover:confirm' is triggered. Its detail contains an Array with instance and an object {event, trigger}. Here, the event value represents the native button click event, while trigger points to the initiating button.

js
const myPopover = document.getElementById("my-popover");
myPopover?.addEventListener("ui-popover:confirm", ({ detail: [popover, { event, trigger }] }) => {
  alert("Confirmed");
  popover.hide();
});

You can also listen to the event on the document. This behavior can be turned off by setting {eventBubble: false}.

js
document.addEventListener("ui-popover:confirm", ({ detail: [popover, { event, trigger }] }) => {
  alert("Confirmed");
  popover.hide();
});

Data

However, what if you have multiple popovers and you don’t want to bind each one with a unique id, but instead wish to set common settings? There’s a static method for that, Popover.data().

You can remove all previously added data-attributes, keeping only the data-ui-popover attribute. By assigning it the name of our popover group, in this case 'confirm', it serves the purpose.

html
<button data-ui-toggle="my-popover">Click me <span></span></button>
<div data-ui-popover="confirm" data-ui-popover-mode="modal" id="my-popover" hidden>
  Confirm
  <button data-ui-confirm>Yes</button>
  <button data-ui-dismiss data-ui-autofocus>No</button>
</div>
js
Popover.data("confirm", (popoverElem) => {
  return {
    toggler: popoverElem.previuosElementSibling,
    mode: "modal",
    on: {
      confirm(popover, { event, trigger }) {
        alert("Confirmed");
        popover.hide();
      },
    },
  };
});
 
Popover.initAll();

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

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

js
Popover.data({});
// or
Popover.data((popoverElem) => ({}));

Options

NameTypeDefaultDescription
initBooleantrueShould the instance be automatically initialized when an instance is created? If false, you’ll need to manually initiate it by calling popover.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 Popover.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-popover:'Prefix for events dispatched on the popover.
eventDispatchBooleantrueDefines if events are dispatched or used only within options.
eventBubbleBooleantrueDefines if events should bubble up the DOM.
breakpointsObjectnullDefines custom options for specific breakpoints.
shownBooleannullDetermines if the popover is expanded after initialization. By default, it’s null, which means it checks the hidden attribute or another attribute, depending on hideMode.
awaitAnimationBooleanfalseDetermines whether to wait for the end of the animation to trigger 'shown','hidden' events and return a promise.
escapeHideBooleantrueWhether to close the popover when the Escape key is pressed.
outsideHideBooleantrueWhether to close the popover when clicking outside of it.
interactiveBooleantrueDetermines whether it is possible to interact with the popover using the keyboard and cursor.
preventScrollBooleanfalseDefines whether the CSS class .ui-prevent-scroll should be added to the <html> element when the popover 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.
togglerClassActiveString'ui-active'CSS class for toggler when popover is open.
popoverClassActiveString'ui-active'CSS class for popover when it’s open.
floatingClassString''Adds a CSS class for the floating wrapper around the popover. Attribute: data-ui-floating-class
triggerString'click'The popover supports triggers by 'click', 'hover' and 'focus'. You may pass multiple triggers; separate them with a space. Attribute: data-ui-trigger
modeStringfalseSets the way the popover is displayed, supports 3 display modes - false, 'dialog' and 'modal' to create a focus trap. Attribute: data-ui-mode
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.
focusTrapBooleanfalseDefines whether the focus must be trapped inside the popover.
dismissBoolean, CSSSelectortrueAllows the popover 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="popover"]' is clicked.
autofocusBoolean, CSSSelector, ElementtrueDefines the element that should be focused when the popover is opened. By default it’s true, meaning the '[autofocus],[data-ui-autofocus]' element will be focused.
topLayerBooleantrueDetermines whether the popover should be displayed in the top layer. CSS Variable: --ui-popover-top-layer: true;
rootElement'body'The element to which the popover will be appended.
moveToRootBooleanfalseMoves the popover to the element defined in the root option when opened. CSS Variable: --ui-popover-move-to-root: false;
a11yBooleantrueAdds aria-controls, aria-expanded and role="button" attributes to the toggler element.
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 popover 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

Floating

NameTypeDefaultDescription
placementString'bottom'How to position the popover: Use 'top','bottom','left', or 'right'. You can also append '-start' or '-end' to these positions for better alignment of the popover, such as 'top-start'.
You can also use the 'dialog' value, in which case the popover will be displayed as a dialog, without being attached to the toggler. Attribute: data-ui-placement CSS Variable: --ui-dropdown-placement: bottom;
delayNumber[150, 0]Sets delay when trigger:'hover'. Accepts 2 values, for mouseevent and mouseleave, if one value is set, it applies to both.
offsetNumber0Specifies the offset between the popover and the toggler. CSS Variable: --ui-popover-offset: 0px;
paddingNumber, Number0Specifies padding from the anchor’s borders. It accepts two values: padding from the start and from the end. If a single value is given, it will be applied to both sides. CSS Variable: --ui-popover-padding: 0px;
flipBoolean, Booleantruewhether the popover element should flip to the opposite placement when there’s not enough space. It can accept two values, corresponding to the X and Y axes. CSS Variable: --ui-popover-flip: true;
stickyBooleantrueDefines whether the popover element should shift to stay in view during a scroll. CSS Variable: --ui-popover-sticky: true;
shrinkBooleanfalseDefines whether the popover element should decrease in size to fit when there’s insufficient space. Remember that you must use the generated CSS variables to apply the available sizes. CSS Variable: --ui-popover-shrink: false;
boundaryOffsetNumber, Number[], Object0Specifies padding from the relative parent’s borders. It can accept up to four values like inset or margin CSS properties. You can also specify an object (for JS only) in the form {top: 0, right: 0, bottom: 0, left: 0} CSS Variable: --ui-popover-boundary-offset: 0px;

Arrow

NameTypeDefaultDescription
arrowBoolean, ObjecttrueObject with arrow options or false to disable.
widthNumber0Sets the width of the arrow. By default, it gets the width of the data-ui-popover-arrow element. CSS Variable: --ui-popover-arrow-width: 0px;
heightNumber0Sets the height of the arrow. By default, it gets the height of the data-ui-popover-arrow element. CSS Variable: --ui-popover-arrow-height: 0px;
offsetNumber0Sets the arrow’s offset from the popover. CSS Variable: --ui-popover-arrow-offset: 0px;
paddingNumber0Sets padding from the popover’s borders. It accepts two values: padding from the start and from the end. If a single value is given, it will be applied to both sides. CSS Variable: --ui-popover-arrow-padding: 0px;

Transition

AttributeTypeDefaultDescription
transitionString, Boolean, TransitionOptiontrueObject 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.
cssVariablesBooleanfalseAdds special CSS variables during the transition to the popover 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 popover and are removed after its completion.

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

CSS Variables

NameTypeDescription
--ui-popover-widthNumberThe width of the popover.
--ui-popover-heightNumberThe height of the popover.
--ui-popover-anchor-widthNumberThe width of the toggler.
--ui-popover-anchor-heightNumberThe height of the toggler.
--ui-popover-available-widthNumberThe remaining width between the toggler and the boundary edge.
Only when the shrink option is enabled.
--ui-popover-available-heightNumberThe remaining height between the toggler and the boundary edge.
Only when the shrink option is enabled.
--ui-popover-arrow-leftNumberThe left offset for the arrow.
--ui-popover-arrow-topNumberThe top offset for the arrow.
--ui-popover-transform-originNumberThe transform-origin computed from the content and arrow position

Methods

NameReturnDescription
init()instanceInitializes the component.
destroy(destroyOptions)instance, nullDestroys 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(toggleOptions, 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(toggleOptions)promiseOpens the component and accepts the same options as the toggle() method.
hide(toggleOptions)promiseCloses the component and accepts the same options as the toggle() method.

Class Methods

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

Properties

NameTypeDescription
idStringThe id of the base / popover element.
isInitBooleanIndicates whether the instance is already initialized.
optsObjectContains the currently applied options for the current breakpoint.
baseOptsObjectContains all options, including the breakpoints option.
base, popoverElementAll components have a base property, which refers to the element through which the component is initialized and where events are fired. popover is the same as base.
isOpenBooleanIndicates whether the popover is currently shown.
initialPlaceNodeNodeReference to the node that is located where the popover was at the moment of initialization.

Class properties

NameTypeDescription
DefaultObjectContains the default options for all instances.
instancesMapA Map that contains all instances of the Popover class.

Events

By default, events that are listened to directly through an element have the prefix 'ui-popover:'. 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.

popover.data("my-popover", {
  on: {
    shown(instance, { trigger, event }) {
      // do something...
    },
    any(eventName, instance, { trigger, event }) {
      if (eventName === "shown") {
        // do something...
      }
    },
  },
});
 
// or directly on the popover element
const popoverElem = document.querySelector(".my-popover");
popoverElem.addEventListener("ui-popover:show", (e) => {
  const [instance, { trigger, event }] = e.detail;
  // do something...
});
 
// or on the document
document.addEventListener("ui-popover: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 popover 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.
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 🇪🇸 🇺🇦