{"id":1104,"date":"2024-03-04T14:21:03","date_gmt":"2024-03-04T20:21:03","guid":{"rendered":"https:\/\/frontendmasters.com\/blog\/?p=1104"},"modified":"2024-03-04T14:21:03","modified_gmt":"2024-03-04T20:21:03","slug":"menus-toasts-and-more","status":"publish","type":"post","link":"https:\/\/frontendmasters.com\/blog\/menus-toasts-and-more\/","title":{"rendered":"Menus, toasts and more with the Popover API, the dialog element, invokers, anchor positioning and @starting-style"},"content":{"rendered":"\n<p>Dropdowns, menus, tooltips, comboboxes, toasts \u2014 the <code>popover<\/code> attribute will make building a large variety of UI components easier. The <code>popover<\/code> attribute can be used on any HTML element, so you have the flexibility to choose whichever element is most appropriate semantically for each particular use case. Unlike a dialog, a popover is always <a href=\"https:\/\/hidde.blog\/dialog-modal-popover-differences\/\">non-modal<\/a> \u2014 meaning they don&#8217;t block interaction with anything else on the page. To toggle a popover open and closed, a <code>button<\/code> element needs to include an <code>invoketarget<\/code> attribute with a value that matches the <code>id<\/code> of the popover.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-1\" data-shcb-language-name=\"HTML, XML\" data-shcb-language-slug=\"xml\"><span><code class=\"hljs language-xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">button<\/span> <span class=\"hljs-attr\">invoketarget<\/span>=<span class=\"hljs-string\">\"foobar\"<\/span>&gt;<\/span>Toggle popover<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">button<\/span>&gt;<\/span>\n\n<span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> <span class=\"hljs-attr\">id<\/span>=<span class=\"hljs-string\">\"foobar\"<\/span> <span class=\"hljs-attr\">popover<\/span>&gt;<\/span>\n  Popover content goes here...\n<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-1\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">HTML, XML<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">xml<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>A <code>&lt;button&gt;<\/code> with an <code>invoketarget<\/code> attribute is called an <em>invoker<\/em>. Invokers might eventually bring all sorts of power to HTML markup, but in its first iteration it&#8217;s limited to opening and closing popovers and dialogs. You don\u2019t need <code>onclick=<\/code> or <code>addEventListener<\/code>, it\u2019ll just work.<\/p>\n\n\n\n<p>The fact that popovers work without JavaScript is nice, but toggling <code>display: none<\/code> on an element using JS was never challenging. Popovers do, however, bring far more to the table:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Popovers make use of the top layer.<\/li>\n\n\n\n<li>Light-dismiss functionality: clicking outside of the popover will close the popover.<\/li>\n\n\n\n<li>Hitting the escape key will close the popover.<\/li>\n\n\n\n<li>Focus management: when you open a popover, the next tab stop will be the first focusable element inside the popover. If you&#8217;ve focused an element within the popover and then close the popover, focus is returned to the correct place (this was tricky to get right with JavaScript).<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">Browser support<\/h2>\n\n\n\n<p>The <code>popover<\/code> attribute is supported in Chrome, Safari, and Firefox 125. The <code>popovertarget<\/code> attribute currently has better browser support than <code>invoketarget<\/code>. <code>popovertarget<\/code> is popover-specific, offering a declarative way to toggle popovers open and closed. <code>popovertarget<\/code> will likely eventually be <a href=\"https:\/\/github.com\/openui\/open-ui\/issues\/869\">deprecated and replaced<\/a> by the more flexible <code>invoketarget<\/code>. After popovers shipped in Chrome, some smart people realised it would also be handy to have a declarative way for buttons to open dialogs and perform other tasks, which is why there are two ways to do the same thing. A <a href=\"https:\/\/www.npmjs.com\/package\/invokers-polyfill\">polyfill for invokers<\/a> is available.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Light dismiss<\/h2>\n\n\n\n<p>The <code>popover<\/code> attribute can be set to either <code>auto<\/code> (the default) or <code>manual<\/code>. When set to <code>auto<\/code>, the popover has light dismiss functionality: if the user clicks outside of the popover, the popover is closed. Pressing the escape key will also close the popover. Only one <code>auto<\/code> popover is ever open at a time.<\/p>\n\n\n\n<p>When set to <code>manual<\/code>, there is no light dismiss functionality and the escape key does not close the popover. The popover must be explicitly closed by pressing the button again (or by calling <code>hidePopover()<\/code> in JavaScript). Multiple <code>manual<\/code> popovers can be open at the same time.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-2\" data-shcb-language-name=\"HTML, XML\" data-shcb-language-slug=\"xml\"><span><code class=\"hljs language-xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">button<\/span> <span class=\"hljs-attr\">invoketarget<\/span>=<span class=\"hljs-string\">\"foobar\"<\/span>&gt;<\/span>Toggle popover<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">button<\/span>&gt;<\/span>\n\n<span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> <span class=\"hljs-attr\">id<\/span>=<span class=\"hljs-string\">\"foobar\"<\/span> <span class=\"hljs-attr\">popover<\/span>=<span class=\"hljs-string\">\"manual\"<\/span>&gt;<\/span>\n  Popover content goes here...\n<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-2\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">HTML, XML<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">xml<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<h2 class=\"wp-block-heading\">Invoker actions<\/h2>\n\n\n\n<p>Along with the <code>invoketarget<\/code> attribute, a button can also optionally include an <code>invokeaction<\/code> attribute. The different actions are listed below.<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table><thead><tr><th>Action<\/th><th>Description<\/th><\/tr><\/thead><tbody><tr><td><code>showpopover<\/code><\/td><td>Show a popover.<\/td><\/tr><tr><td><code>hidepopover<\/code><\/td><td>Close a popover.<\/td><\/tr><tr><td><code>showmodal<\/code><\/td><td>Open a dialog element as modal.<\/td><\/tr><tr><td><code>close<\/code><\/td><td>Close a dialog element.<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p>If you omit the <code>invokeaction<\/code> attribute, the default behaviour depends on the context: If the target set by <code>invoketarget<\/code> is a popover it will call <code>.togglePopover()<\/code>. If the target is a dialog it will call <code>showModal()<\/code> if the dialog is closed and will close the dialog if the dialog is open.<\/p>\n\n\n\n<p>Using invokers for the dialog element looks much the same as the popover example:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-3\" data-shcb-language-name=\"HTML, XML\" data-shcb-language-slug=\"xml\"><span><code class=\"hljs language-xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">button<\/span> <span class=\"hljs-attr\">invoketarget<\/span>=<span class=\"hljs-string\">\"my-dialog\"<\/span>&gt;<\/span>Open Dialog<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">button<\/span>&gt;<\/span>\n\n<span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">dialog<\/span> <span class=\"hljs-attr\">id<\/span>=<span class=\"hljs-string\">\"my-dialog\"<\/span>&gt;<\/span>\n  Dialog content goes here.\n  <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">button<\/span> <span class=\"hljs-attr\">invoketarget<\/span>=<span class=\"hljs-string\">\"my-dialog\"<\/span> <span class=\"hljs-attr\">invokeaction<\/span>=<span class=\"hljs-string\">\"close\"<\/span>&gt;<\/span>Close dialog<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">button<\/span>&gt;<\/span>\n<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">dialog<\/span>&gt;<\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-3\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">HTML, XML<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">xml<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Along with built-in actions, developers can write custom actions. This is outside the scope of this article as a custom action <a href=\"https:\/\/codepen.io\/keithamus\/pen\/abXbzqv\">could do anything<\/a> \u2014 it need not be related to dialogs or popovers.<\/p>\n\n\n\n<p>While a selling point of invokers is forgoing JavaScript, they also provide a new JavaScript <code>invoke<\/code> event should you need more than the default behaviour. This event is fired on the popover or dialog, not the button.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-4\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-built_in\">document<\/span>.querySelector(<span class=\"hljs-string\">\"&#91;popover]\"<\/span>).addEventListener(<span class=\"hljs-string\">\"invoke\"<\/span>, <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span>(<span class=\"hljs-params\">event<\/span>) <\/span>{\n    <span class=\"hljs-built_in\">console<\/span>.log(event.action);\n    <span class=\"hljs-built_in\">console<\/span>.log(event.invoker);\n    <span class=\"hljs-comment\">\/\/ do something useful here...<\/span>\n  });<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-4\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Within the event handler you can get a reference to whichever button triggered the invocation with <code>event.invoker<\/code> and determine the action specified by <code>invokeaction<\/code> with <code>event.action<\/code>.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Popover methods and events<\/h2>\n\n\n\n<p>For many use cases, the popover API doesn\u2019t require JavaScript. What if we want to display a toast notification without a user first interacting with a button, for example?<\/p>\n\n\n\n<p>There are methods to show, hide, or toggle a popover element: <code>.showPopover()<\/code>, <code>.hidePopover()<\/code> and <code>.togglePopover()<\/code>, respectively.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-5\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-built_in\">document<\/span>.getElementById(<span class=\"hljs-string\">'toast'<\/span>).showPopover();<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-5\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>There is a <code>toggle<\/code> event that fires on the popover both when the popover gets shown and when it gets hidden (there are no separate open or close events). This would be useful for a toast alert that automatically disappears after a set amount of time, for example, as there\u2019s no markup or CSS-based way to do that.<\/p>\n\n\n\n<p>Its worth checking that the popover isn\u2019t already hidden before calling <code>hidePopover()<\/code>. We can do that with either <code>.matches(':popover-open')<\/code>, <a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/API\/Element\/checkVisibility\"><code>.checkVisibility()<\/code><\/a>, or <code>event.newState === 'open'<\/code>, all of which will return <code>true<\/code> if the popover is open.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-6\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\">toast.addEventListener(<span class=\"hljs-string\">\"toggle\"<\/span>, <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> (<span class=\"hljs-params\">event<\/span>) <\/span>{\n  <span class=\"hljs-keyword\">if<\/span> (event.target.matches(<span class=\"hljs-string\">\":popover-open\"<\/span>)) {\n    setTimeout(<span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> (<span class=\"hljs-params\"><\/span>) <\/span>{\n      toast.hidePopover();\n    }, <span class=\"hljs-number\">3000<\/span>);\n  }\n});<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-6\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<div class=\"wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper\"><iframe id=\"cp_embed_XWxVWyw\" src=\"\/\/codepen.io\/anon\/embed\/XWxVWyw?height=450&amp;theme-id=47434&amp;slug-hash=XWxVWyw&amp;default-tab=js,result\" height=\"450\" scrolling=\"no\" frameborder=\"0\" allowfullscreen allowpaymentrequest name=\"CodePen Embed XWxVWyw\" title=\"CodePen Embed XWxVWyw\" class=\"cp_embed_iframe\" style=\"width:100%;overflow:hidden\">CodePen Embed Fallback<\/iframe><\/div>\n\n\n\n<p>There&#8217;s also a <code>beforetoggle<\/code> method, which is similar but lets you call <code>event.preventDefault()<\/code> inside the event handler, should you need to \u2014 and it might come in useful for animations. The <code>toggle<\/code> event, by contrast, isn&#8217;t cancellable.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Default popover styles<\/h2>\n\n\n\n<p>By default a popover is set to <code>position: fixed<\/code> and displayed in the center of the viewport with a solid black border but you&#8217;re free to style it however you like. The styles the browser applies to a popover look something like this:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-7\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css\"><span class=\"hljs-selector-attr\">&#91;popover]<\/span> {\n    <span class=\"hljs-attribute\">position<\/span>: fixed;\n    <span class=\"hljs-attribute\">width<\/span>: fit-content;\n    <span class=\"hljs-attribute\">height<\/span>: fit-content;\n    <span class=\"hljs-attribute\">inset<\/span>: <span class=\"hljs-number\">0px<\/span>;\n    <span class=\"hljs-attribute\">margin<\/span>: auto;\n    <span class=\"hljs-attribute\">border<\/span>: solid;\n    <span class=\"hljs-attribute\">padding<\/span>: <span class=\"hljs-number\">0.25em<\/span>;\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-7\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">CSS<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">css<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>If I wanted to position a popover in the bottom left, for example, I&#8217;d need to set <code>top<\/code> and <code>right<\/code> to either <code>auto<\/code>, <code>initial<\/code> or <code>unset<\/code>.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-8\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css\"><span class=\"hljs-selector-class\">.toast<\/span> {\n    <span class=\"hljs-attribute\">inset<\/span>: unset;\n    <span class=\"hljs-attribute\">bottom<\/span>: <span class=\"hljs-number\">12px<\/span>;\n    <span class=\"hljs-attribute\">left<\/span>: <span class=\"hljs-number\">12px<\/span>;\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-8\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">CSS<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">css<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<h2 class=\"wp-block-heading\">Beyond <code>z-index<\/code>: The top layer<\/h2>\n\n\n\n<p>Some JavaScript frameworks have something called <em>portals<\/em> for rendering things like tooltips and dialogs. I always found portals difficult to work with. The <a href=\"https:\/\/react.dev\/reference\/react-dom\/createPortal#rendering-to-a-different-part-of-the-dom\">React docs<\/a> describe portals like so:<\/p>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p>&#8220;Portals let your components render some of their children into a different place in the DOM. This lets a part of your component \u201cescape\u201d from whatever containers it may be in. For example, a component can display a modal dialog or a tooltip that appears above and outside of the rest of the page&#8230; You can use a portal to create a modal dialog that floats above the rest of the page, even if the component that summons the dialog is inside a container with overflow: hidden.&#8221;<\/p>\n<\/blockquote>\n\n\n\n<p>When working with either the <code>&lt;dialog&gt;<\/code> element (rather than crafting one out of divs) or the <code>popover<\/code> attribute, you can avoid this issue entirely \u2014 no portals required. Their location in the DOM doesn&#8217;t matter. Its often convenient to collocate the markup for a popover or <code>&lt;dialog&gt;<\/code> together with the button that opens it. They can appear anywhere in your markup and won&#8217;t get cropped by <code>overflow: hidden<\/code> on a parent element. They make use of the top layer, which is a native web solution for rendering content above the rest of the document. The top layer sits above the document and always trumps <code>z-index<\/code>. An element in the top layer can also make use of a styleable <code>::backdrop<\/code> pseudo-element.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Animate an element into and out of the top layer<\/h2>\n\n\n\n<p>By default, when a popover or dialog is opened, it instantly appears. You might want to add an entry animation \u2014 perhaps a quick opacity fade-in, for example. <code>@starting-style<\/code> is used to animate an element into view with a CSS <code>transition<\/code> (you don&#8217;t need <code>@starting-style<\/code> when working with <code>@keyframes<\/code>). <code>@starting-style<\/code> works both when you&#8217;re adding a new element to the DOM and when an element is already in the DOM but is being made visible by changing its display value from <code>display: none<\/code>. When in a closed state, both the popover attribute and the <code>&lt;dialog&gt;<\/code> element make use of <code>display: none<\/code> under the hood, so <code>@starting-style<\/code> can be used to animate them onto the page.<\/p>\n\n\n\n<p>The following transition will fade and spin the popover into view, and scale down the size of the popover for the exit transition.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-9\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css\"><span class=\"hljs-comment\">\/*  Transition to these styles on entry, and from these styles on exit   *\/<\/span>\n<span class=\"hljs-selector-attr\">&#91;popover]<\/span><span class=\"hljs-selector-pseudo\">:popover-open<\/span> {\n  <span class=\"hljs-attribute\">opacity<\/span>: <span class=\"hljs-number\">1<\/span>;\n  <span class=\"hljs-attribute\">rotate<\/span>: <span class=\"hljs-number\">0turn<\/span>;\n  <span class=\"hljs-attribute\">transition<\/span>: rotate .<span class=\"hljs-number\">5s<\/span>, opacity .<span class=\"hljs-number\">5s<\/span>, display .<span class=\"hljs-number\">5s<\/span> allow-discrete, overlay .<span class=\"hljs-number\">5s<\/span> allow-discrete;\n}\n\n<span class=\"hljs-comment\">\/*   Entry transition starts with these styles  *\/<\/span>\n<span class=\"hljs-keyword\">@starting-style<\/span> {\n  <span class=\"hljs-selector-attr\">&#91;popover]<\/span><span class=\"hljs-selector-pseudo\">:popover-open<\/span> {\n    <span class=\"hljs-attribute\">opacity<\/span>: <span class=\"hljs-number\">0<\/span>;\n    <span class=\"hljs-attribute\">rotate<\/span>: <span class=\"hljs-number\">1turn<\/span>;\n  }\n}\n\n<span class=\"hljs-comment\">\/*  Exit transition ends with these styles  *\/<\/span>\n<span class=\"hljs-selector-attr\">&#91;popover]<\/span><span class=\"hljs-selector-pseudo\">:not(<\/span><span class=\"hljs-selector-pseudo\">:popover-open)<\/span> {\n  <span class=\"hljs-attribute\">scale<\/span>: <span class=\"hljs-number\">0<\/span>;\n  <span class=\"hljs-attribute\">transition<\/span>: scale .<span class=\"hljs-number\">3s<\/span>, display .<span class=\"hljs-number\">3s<\/span> allow-discrete, overlay .<span class=\"hljs-number\">3s<\/span> allow-discrete;\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-9\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">CSS<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">css<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<div class=\"wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper\"><iframe id=\"cp_embed_LYaBaaP\" src=\"\/\/codepen.io\/anon\/embed\/LYaBaaP?height=450&amp;theme-id=47434&amp;slug-hash=LYaBaaP&amp;default-tab=css,result\" height=\"450\" scrolling=\"no\" frameborder=\"0\" allowfullscreen allowpaymentrequest name=\"CodePen Embed LYaBaaP\" title=\"CodePen Embed LYaBaaP\" class=\"cp_embed_iframe\" style=\"width:100%;overflow:hidden\">CodePen Embed Fallback<\/iframe><\/div>\n\n\n\n<p>The popover will transition from its <code>@starting-style<\/code> styles to its <code>[popover]:popover-open<\/code> styles every time it&#8217;s opened.<\/p>\n\n\n\n<p>The <code>overlay<\/code> transition is necessary boilerplate when transitioning an element in or out of the top layer. The <a href=\"https:\/\/drafts.csswg.org\/css-position-4\/#overlay\"><code>overlay<\/code><\/a> property was added to CSS purely for this use case and has no other practical application. It is an unusual property to the extent that, outside of transitions, it can only be specified by the browser \u2014 you can&#8217;t set it with your own CSS. By default, a dialog or popover is instantly removed from the top layer when closed. This will lead to the element getting clipped and obscured. By transitioning <code>overlay<\/code>, the element stays in the top layer until the transition has finished.<\/p>\n\n\n\n<p><code>transition-behavior<\/code> is a new CSS property that can be set to either <code>normal<\/code> or <code>allow-discrete<\/code>. In the above code example I&#8217;m using the shorthand.<\/p>\n\n\n\n<p>Similarly for the <code>display<\/code> property, by including it in the transition and specifying <code>transition-behavior: allow-discrete<\/code> we ensure that a change from <code>display: none<\/code> happens at the very start of the entrance transition and that a change to <code>display: none<\/code> happens at the very end of the exit transition.<\/p>\n\n\n\n<p><code>@starting-style<\/code> has some useful applications outside of working with popovers and dialogs, but that&#8217;s a topic for a different article.<\/p>\n\n\n\n<p>You can transition the <code>::backdrop<\/code> pseudo-element in a similar way.<\/p>\n\n\n\n<p>e.g.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-10\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css\"><span class=\"hljs-keyword\">@starting-style<\/span> {\n  <span class=\"hljs-selector-attr\">&#91;popover]<\/span><span class=\"hljs-selector-pseudo\">:popover-open<\/span><span class=\"hljs-selector-pseudo\">::backdrop<\/span> {\n    <span class=\"hljs-attribute\">opacity<\/span>: <span class=\"hljs-number\">0<\/span>;\n  }\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-10\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">CSS<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">css<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Now let&#8217;s look at doing the same transition with a <code>&lt;dialog&gt;<\/code> element:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-11\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css\"><span class=\"hljs-comment\">\/*  Transition to these styles on entry, and from these styles on exit   *\/<\/span>\n<span class=\"hljs-selector-tag\">dialog<\/span><span class=\"hljs-selector-pseudo\">:open<\/span> {\n  <span class=\"hljs-attribute\">opacity<\/span>: <span class=\"hljs-number\">1<\/span>;\n  <span class=\"hljs-attribute\">rotate<\/span>: <span class=\"hljs-number\">0turn<\/span>;\n  <span class=\"hljs-attribute\">transition<\/span>: rotate .<span class=\"hljs-number\">5s<\/span>, opacity .<span class=\"hljs-number\">5s<\/span>, display .<span class=\"hljs-number\">5s<\/span> allow-discrete, overlay .<span class=\"hljs-number\">5s<\/span> allow-discrete;\n}\n\n<span class=\"hljs-comment\">\/*   Entry transition starts with these styles  *\/<\/span>\n<span class=\"hljs-keyword\">@starting-style<\/span> {\n  <span class=\"hljs-selector-tag\">dialog<\/span><span class=\"hljs-selector-pseudo\">:open<\/span> {\n    <span class=\"hljs-attribute\">opacity<\/span>: <span class=\"hljs-number\">0<\/span>;\n    <span class=\"hljs-attribute\">rotate<\/span>: <span class=\"hljs-number\">1turn<\/span>;\n  }\n}\n\n<span class=\"hljs-comment\">\/*  Exit transition ends with these styles.  *\/<\/span>\n<span class=\"hljs-selector-tag\">dialog<\/span><span class=\"hljs-selector-pseudo\">:closed<\/span> {\n  <span class=\"hljs-attribute\">scale<\/span>: <span class=\"hljs-number\">0<\/span>;\n  <span class=\"hljs-attribute\">transition<\/span>: scale .<span class=\"hljs-number\">3s<\/span>, display .<span class=\"hljs-number\">3s<\/span> allow-discrete, overlay .<span class=\"hljs-number\">3s<\/span> allow-discrete;\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-11\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">CSS<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">css<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>The <code>:open<\/code> and <code>:closed<\/code> selectors are new pseudo-selectors. They work for details, dialog, and select elements \u2014 but not for popovers. You can use <code>dialog[open]<\/code> and <code>dialog:not([open])<\/code> for the time being for better browser support.<\/p>\n\n\n\n<p>These examples all work in Chrome. <code>@starting-style<\/code> and <code>transition-behavior<\/code> are part of <a href=\"https:\/\/web.dev\/blog\/interop-2024\">Interop 2024<\/a>, meaning they&#8217;ll likely be fully supported by the end of the year. <a href=\"https:\/\/developer.apple.com\/documentation\/safari-release-notes\/safari-17_4-release-notes#Web-Animations\">Safari 17.4<\/a> added support for <code>transition-behavior: allow-discrete<\/code>. Safari Technology Preview 189 added support for <code>@starting-style<\/code>. WebKit have yet to declare a <a href=\"https:\/\/github.com\/WebKit\/standards-positions\/issues\/169\">position<\/a> on the <code>overlay<\/code> property.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Anchor positioning<\/h2>\n\n\n\n<p>With a component like a toast or a dialog, we generally want to position the element in relation to the viewport. We typically display a dialog in the center of the screen, and a toast at the bottom. That\u2019s easy to do. There are other times when you need to position an element in relation to another element on the page. For a dropdown menu, for example, we want to place the popover in relation to the button that opened it. This is more challenging.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full is-resized\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"947\" height=\"463\" src=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/03\/youtube-menu-example.png?resize=947%2C463&#038;ssl=1\" alt=\"Screenshot of the ... three dot menu on YouTube opened up showing a menu of three options: Clip, Save, and Report.\" class=\"wp-image-1106\" style=\"width:515px;height:auto\" srcset=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/03\/youtube-menu-example.png?w=947&amp;ssl=1 947w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/03\/youtube-menu-example.png?resize=300%2C147&amp;ssl=1 300w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/03\/youtube-menu-example.png?resize=768%2C375&amp;ssl=1 768w\" sizes=\"auto, (max-width: 947px) 100vw, 947px\" \/><\/figure>\n\n\n\n<p>This sort of behaviour usually requires JavaScript and led to the creation of the popular JavaScript libraries Popper, Floating UI and Tether. With the addition of anchor positioning to CSS, we&#8217;ll no longer need to reach for JavaScript. The <a href=\"https:\/\/drafts.csswg.org\/css-anchor-position-1\/\"><code>anchor()<\/code> function<\/a> allows developers to tether an absolutely positioned element to one or more other elements on the page. Unfortunately, it&#8217;s a work-in-progress so I&#8217;ll revisit the topic when the spec and implementation are more solid.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Conclusion<\/h2>\n\n\n\n<p>I covered a lot in this article but there&#8217;s more to come. The popover attribute can be useful all by itself but some forthcoming web APIs will help cover more use cases. Anchor positioning looks set to be the most useful CSS feature since grid. Stay tuned.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Dropdowns, menus, tooltips, comboboxes, toasts \u2014 the popover attribute will make building a large variety of UI components easier. The popover attribute can be used on any HTML element, so you have the flexibility to choose whichever element is most appropriate semantically for each particular use case. Unlike a dialog, a popover is always non-modal [&hellip;]<\/p>\n","protected":false},"author":14,"featured_media":1110,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"sig_custom_text":"","sig_image_type":"featured-image","sig_custom_image":0,"sig_is_disabled":false,"inline_featured_image":false,"_jetpack_memberships_contains_paid_content":false,"footnotes":""},"categories":[1],"tags":[121,7,98,120],"class_list":["post-1104","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-blog-post","tag-anchor","tag-css","tag-dialog","tag-popover"],"acf":[],"jetpack_featured_media_url":"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/03\/toast-thumb.jpg?fit=1000%2C500&ssl=1","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/posts\/1104","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/users\/14"}],"replies":[{"embeddable":true,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/comments?post=1104"}],"version-history":[{"count":5,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/posts\/1104\/revisions"}],"predecessor-version":[{"id":1113,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/posts\/1104\/revisions\/1113"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/media\/1110"}],"wp:attachment":[{"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/media?parent=1104"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/categories?post=1104"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/tags?post=1104"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}