Created:
Last modified:
Dialog Element and Popover Attribute
While the popover attribute is not yet fully implemented across browsers at the time of writing, it sounds like it's on it's way. The dialog element has been around for a good long while, from what I understand, however hasn't been in wide use. Today I will explore both of these. At this time popover seems to only be available on Chrome and Edge, so if you're viewing on another browser you may not get the full effect.
Starting with dialog element
A dialog element has the option to be opened as a modal or as a non-modal element. A modal element is added
to the #top-layer, which is a sibling element to the html element. It automatically takes up the whole width and height of the html
element, is position: fixed, and does not allow the user to interact with the
rest of the website while it is open. Although it does allow the page to scroll still. A dialog opened as a
modal also adds a translucent background to the ::backdrop, greying out the rest
of the page slightly to indicate it cannot be interacted with.
On the other hand, a dialog element opened as a non-modal is position:
absolute, is rendered in the normal flow of the document and doesn't make use of the ::backdrop property automatically.
With the examples below, you can open the second dialog (non-modal), and then open the first dialog (modal). You will then not be able to close the second dialog until you close the first one as it blocks access to anything but itself.
Modal dialog
The modal version of the dialog is activated by using the showModal()
property to open the dialog
<button class="btn" onclick="showDialogModal('first-dialog')">
Open dialog
</button>
<dialog id="first-dialog">
<p>Hi, I'm a dialog element</p>
<button class="btn" onclick="closeLatestDialog(event)">
Close dialog
</button>
</dialog>
const showDialogModal = (dialogId: string) => {
const dialog = document.querySelector(
"dialog#" + dialogId
) as HTMLDialogElement;
if (dialog) {
dialog.showModal();
}
};
Non-modal dialog
The non-modal version of the dialog however is activated using the show()
property, or by toggling the open attribute on the dialog itself. However it
is not recommened to edit the open attribute.
<button class="btn" onclick="showDialog('second-dialog')">
Open dialog
</button>
<dialog id="second-dialog">
<p>Hi, I'm a dialog element</p>
<button class="btn" onclick="closeLatestDialog(event)">
Close dialog
</button>
</dialog>
const showDialog = (dialogId: string) => {
const dialog = document.querySelector(
"dialog#" + dialogId
) as HTMLDialogElement;
if (dialog) {
dialog.show();
}
};
Custom dialog web component
A custom web component with shadowDom enabled would have to contain it's own button for opening and closing. The below is a custom dialog web component, but the button is the same as ones above, simply querying the document for the corresponding dialog element with allocated id. This dialog cannot be found as it is on the shadowDom, so the dialog isn't opened.
This button attempts to open a custom element attached to a shadow dom. This one DOES NOT WORK.
Hi, I'm a custom dialog element
This button opens a custom element which extends the HTMLDialogElement.
This button opens a custom dialog element, not attached to shadow dom.
Here is a full custom element attached to the shadow dom (hence the lack of inherited styling). The button to open the dialog along with the dialog itself is all part of the one component.
More on the dialog element
The dialog element natively has margin: auto set. However, I like many other
devs reset a few CSS properties, including margin. So I do have to remember to explicitly set the margin
property to auto for the dialog afterwards. It has got to be one of the few elements which is automatically
centered, and would have devs jumping for joy if they knew or used it. I started learning web development
properly after centering things became less of a hassle, so I only vaguely understand this inherent
frustration.
The ::backdrop property is only available when the dialog is opened as a modal,
so opened using showModal().
The dialog element is not a valid element to attach a shadow dom to - see dom spec.
A modal dialog automatically has the keyboard accessibility of using escape to
close. A non-modal dialog does not automatically have this option.
Both modal and non-modal dialogs automatically focus control to the first focusable element inside the dialog.
Popover attribute
This next section may not work on your browser. Currently the popover attribute is only fully available on Chrome and Edge.
Popover is a global attribute which is used to designate the element as a popover element. It is currently an experimental technology, currently available in Chrome, experimental in Safari and behind a feature flag in Firefox.
popover="auto"
Here we have a basic div with the popover="auto" attribute added, and a button with the attribute popovertarget referencing the id of the popover div.
With the popover set to auto, the popover can be 'light-dismissed' by clicking outside of the popover,
closed via the keyboard using the escape key, or by clicking the controlling
button which toggles the view.
I am an auto popover. I can be light-dismissed by clicking outside the popover or pressing escape.
<button class="btn mini" popovertarget="first-auto-popover">
Open popover
</button>
<div class="popover" id="first-auto-popover" popover="auto">
<p>Lorem ipsum dolor sit amet consectetur adipisicing elit. Consequatur ea accusantium impedit modi eveniet, sit quisquam. Fugiat, cupiditate velit harum labore perspiciatis accusantium quisquam? Error beatae atque dolore excepturi veniam?</p>
</div>
popover="manual"
Here we have the same set up as above except with the popover="manual"
attribute changed.
A manual popover does not receive the light-dismiss or keyboard closure options automatically, and can only be closed via toggling the original button, or with an extra button or extra javascript.
<button popovertarget="first-manual-popover"> Toggle popover</button>
popovertargetaction="toggle", thus doesn't have to be explicitly
added to this button<button popovertarget="first-manual-popover" popovertargetaction="show"> Show popover</button>
<button popovertarget="first-manual-popover" popovertargetaction="hide"> Hide popover</button>
I am a manual popover, meaning I need an extra button or extra javascript to close me.
<button class="btn mini" popovertarget="first-manual-popover">
Open popover
</button>
<div class="popover" id="first-manual-popover" popover="manual">
<p>Lorem ipsum dolor sit amet consectetur adipisicing elit. Consequatur ea accusantium impedit modi eveniet, sit quisquam. Fugiat, cupiditate velit harum labore perspiciatis accusantium quisquam? Error beatae atque dolore excepturi veniam?</p>
</div>
Other things
A popover can also be controlled via javascript methods:
element.showPopover()- to show a popover
element.hidePopover()- to hide a popover
element.togglePopover()- to toggle a popover
[popover],
dialog {
margin: auto;
}
Much like the dialog, using the popover attribute adds certain CSS properties to the element. One of
these in margin: auto which I again have to add back to the element.
More on popover
According to the spec on the Popover API popovers are always non-modal.
On click of the activating button, unlike with the dialog element, the focus remains on the button itself (unless the popover is a dialog element, but more on that later).
For styling an open popover you can use the :popover-open attribute in CSS.
Here you can change the popover element itself and any nested elements. As a popover is also rendered in
the #top-layer, we can use the ::backdrop
property to add... well... a backdrop. However
If you're seeing this, popover is not
currently supported on your browser
<button class="btn mini" popovertarget="first-form-popover" onclick="console.log(document.activeElement)">
Open form
</button>
<form id="first-form-popover" popover>
...
</form>
Combining forces
Here we have a dialog with the popover="auto" set,
along with an internal button to close the dialog/popover.
<button class="btn mini" popovertarget="first-dialog-popover" onclick="console.log(document.activeElement)">
Open dialog as modal
</button>
<dialog id="first-dialog-popover" popover="auto">
<p>Hi, I'm a dialog modal element</p>
<button class="btn mini" onclick="closeLatestDialog(event)">
Close dialog
</button>
</dialog>
Let's make it a little prettier
It's fine, it does the job, but why settle for that. I don't feel a need to go all out with web design, but I do like clean, easy to read/understand/use. I'm good at developing other peoples way out designs, don't get me wrong. It's just not my personal taste. Function over form, with a little form thrown in, right?
Tooltip
I also like the idea of using the popover as a tooltip. However because the popover is rendered in the
#top-layer any positioning of the element is relative to this layer, not, for
instance, the button pressed to get the tooltip.
As a popover:
.tooltip {
position: relative;
&-tip {
top: 50%;
left: 0;
margin: unset;
background: deeppink;
}
}
As a dialog:
<button class="tooltip" onclick="toggleDialog('dialog-tooltip')">
tooltip
</button>
<dialog class="tooltip-tip" id="dialog-tooltip">
A tooltip gives the user more information about something they may be unaware of.
</dialog>