<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Anja Beisel</title>
    <description>The latest articles on DEV Community by Anja Beisel (@anjab).</description>
    <link>https://dev.to/anjab</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F278708%2F31149b75-ac5c-45e4-9128-898ca29e871e.png</url>
      <title>DEV Community: Anja Beisel</title>
      <link>https://dev.to/anjab</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/anjab"/>
    <language>en</language>
    <item>
      <title>The Hybrid Analytics Architecture</title>
      <dc:creator>Anja Beisel</dc:creator>
      <pubDate>Mon, 23 Mar 2026 12:26:02 +0000</pubDate>
      <link>https://dev.to/anjab/the-hybrid-analytics-architecture-4chc</link>
      <guid>https://dev.to/anjab/the-hybrid-analytics-architecture-4chc</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fx1d613sie2pd6e39egok.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fx1d613sie2pd6e39egok.png" alt="In a hybrid setup, privacy-first analytics runs for everyone, while Google Analytics is enabled only after explicit consent." width="800" height="480"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Privacy-First Analytics for Modern Web Apps (Part 4)
&lt;/h2&gt;

&lt;p&gt;A modern and practical solution is to combine two analytics systems:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Layer 1&lt;/strong&gt;: privacy-first analytics for all visitors&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Layer 2&lt;/strong&gt;: Google Analytics only for users who explicitly accept cookies&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This architecture separates two different goals:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;understanding product usage&lt;/li&gt;
&lt;li&gt;running detailed analytics and marketing analysis&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;By splitting these responsibilities, you get useful insights without relying entirely on cookie-based tracking. &lt;/p&gt;

&lt;h3&gt;
  
  
  Why a hybrid analytics setup helps
&lt;/h3&gt;

&lt;p&gt;When using &lt;strong&gt;Basic Consent Mode&lt;/strong&gt;, users who decline analytics cookies are completely invisible in Google Analytics. Your analytics becomes incomplete.&lt;/p&gt;

&lt;p&gt;A &lt;strong&gt;hybrid architecture&lt;/strong&gt; solves this by using a privacy-first analytics platform for baseline reporting while keeping Google Analytics behind explicit consent.&lt;/p&gt;

&lt;p&gt;In practice this means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;everyone contributes to anonymous product analytics&lt;/li&gt;
&lt;li&gt;only consenting users are tracked in Google Analytics&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The result is a better balance between:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;privacy&lt;/li&gt;
&lt;li&gt;product insight&lt;/li&gt;
&lt;li&gt;legal safety&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Layer 1 — Privacy-first analytics
&lt;/h3&gt;

&lt;p&gt;For the first layer, you use a tool designed to work without cookies and without persistent identifiers.&lt;/p&gt;

&lt;p&gt;Popular options include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Umami&lt;/li&gt;
&lt;li&gt;Plausible&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These tools typically provide:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;pageviews&lt;/li&gt;
&lt;li&gt;referrers&lt;/li&gt;
&lt;li&gt;country / region statistics&lt;/li&gt;
&lt;li&gt;device types&lt;/li&gt;
&lt;li&gt;simple product-usage events&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;They intentionally avoid:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;cross-site tracking&lt;/li&gt;
&lt;li&gt;persistent user identifiers&lt;/li&gt;
&lt;li&gt;advertising profiles&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This makes them easier to justify from a privacy perspective.&lt;/p&gt;

&lt;h4&gt;
  
  
  Example: Event tracking with Unami
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;Setting up Umami&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Umami offers both cloud hosting and self-hosting.&lt;/p&gt;

&lt;p&gt;Typical onboarding steps are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Create an account at Umami Cloud.&lt;/li&gt;
&lt;li&gt;Add your website domain.&lt;/li&gt;
&lt;li&gt;Copy the tracking script from the dashboard.&lt;/li&gt;
&lt;li&gt;Insert it into your app’s HTML template.&lt;/li&gt;
&lt;li&gt;Verify pageviews appear in the analytics dashboard.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;At the time of writing, Umami Cloud offers a Hobby tier with around 100k events/month for free, though pricing can change.&lt;/p&gt;

&lt;p&gt;The dashboard typically shows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;top pages&lt;/li&gt;
&lt;li&gt;traffic sources&lt;/li&gt;
&lt;li&gt;device types&lt;/li&gt;
&lt;li&gt;countries&lt;/li&gt;
&lt;li&gt;custom events&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is already enough to answer many product questions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Event tracking with Umami in React + TypeScript&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Add the Umami tracker script to your main HTML template.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;defer&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"https://cloud.umami.is/script.js"&lt;/span&gt; &lt;span class="na"&gt;data-website-id=&lt;/span&gt;&lt;span class="s"&gt;"YOUR_UMAMI_WEBSITE_ID"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This automatically tracks pageviews.&lt;/p&gt;

&lt;p&gt;Create a helper wrapper in your React project.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/lib/analytics/umami.ts&lt;/span&gt;

&lt;span class="kr"&gt;declare&lt;/span&gt; &lt;span class="nb"&gt;global&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;Window&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;umami&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;track&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;event&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nb"&gt;Record&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;unknown&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;trackEvent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
   &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nb"&gt;Record&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;unknown&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
   &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;umami&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;track&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add the event tracking helper in a component&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;trackEvent&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;../lib/analytics/umami&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;OpenEditorButton&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
   &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;
         &lt;span class="na"&gt;onClick&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
             &lt;span class="nf"&gt;trackEvent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;open_editor&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
         &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      Open editor
      &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
   &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This event will appear in the Umami dashboard. Typical product-analytics events might include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;open_editor&lt;/li&gt;
&lt;li&gt;view_gallery_item&lt;/li&gt;
&lt;li&gt;view_pricing_page&lt;/li&gt;
&lt;li&gt;start_publish&lt;/li&gt;
&lt;li&gt;publish_complete&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Layer 2 — Google Analytics (opt-in only)
&lt;/h3&gt;

&lt;p&gt;For users who explicitly accept analytics cookies, you can enable Google Analytics. Google Analytics provides deeper analysis including:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;user journeys&lt;/li&gt;
&lt;li&gt;funnels&lt;/li&gt;
&lt;li&gt;campaign attribution&lt;/li&gt;
&lt;li&gt;conversion analysis&lt;/li&gt;
&lt;li&gt;returning sessions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Because GA uses cookies and identifiers, it should only be initialized after consent.&lt;/p&gt;

&lt;h4&gt;
  
  
  Example: Remember consent and track GA events only with given content
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;Add a remember consent helper&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/lib/analytics/consent.ts&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getConsent&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
   &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;localStorage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;cookie_consent&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

   &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;true&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
   &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;false&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;

   &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;setConsent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
   &lt;span class="nx"&gt;localStorage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;cookie_consent&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;true&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;false&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Add google analytics helper&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/lib/analytics/googleAnalytics.ts&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;ReactGA&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;react-ga4&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;GA_ID&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;REACT_APP_GA_ID&lt;/span&gt;

&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;initialized&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;initGA&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;GA_ID&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;initialized&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;

  &lt;span class="nx"&gt;ReactGA&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;initialize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;GA_ID&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nx"&gt;initialized&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;trackPageView&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;initialized&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;

  &lt;span class="nx"&gt;ReactGA&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;hitType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;pageview&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;page&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Enable GA only after consent&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Example cookie consent handler:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;setConsent&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;../analytics/consent&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;initGA&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;../analytics/googleAnalytics&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;acceptAnalytics&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;setConsent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nf"&gt;initGA&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;declineAnalytics&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;setConsent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Initialize GA on page load if previously accepted&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useEffect&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;getConsent&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./analytics/consent&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;initGA&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./analytics/googleAnalytics&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;App&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;getConsent&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;initGA&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[])&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Routes&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  How the two layers work together
&lt;/h3&gt;

&lt;p&gt;With the hybrid setup, the user journey looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Visitor arrives
  ↓
Umami analytics runs for everyone
  ↓
Cookie banner appears
  ↓
Accept → Google Analytics is initialized
Decline → Google Analytics stays disabled
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This provides:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;baseline product analytics for all visitors&lt;/li&gt;
&lt;li&gt;deeper insights for consenting users&lt;/li&gt;
&lt;li&gt;a clearer privacy model&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  What each layer is best at
&lt;/h4&gt;

&lt;p&gt;Privacy-first analytics (Umami / Plausible)&lt;/p&gt;

&lt;p&gt;Best for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;product decisions&lt;/li&gt;
&lt;li&gt;feature usage&lt;/li&gt;
&lt;li&gt;top pages&lt;/li&gt;
&lt;li&gt;referrers&lt;/li&gt;
&lt;li&gt;general traffic insight&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Google Analytics&lt;/p&gt;

&lt;p&gt;Best for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;funnels&lt;/li&gt;
&lt;li&gt;marketing analysis&lt;/li&gt;
&lt;li&gt;campaign attribution&lt;/li&gt;
&lt;li&gt;deeper engagement analysis&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Separating these roles is exactly why the hybrid architecture works well.&lt;/p&gt;

&lt;h3&gt;
  
  
  Practical recommendation
&lt;/h3&gt;

&lt;p&gt;For most modern web apps — especially developer tools — a good starting setup is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Umami for all visitors&lt;/li&gt;
&lt;li&gt;Google Analytics only after consent&lt;/li&gt;
&lt;li&gt;no Advanced Consent Mode initially&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This gives you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;useful analytics from day one&lt;/li&gt;
&lt;li&gt;less legal ambiguity&lt;/li&gt;
&lt;li&gt;a clean foundation if you later introduce advertising or subscriptions&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  One final note on privacy
&lt;/h3&gt;

&lt;p&gt;Privacy-first analytics tools often market themselves as cookie-free and simpler from a compliance perspective. That can reduce complexity, but it does not remove your responsibility to review your own legal obligations.&lt;/p&gt;

&lt;p&gt;A good rule of thumb is simple transparency:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;anonymous product analytics help improve the product&lt;/li&gt;
&lt;li&gt;optional analytics cookies enable deeper reporting&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Users usually respond well to that level of honesty.&lt;/p&gt;




&lt;h3&gt;
  
  
  Series: Privacy-First Analytics for Modern Web Apps
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;&lt;a href="https://dev.to/anjab/your-cookie-consent-banner-is-probably-breaking-your-analytics-5c4h"&gt;Why Cookie Consent Breaks Your Analytics&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/anjab/google-consent-mode-explained-react-typescript-2ld6"&gt;Google Consent Mode Explained (React + TypeScript)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/anjab/ads-tracking-and-the-legal-reality-in-the-eu-and-uk-lcn"&gt;Ads, Tracking, and the Legal Reality in the EU and UK&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The Hybrid Analytics Architecture&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;




&lt;h3&gt;
  
  
  About this series
&lt;/h3&gt;

&lt;p&gt;This series is based on real-world work building &lt;strong&gt;CSSEXY&lt;/strong&gt;, a visual UI platform where understanding user behaviour is essential for improving the product.&lt;/p&gt;

&lt;p&gt;All articles are also available on CSSEXY and there in the Gallery.&lt;/p&gt;

</description>
      <category>analytics</category>
      <category>google</category>
      <category>react</category>
      <category>typescript</category>
    </item>
    <item>
      <title>Ads, Tracking, and the Legal Reality in the EU and UK</title>
      <dc:creator>Anja Beisel</dc:creator>
      <pubDate>Sun, 22 Mar 2026 19:17:39 +0000</pubDate>
      <link>https://dev.to/anjab/ads-tracking-and-the-legal-reality-in-the-eu-and-uk-lcn</link>
      <guid>https://dev.to/anjab/ads-tracking-and-the-legal-reality-in-the-eu-and-uk-lcn</guid>
      <description>&lt;h2&gt;
  
  
  Privacy-First Analytics for Modern Web Apps (Part 3)
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Why analytics, advertising, and consent are tightly connected — and where the risks actually are
&lt;/h3&gt;

&lt;p&gt;Analytics and advertising are often discussed separately.&lt;/p&gt;

&lt;p&gt;In reality, they are deeply connected.&lt;/p&gt;

&lt;p&gt;The moment you introduce ads — or even just conversion tracking — your analytics setup becomes part of a much larger system involving:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;user tracking&lt;/li&gt;
&lt;li&gt;profiling&lt;/li&gt;
&lt;li&gt;legal compliance&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To understand the trade-offs, it helps to break things down.&lt;/p&gt;




&lt;h3&gt;
  
  
  The three types of advertising
&lt;/h3&gt;

&lt;p&gt;Not all ads are the same. From a privacy and legal perspective, there are three fundamentally different models.&lt;/p&gt;




&lt;h4&gt;
  
  
  1. Contextual advertising (privacy-first)
&lt;/h4&gt;

&lt;p&gt;Contextual ads are selected based on &lt;strong&gt;what the user is currently viewing&lt;/strong&gt;, not who they are.&lt;/p&gt;

&lt;p&gt;Example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Page about CSS tools
  ↓
Ad for web hosting
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Advantages&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;no user tracking&lt;/li&gt;
&lt;li&gt;no personal profiles&lt;/li&gt;
&lt;li&gt;simple compliance&lt;/li&gt;
&lt;li&gt;works without consent banners in many cases&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Typical networks&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Carbon Ads&lt;/li&gt;
&lt;li&gt;EthicalAds&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is the most privacy-friendly model and aligns well with developer-focused platforms.&lt;/p&gt;




&lt;h4&gt;
  
  
  2. Personalized advertising (tracking-heavy)
&lt;/h4&gt;

&lt;p&gt;Personalized ads rely on &lt;strong&gt;tracking users across multiple websites&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User visits hosting websites
  ↓
Ad network builds profile
  ↓
Hosting ads shown elsewhere
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Advantages&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;higher ad revenue&lt;/li&gt;
&lt;li&gt;better targeting&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Disadvantages&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;requires extensive tracking&lt;/li&gt;
&lt;li&gt;strict consent requirements&lt;/li&gt;
&lt;li&gt;legally more complex&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Typical platform&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Google AdSense&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is where most privacy concerns — and regulations — apply.&lt;/p&gt;




&lt;h4&gt;
  
  
  3. Conversion tracking (the hidden layer)
&lt;/h4&gt;

&lt;p&gt;Even if you are not running ads, you may still care about:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Which action led to a signup?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is called &lt;strong&gt;conversion tracking&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;It is often used for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;marketing campaigns&lt;/li&gt;
&lt;li&gt;onboarding optimisation&lt;/li&gt;
&lt;li&gt;product growth&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is also where &lt;strong&gt;Google Consent Mode&lt;/strong&gt; becomes relevant.&lt;/p&gt;




&lt;h3&gt;
  
  
  Where Advanced Consent Mode fits in
&lt;/h3&gt;

&lt;p&gt;Advanced Consent Mode tries to solve a key problem:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;How can you measure behaviour if users decline cookies?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It does this by sending &lt;strong&gt;cookieless signals&lt;/strong&gt; such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;page views&lt;/li&gt;
&lt;li&gt;timestamps&lt;/li&gt;
&lt;li&gt;device/browser info&lt;/li&gt;
&lt;li&gt;approximate location&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Google then uses modelling to estimate user behaviour.&lt;/p&gt;




&lt;h3&gt;
  
  
  The legal reality in the EU and UK
&lt;/h3&gt;

&lt;p&gt;Privacy regulation in Europe mainly comes from:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;GDPR&lt;/li&gt;
&lt;li&gt;ePrivacy / PECR&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The core rule is simple:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Users must give consent before non-essential technologies access or store information on their device.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Traditional analytics cookies clearly require consent.&lt;/p&gt;




&lt;h3&gt;
  
  
  Why Advanced Consent Mode is debated
&lt;/h3&gt;

&lt;p&gt;Even without cookies, Advanced Consent Mode still sends:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;device information&lt;/li&gt;
&lt;li&gt;page URLs&lt;/li&gt;
&lt;li&gt;IP-based location&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Some privacy experts argue that this still counts as:&lt;/p&gt;

&lt;p&gt;👉 accessing information from a user’s device&lt;/p&gt;

&lt;p&gt;Because of this, Advanced Consent Mode sits in a &lt;strong&gt;legal grey area&lt;/strong&gt;.&lt;/p&gt;




&lt;h3&gt;
  
  
  What can actually happen in practice
&lt;/h3&gt;

&lt;p&gt;In reality, enforcement tends to focus on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;large platforms&lt;/li&gt;
&lt;li&gt;large-scale tracking&lt;/li&gt;
&lt;li&gt;clear violations&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Examples of companies involved in major enforcement actions include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Meta&lt;/li&gt;
&lt;li&gt;Google&lt;/li&gt;
&lt;li&gt;TikTok&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For smaller websites, the most likely scenario is much simpler:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Regulator review
  ↓
Request to disable tracking before consent
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In most cases:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;no fines&lt;/li&gt;
&lt;li&gt;no legal escalation&lt;/li&gt;
&lt;li&gt;just a requirement to adjust implementation&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  What this means for developers
&lt;/h3&gt;

&lt;p&gt;When choosing an analytics and advertising setup, you are balancing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;data quality&lt;/li&gt;
&lt;li&gt;legal clarity&lt;/li&gt;
&lt;li&gt;implementation complexity&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A simple way to think about it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Contextual ads → lowest risk
Hybrid analytics → balanced
Advanced consent mode → more data, more uncertainty
Personalized ads → highest risk
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  Key takeaway
&lt;/h3&gt;

&lt;p&gt;The moment you introduce:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;ads&lt;/li&gt;
&lt;li&gt;tracking&lt;/li&gt;
&lt;li&gt;or conversion measurement&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;you are no longer just dealing with analytics.&lt;/p&gt;

&lt;p&gt;You are designing a &lt;strong&gt;data collection system under legal constraints&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Understanding the differences between advertising models and consent strategies is essential to making the right trade-offs.&lt;/p&gt;




&lt;h3&gt;
  
  
  Series: Privacy-First Analytics for Modern Web Apps
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;&lt;a href="https://dev.to/anjab/your-cookie-consent-banner-is-probably-breaking-your-analytics-5c4h"&gt;Why Cookie Consent Breaks Your Analytics&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/anjab/google-consent-mode-explained-react-typescript-2ld6"&gt;Google Consent Mode Explained (React + TypeScript)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Ads, Tracking, and the Legal Reality in the EU and UK&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/anjab/the-hybrid-analytics-architecture-4chc"&gt;The Hybrid Analytics Architecture&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;




&lt;h3&gt;
  
  
  About this series
&lt;/h3&gt;

&lt;p&gt;This series is based on real-world work building &lt;strong&gt;CSSEXY&lt;/strong&gt;, a visual UI platform where understanding user behaviour is essential for improving the product.&lt;/p&gt;

&lt;p&gt;All articles are also available on CSSEXY and there in the Gallery.&lt;/p&gt;

</description>
      <category>analytics</category>
      <category>google</category>
      <category>react</category>
      <category>typescript</category>
    </item>
    <item>
      <title>Google Consent Mode Explained (React + TypeScript)</title>
      <dc:creator>Anja Beisel</dc:creator>
      <pubDate>Fri, 20 Mar 2026 09:10:01 +0000</pubDate>
      <link>https://dev.to/anjab/google-consent-mode-explained-react-typescript-2ld6</link>
      <guid>https://dev.to/anjab/google-consent-mode-explained-react-typescript-2ld6</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fijq7gz65lci94asdcg8l.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fijq7gz65lci94asdcg8l.png" alt="Google consent Mode" width="800" height="250"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Privacy-First Analytics for Modern Web Apps (Part 2)
&lt;/h2&gt;

&lt;p&gt;To address the data-loss problem (see Part 1), Google introduced &lt;strong&gt;Consent Mode&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The goal is to allow analytics tags to load &lt;strong&gt;before consent&lt;/strong&gt;, but with restricted behaviour.&lt;/p&gt;




&lt;h3&gt;
  
  
  What Advanced Consent Mode does
&lt;/h3&gt;

&lt;p&gt;With Advanced Consent Mode:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User visits site
  ↓
Google tag loads
  ↓
Consent state = denied
  ↓
Cookieless pings sent
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These pings may contain:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;page URL&lt;/li&gt;
&lt;li&gt;timestamp&lt;/li&gt;
&lt;li&gt;browser / device type&lt;/li&gt;
&lt;li&gt;approximate region&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But they &lt;strong&gt;do not set cookies&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Google uses these signals to estimate user behaviour.&lt;/p&gt;




&lt;h3&gt;
  
  
  Basic vs Advanced Consent Mode
&lt;/h3&gt;

&lt;p&gt;Basic consent mode: Google Analytics loads only after consent&lt;/p&gt;

&lt;p&gt;Advanced consent mode: Google tag loads immediately with restricted tracking&lt;/p&gt;




&lt;h3&gt;
  
  
  Implementing Basic Consent Mode (React + TypeScript)
&lt;/h3&gt;

&lt;p&gt;Example cookie consent logic:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;ReactGA&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;react-ga4&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;enableAnalytics&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;GA_ID&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;REACT_APP_GA_ID&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;GA_ID&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;ReactGA&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;initialize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;GA_ID&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Called when the user clicks &lt;strong&gt;Accept&lt;/strong&gt;.&lt;/p&gt;




&lt;h3&gt;
  
  
  Implementing Advanced Consent Mode
&lt;/h3&gt;

&lt;p&gt;Advanced Consent Mode uses the &lt;code&gt;gtag&lt;/code&gt; consent API.&lt;/p&gt;

&lt;p&gt;Default state:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;gtag&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;consent&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;default&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;analytics_storage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;denied&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;ad_storage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;denied&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When the user accepts:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;gtag&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;consent&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;update&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;analytics_storage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;granted&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;ad_storage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;granted&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When the user declines:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;gtag&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;consent&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;update&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;analytics_storage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;denied&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This allows the Google tag to operate in &lt;strong&gt;restricted cookieless mode&lt;/strong&gt;.&lt;/p&gt;




&lt;h3&gt;
  
  
  Series: Privacy-First Analytics for Modern Web Apps
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;&lt;a href="https://dev.to/anjab/your-cookie-consent-banner-is-probably-breaking-your-analytics-5c4h"&gt;Why Cookie Consent Breaks Your Analytics&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Google Consent Mode Explained (React + TypeScript)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/anjab/ads-tracking-and-the-legal-reality-in-the-eu-and-uk-lcn"&gt;Ads, Tracking, and the Legal Reality in the EU and UK&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/anjab/the-hybrid-analytics-architecture-4chc"&gt;The Hybrid Analytics Architecture&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;




&lt;h3&gt;
  
  
  About this series
&lt;/h3&gt;

&lt;p&gt;This series is based on real-world work building &lt;strong&gt;CSSEXY&lt;/strong&gt;, a visual UI platform where understanding user behaviour is essential for improving the product.&lt;/p&gt;

&lt;p&gt;All articles are also available on CSSEXY and there in the Gallery.&lt;/p&gt;

</description>
      <category>analytics</category>
      <category>google</category>
      <category>react</category>
      <category>typescript</category>
    </item>
    <item>
      <title>The HTML popover Attribute — Complete Deep Dive</title>
      <dc:creator>Anja Beisel</dc:creator>
      <pubDate>Wed, 18 Mar 2026 18:32:53 +0000</pubDate>
      <link>https://dev.to/anjab/the-html-popover-attribute-complete-deep-dive-l6d</link>
      <guid>https://dev.to/anjab/the-html-popover-attribute-complete-deep-dive-l6d</guid>
      <description>&lt;p&gt;The popover attribute is a modern, built-in way to create lightweight overlays like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;dropdowns&lt;/li&gt;
&lt;li&gt;menus&lt;/li&gt;
&lt;li&gt;tooltips&lt;/li&gt;
&lt;li&gt;context panels&lt;/li&gt;
&lt;li&gt;mini-dialogs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It is native HTML, meaning:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;no JavaScript library required&lt;/li&gt;
&lt;li&gt;no ARIA hacks required&lt;/li&gt;
&lt;li&gt;no portal logic&lt;/li&gt;
&lt;li&gt;minimal focus management (mostly handled for you)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It was added as part of the Open UI / HTML Living Standard and is now supported in modern Chromium, Safari, and Firefox versions.&lt;/p&gt;




&lt;h3&gt;
  
  
  Why this matters
&lt;/h3&gt;

&lt;p&gt;For years, building dropdowns, menus, and overlays meant:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;custom JS state management&lt;/li&gt;
&lt;li&gt;focus traps&lt;/li&gt;
&lt;li&gt;event listeners everywhere&lt;/li&gt;
&lt;li&gt;z-index bugs&lt;/li&gt;
&lt;li&gt;accessibility edge cases&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The Popover API replaces most of that with native browser behavior.&lt;/p&gt;

&lt;p&gt;This is one of the biggest shifts in UI development since &lt;code&gt;&amp;lt;dialog&amp;gt;&lt;/code&gt;.&lt;/p&gt;




&lt;h3&gt;
  
  
  Popover vs Dialog vs Div
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;Popover&lt;/th&gt;
&lt;th&gt;Dialog&lt;/th&gt;
&lt;th&gt;Div + JS&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Built-in open/close&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Light dismiss&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;❌ (modal only)&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Top layer&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Focus management&lt;/td&gt;
&lt;td&gt;Partial&lt;/td&gt;
&lt;td&gt;Strong&lt;/td&gt;
&lt;td&gt;Manual&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Positioning&lt;/td&gt;
&lt;td&gt;Needs anchor&lt;/td&gt;
&lt;td&gt;Centered&lt;/td&gt;
&lt;td&gt;Flexible&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Best for&lt;/td&gt;
&lt;td&gt;Menus, dropdowns&lt;/td&gt;
&lt;td&gt;Modals&lt;/td&gt;
&lt;td&gt;Custom layouts&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h3&gt;
  
  
  Basic Concept
&lt;/h3&gt;

&lt;p&gt;A popover is a hidden element that can be shown/hidden declaratively via attributes. You declare&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;popover&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"menu"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  Hello Popover
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The element is hidden by default. To show it, connect it to a button:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;popovertarget=&lt;/span&gt;&lt;span class="s"&gt;"menu"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Open&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That’s it. No JS required.&lt;/p&gt;




&lt;h3&gt;
  
  
  How it works internally
&lt;/h3&gt;

&lt;p&gt;When a popover is opened, it is removed from &lt;code&gt;display: none&lt;/code&gt;, enters the top layer (like &lt;code&gt;&amp;lt;dialog&amp;gt;&lt;/code&gt;) and so renders above all stacking contexts and behaves independently of &lt;code&gt;z-index&lt;/code&gt;, and is not clipped by &lt;code&gt;overflow: hidden&lt;/code&gt;.&lt;/p&gt;




&lt;h3&gt;
  
  
  popover Attribute Values
&lt;/h3&gt;

&lt;h4&gt;
  
  
  &lt;code&gt;&amp;lt;div popover="auto"&amp;gt;&lt;/code&gt; or &lt;code&gt;&amp;lt;div popover&amp;gt;&lt;/code&gt; (default)
&lt;/h4&gt;

&lt;p&gt;Behavior&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;clicking outside closes it&lt;/li&gt;
&lt;li&gt;pressing ESC closes it&lt;/li&gt;
&lt;li&gt;only one auto popover open at a time&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is ideal for menus, dropdowns, tooltips,  context panels&lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;code&gt;&amp;lt;div popover="manual"&amp;gt;&lt;/code&gt;
&lt;/h4&gt;

&lt;p&gt;Behavior&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;does NOT close automatically&lt;/li&gt;
&lt;li&gt;you must close it manually via JS&lt;/li&gt;
&lt;li&gt;multiple can be open simultaneously&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Use this when you need custom open/close logic, you're building complex UI.&lt;/p&gt;




&lt;h3&gt;
  
  
  How To Trigger a Popover
&lt;/h3&gt;

&lt;p&gt;Declarative Trigger&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;popovertarget=&lt;/span&gt;&lt;span class="s"&gt;"myPopover"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  Open
&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;popover&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"myPopover"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  Content
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can control the toggle behavior with &lt;code&gt;popovertargetaction&lt;/code&gt; with possible values &lt;code&gt;toggle&lt;/code&gt; (default), &lt;code&gt;show&lt;/code&gt; or &lt;code&gt;hide&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt;
   &lt;span class="na"&gt;popovertarget=&lt;/span&gt;&lt;span class="s"&gt;"myPopover"&lt;/span&gt;
   &lt;span class="na"&gt;popovertargetaction=&lt;/span&gt;&lt;span class="s"&gt;"show"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  JavaScript API
&lt;/h3&gt;

&lt;p&gt;You can control popovers manually&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;pop&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;myPopover&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;pop&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;showPopover&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nx"&gt;pop&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;hidePopover&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nx"&gt;pop&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;togglePopover&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="c1"&gt;// check popover state&lt;/span&gt;
&lt;span class="nx"&gt;pop&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;matches&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;:popover-open&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  Styling Popovers
&lt;/h3&gt;

&lt;p&gt;The default styling depends on the browser. For example in Chrome you see&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nt"&gt;popover&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="nd"&gt;:where&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nd"&gt;:not&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nd"&gt;:popover-open&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
   &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;none&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nt"&gt;popover&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="nd"&gt;:popover-open&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
   &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;block&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
   &lt;span class="py"&gt;overlay&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;auto&lt;/span&gt; &lt;span class="cp"&gt;!important&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nt"&gt;popover&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
   &lt;span class="nl"&gt;position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;fixed&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
   &lt;span class="py"&gt;position-anchor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;auto&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
   &lt;span class="nl"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;fit-content&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
   &lt;span class="nl"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;fit-content&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
   &lt;span class="nl"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;canvastext&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
   &lt;span class="nl"&gt;background-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;canvas&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
   &lt;span class="py"&gt;inset&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
   &lt;span class="nl"&gt;margin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;auto&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
   &lt;span class="nl"&gt;border-width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;initial&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
   &lt;span class="nl"&gt;border-style&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;solid&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
   &lt;span class="nl"&gt;border-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;initial&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
   &lt;span class="nl"&gt;border-image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;initial&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
   &lt;span class="nl"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0.25em&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
   &lt;span class="nl"&gt;overflow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;auto&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Popover is in the top layer, with fixed position, and centered on the visible screen. A simple way to style it is to leave its positioning, inset, and margin alone.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nt"&gt;popover&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
   &lt;span class="nl"&gt;border&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;none&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
   &lt;span class="nl"&gt;border-radius&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--radius-xl&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
   &lt;span class="nl"&gt;box-shadow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--shadow-lg&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
   &lt;span class="py"&gt;padding-block&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;calc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--spacing&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="err"&gt;*&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
   &lt;span class="py"&gt;padding-inline&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;calc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--spacing&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="err"&gt;*&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
   &lt;span class="nl"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;max-content&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
   &lt;span class="nl"&gt;max-width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;40rem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To add an effect when the Popover opens, use the pseudo class &lt;code&gt;:popover-open&lt;/code&gt; like in&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nt"&gt;popover&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="nc"&gt;.popIn&lt;/span&gt;&lt;span class="nd"&gt;:popover-open&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
   &lt;span class="nl"&gt;animation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;menuPopIn&lt;/span&gt; &lt;span class="m"&gt;0.2s&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;@keyframes&lt;/span&gt; &lt;span class="n"&gt;menuPopIn&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
   &lt;span class="err"&gt;0&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nl"&gt;opacity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nl"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;scale&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0.9&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0.9&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
   &lt;span class="p"&gt;}&lt;/span&gt;
   &lt;span class="err"&gt;60&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nl"&gt;opacity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nl"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;scale&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1.03&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;1.03&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
   &lt;span class="p"&gt;}&lt;/span&gt;
   &lt;span class="err"&gt;100&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nl"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;scale&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
   &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The open Popover has a &lt;code&gt;::backdrop&lt;/code&gt; pseudo-element which you can style&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nt"&gt;popover&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="nc"&gt;.showBackdrop&lt;/span&gt;&lt;span class="nd"&gt;::backdrop&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
   &lt;span class="nl"&gt;background-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--blue-500-10&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
   &lt;span class="py"&gt;backdrop-filter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;blur&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;8px&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  Popover positioning
&lt;/h3&gt;

&lt;p&gt;As mentioned before, a popover is opened on the top layer, position fixed and centered. &lt;/p&gt;

&lt;p&gt;But what if I want a popover for example positioned relative to the button which opens it?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.popover-wrapper&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
   &lt;span class="nl"&gt;position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;absolute&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
   &lt;span class="nl"&gt;top&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
   &lt;span class="nl"&gt;left&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"relative"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
   &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"button"&lt;/span&gt; &lt;span class="na"&gt;popovertarget=&lt;/span&gt;&lt;span class="s"&gt;"popover3"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Open popover&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
   &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"popover3"&lt;/span&gt; &lt;span class="na"&gt;popover=&lt;/span&gt;&lt;span class="s"&gt;"auto"&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"popover-wrapper"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      ...
   &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt; 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As you've probably guessed, the popover will not behave like a normal absolutely positioned element inside a relative parent. Popover elements are rendered in the top layer. They are removed from normal DOM stacking. They are not positioned relative to their DOM parent. It will appear at Top-Left of the viewport.&lt;/p&gt;

&lt;p&gt;If you want to position a popover relative to another element, you have three real options&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;anchor Positioning (Best modern solution)&lt;/li&gt;
&lt;li&gt;JS positioning&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;const rect = button.getBoundingClientRect();&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;popover.style.top = rect.bottom + "px";&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;popover.style.left = rect.left + "px";&lt;/code&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;don’t use popover&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Use a normal &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt;. Toggle visibility manually.  Then &lt;code&gt;position: absolute&lt;/code&gt; works normally.&lt;/p&gt;




&lt;h3&gt;
  
  
  Anchored Positioning
&lt;/h3&gt;

&lt;p&gt;Modern browser support anchor positioning&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nt"&gt;button&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="py"&gt;anchor-name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;--tabs&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nt"&gt;popover&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="py"&gt;position-anchor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;--tabs&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This allows true dynamic anchoring without JS.&lt;/p&gt;

&lt;p&gt;By assigning an &lt;code&gt;anchor name&lt;/code&gt; and then the anchor to the popover with &lt;code&gt;position-anchor&lt;/code&gt;, the distance between the anchor and the viewport top and left is available in the styles of the popover.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fv063wn8993yl4p0yyu7o.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fv063wn8993yl4p0yyu7o.png" alt="CSS Popover Anchoring" width="800" height="367"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.anchor&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="py"&gt;anchor-name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;--tabs&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.anchored-popover&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="py"&gt;position-anchor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;--tabs&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;top&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;anchor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;bottom&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nl"&gt;left&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;anchor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;right&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nl"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;translateX&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;-100%&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nl"&gt;margin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;div&amp;gt;&lt;/span&gt;
   &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"button"&lt;/span&gt; &lt;span class="na"&gt;popovertarget=&lt;/span&gt;&lt;span class="s"&gt;"popover3"&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"anchor"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Anchor positioned popover&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
   &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"popover3"&lt;/span&gt; &lt;span class="na"&gt;popover=&lt;/span&gt;&lt;span class="s"&gt;"auto"&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"anchored-popover"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;ul&amp;gt;&lt;/span&gt;
         &lt;span class="nt"&gt;&amp;lt;li&amp;gt;&lt;/span&gt;Tree&lt;span class="nt"&gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
         &lt;span class="nt"&gt;&amp;lt;li&amp;gt;&lt;/span&gt;Add&lt;span class="nt"&gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
         &lt;span class="nt"&gt;&amp;lt;li&amp;gt;&lt;/span&gt;Edit&lt;span class="nt"&gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
         &lt;span class="nt"&gt;&amp;lt;li&amp;gt;&lt;/span&gt;Assign CSS&lt;span class="nt"&gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;/ul&amp;gt;&lt;/span&gt;
   &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  No anchor positioning in older browsers: CSS supports check + JS fallback
&lt;/h3&gt;

&lt;p&gt;Popover API and CSS Anchor Positioning are both young, with Popover API being realistically supported by all modern engines since April 2024. CSS Anchor Positioning only recently landed in Firefox (late 2025). &lt;/p&gt;

&lt;p&gt;Though you could demand that your users work with modern browser only, there is a safer way and it is not hard to set up. &lt;/p&gt;

&lt;p&gt;Use CSS &lt;code&gt;@supports&lt;/code&gt; to check if your browser supports CSS anchoring&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="k"&gt;@supports&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;anchor-name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;--a&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
   &lt;span class="nc"&gt;.anchor&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; 
      &lt;span class="py"&gt;anchor-name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;--tabs&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; 
   &lt;span class="p"&gt;}&lt;/span&gt;

   &lt;span class="nc"&gt;.anchored-popover&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="py"&gt;position-anchor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;--tabs&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nl"&gt;top&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;anchor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;bottom&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nl"&gt;left&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;anchor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;right&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nl"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;translateX&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;-100%&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
   &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If CSS anchoring is not supported, add a JS fallback, here one way to do it with a React Custom Hook&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;supportsAnchors&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
   &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;!!&lt;/span&gt;&lt;span class="nx"&gt;CSS&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;supports&lt;/span&gt;&lt;span class="p"&gt;?.(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;anchor-name: --a&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;useGetAnchorStyles&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
   &lt;span class="nx"&gt;anchorName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="nx"&gt;pos&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="nx"&gt;offSetH&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="nx"&gt;offSetV&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="nx"&gt;popoverRef&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="nx"&gt;anchorRef&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
   &lt;span class="nl"&gt;anchorName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
   &lt;span class="nl"&gt;pos&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;TPos&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
   &lt;span class="nx"&gt;offSetH&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
   &lt;span class="nx"&gt;offSetV&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
   &lt;span class="nl"&gt;popoverRef&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;RefObject&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;HTMLElement&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
   &lt;span class="nl"&gt;anchorRef&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;RefObject&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;HTMLElement&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
   &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;styles&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setStyles&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;CSSProperties&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;({});&lt;/span&gt;

   &lt;span class="nf"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;pop&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;popoverRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;anchor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;anchorRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;pop&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;anchor&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;handleToggle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
         &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;toggleEvent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;ToggleEvent&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
         &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;isOpen&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;toggleEvent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;newState&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;open&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

         &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;isOpen&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nf"&gt;supportsAnchors&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;nextStyles&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getPopUpPositionRelativeToAnchor&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
               &lt;span class="nx"&gt;pos&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
               &lt;span class="nx"&gt;offSetH&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
               &lt;span class="nx"&gt;offSetV&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
               &lt;span class="nx"&gt;popoverRef&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
               &lt;span class="nx"&gt;anchorRef&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;});&lt;/span&gt;
            &lt;span class="nf"&gt;setStyles&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;nextStyles&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
         &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;};&lt;/span&gt;
      &lt;span class="nx"&gt;pop&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;toggle&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;handleToggle&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

      &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
         &lt;span class="nx"&gt;pop&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;removeEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;toggle&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;handleToggle&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="p"&gt;};&lt;/span&gt;
   &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;popoverRef&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;anchorRef&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;pos&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;offSetH&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;offSetV&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;

   &lt;span class="nf"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;supportsAnchors&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
         &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;nextStyles&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getAnchorStyles&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
            &lt;span class="nx"&gt;anchorName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="nx"&gt;pos&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="nx"&gt;offSetH&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="nx"&gt;offSetV&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
         &lt;span class="p"&gt;});&lt;/span&gt;
         &lt;span class="nf"&gt;setStyles&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;nextStyles&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
   &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;anchorName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;pos&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;offSetH&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;offSetV&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;

   &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;styles&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And then in your component you set the styles generated by &lt;code&gt;useGetAnchorStyles&lt;/code&gt; on the popover which results, depending on the make and age of the browser of the user, in a relative position of the &lt;code&gt;top&lt;/code&gt; &lt;code&gt;left&lt;/code&gt; corner of the popover as achor() or in pixel.&lt;/p&gt;

&lt;p&gt;React style example&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// browser does support anchor positioning&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
   &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;positionAnchor&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;--html-tree-anchor&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;top&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;anchor(bottom)&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;left&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;anchor(left)&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="c1"&gt;// browser does not support anchor positioning&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
   &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;left&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;73.5px&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;top&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;321.25px&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  While you are here: Fix Popovers Above &lt;code&gt;&amp;lt;iframe&amp;gt;&lt;/code&gt; with pure CSS
&lt;/h3&gt;

&lt;p&gt;When using the Popover API, the browser normally closes a popover automatically when the user clicks outside it. This is called light dismiss and works out of the box with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;popover=&lt;/span&gt;&lt;span class="s"&gt;"auto"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;...&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;However, there is one common case where this breaks. If a popover sits above an &lt;code&gt;&amp;lt;iframe&amp;gt;&lt;/code&gt;, clicking on the iframe does not close the popover. Why? Because an iframe is a separate browsing context. Pointer events inside it do not reach the parent document. So when the popover is open, user clicks iframe, event handled inside iframe document, parent page never receives the outside click and popover stays open.&lt;/p&gt;

&lt;h4&gt;
  
  
  The modern CSS solution
&lt;/h4&gt;

&lt;p&gt;You can disable pointer interaction with the iframe whenever a popover is open:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nt"&gt;body&lt;/span&gt;&lt;span class="nd"&gt;:has&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nd"&gt;:popover-open&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="nt"&gt;iframe&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;pointer-events&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;none&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  How it works
&lt;/h4&gt;

&lt;p&gt;This uses three modern CSS features &lt;code&gt;:has()&lt;/code&gt;, &lt;code&gt;:popover-open&lt;/code&gt; and &lt;code&gt;pointer-events: none&lt;/code&gt;. &lt;code&gt;:has()&lt;/code&gt; allows a parent element to react to the state of its descendants. &lt;code&gt;body:has(:popover-open)&lt;/code&gt; means "if any element in the document currently has an open popover". &lt;code&gt;pointer-events: none&lt;/code&gt; removes the element from pointer hit-testing. Now when a popover is open CSS disables pointer events on iframe. When the user clicks outside the popover in the iframe area, the click goes to the parent page and the browser performs automatic popover light-dismiss, means the popover closes.&lt;/p&gt;

&lt;p&gt;This technique is useful when you have UI elements above an iframe, such as preview panels, embedded demos, code playgrounds, editors with a rendered output, and dashboards with embedded content.&lt;/p&gt;

&lt;p&gt;This tiny CSS snippet is super clever. I did discuss my initial problem, can't close popovers with click over iframe, with ChatGPT. There were some long and complex suggestions where and how to attach event listeners, or add manual overlays, checking the internet, and answering patiently my requests for a simple solution. CSS and HTML become really clever, but we are not used to it. I keep the one above as my little spell, which shall guide me to think straight about a modern HTML and CSS solutation first, and plan the JS fallback really just if I must.&lt;/p&gt;




&lt;h3&gt;
  
  
  Common gotchas
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Popovers ignore parent positioning (top layer)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;margin: auto&lt;/code&gt; centers them by default&lt;/li&gt;
&lt;li&gt;Clicks inside iframes don’t close them&lt;/li&gt;
&lt;li&gt;Anchor positioning is not fully supported everywhere yet&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  Final thought
&lt;/h3&gt;

&lt;p&gt;The Popover API is not just a new feature — it’s a shift.&lt;/p&gt;

&lt;p&gt;It moves UI patterns from JavaScript into the browser itself.&lt;/p&gt;

&lt;p&gt;Fewer bugs. Less code. Better accessibility.&lt;/p&gt;




&lt;p&gt;If you want to explore more modern HTML, CSS, and SVG patterns,&lt;br&gt;&lt;br&gt;
you can find interactive examples on &lt;strong&gt;CSSEXY.com&lt;/strong&gt;.&lt;/p&gt;

</description>
      <category>frontend</category>
      <category>html</category>
      <category>ui</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Your Cookie Consent Banner Is Probably Breaking Your Analytics</title>
      <dc:creator>Anja Beisel</dc:creator>
      <pubDate>Wed, 18 Mar 2026 18:16:34 +0000</pubDate>
      <link>https://dev.to/anjab/your-cookie-consent-banner-is-probably-breaking-your-analytics-5c4h</link>
      <guid>https://dev.to/anjab/your-cookie-consent-banner-is-probably-breaking-your-analytics-5c4h</guid>
      <description>&lt;h2&gt;
  
  
  Privacy-First Analytics for Modern Web Apps (Part 1)
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;How GDPR compliance causes data loss — and what modern web apps do instead&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Most cookie consent banners quietly destroy your analytics.&lt;/p&gt;

&lt;p&gt;If users decline cookies, tools like Google Analytics stop tracking completely.&lt;br&gt;
For many websites, that means losing 30–50% of user behaviour data.&lt;/p&gt;

&lt;p&gt;That creates a real problem for developers:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How do you respect GDPR and ePrivacy rules while still understanding how users use your product?&lt;/strong&gt;&lt;/p&gt;


&lt;h3&gt;
  
  
  The real problem
&lt;/h3&gt;

&lt;p&gt;Modern web applications depend on analytics to answer simple but critical questions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Which pages do users visit?&lt;/li&gt;
&lt;li&gt;Which features are actually used?&lt;/li&gt;
&lt;li&gt;Where do users drop off?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But in the UK and EU, analytics intersects with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;GDPR&lt;/li&gt;
&lt;li&gt;ePrivacy / PECR&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These regulations require user consent before setting non-essential cookies.&lt;/p&gt;

&lt;p&gt;And that’s where things break.&lt;/p&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fekaxcoly4r6uyguc69rc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fekaxcoly4r6uyguc69rc.png" alt="Basic vs Advanced vs Hybrid Analytics " width="800" height="506"&gt;&lt;/a&gt;&lt;/p&gt;


&lt;h3&gt;
  
  
  What actually happens with basic cookie consent
&lt;/h3&gt;

&lt;p&gt;The typical implementation looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User visits site
  ↓
Cookie banner appears
  ↓
Accept → Google Analytics loads
Decline → Google Analytics never loads
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In many React applications, the logic looks roughly like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userAcceptedAnalytics&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;ReactGA&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;initialize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;GA_ID&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the user declines, Google Analytics never runs.&lt;/p&gt;

&lt;p&gt;That means for a significant portion of your users:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;no pageviews are recorded&lt;/li&gt;
&lt;li&gt;no navigation behaviour is tracked&lt;/li&gt;
&lt;li&gt;no product usage data is captured&lt;/li&gt;
&lt;li&gt;From an analytics perspective, those users effectively do not exist.&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  Why this breaks your analytics
&lt;/h3&gt;

&lt;p&gt;Let’s say your site gets 1000 visitors:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1000 visitors
  ↓
40% decline cookies
  ↓
Google Analytics sees only 600 users
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;you miss real user journeys&lt;/li&gt;
&lt;li&gt;feature usage becomes unclear&lt;/li&gt;
&lt;li&gt;conversion funnels are incomplete&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Your data becomes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;❌ incomplete&lt;/li&gt;
&lt;li&gt;❌ biased&lt;/li&gt;
&lt;li&gt;❌ misleading&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is especially painful when building:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;SaaS products&lt;/li&gt;
&lt;li&gt;developer tools&lt;/li&gt;
&lt;li&gt;interactive apps&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  Why this is still the “safe” approach
&lt;/h3&gt;

&lt;p&gt;Despite the drawbacks, this model is widely used because it is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;✔ legally conservative&lt;/li&gt;
&lt;li&gt;✔ easy to implement&lt;/li&gt;
&lt;li&gt;✔ easy to explain&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;No cookies before consent → no legal ambiguity.&lt;/p&gt;




&lt;h3&gt;
  
  
  Important: this is a Google Analytics problem
&lt;/h3&gt;

&lt;p&gt;This issue exists because of how Google Analytics works:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;relies on cookies (_ga, _gid)&lt;/li&gt;
&lt;li&gt;uses persistent identifiers&lt;/li&gt;
&lt;li&gt;tracks users across sessions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That design conflicts directly with modern privacy requirements.&lt;/p&gt;




&lt;h3&gt;
  
  
  What modern apps do instead
&lt;/h3&gt;

&lt;p&gt;Instead of relying on a single tool, many teams now use a hybrid analytics architecture:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Visitor arrives
  ↓
Anonymous analytics runs for everyone
  ↓
Cookie banner appears

Accept → Google Analytics enabled
Decline → Google Analytics disabled
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This setup combines:&lt;/p&gt;

&lt;h4&gt;
  
  
  1. Privacy-first analytics (for all users)
&lt;/h4&gt;

&lt;p&gt;Tools like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Umami&lt;/li&gt;
&lt;li&gt;Plausible&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These provide:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;pageviews&lt;/li&gt;
&lt;li&gt;referrers&lt;/li&gt;
&lt;li&gt;device types&lt;/li&gt;
&lt;li&gt;product usage events&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Without:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;cookies&lt;/li&gt;
&lt;li&gt;persistent identifiers&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  2. Google Analytics (opt-in only)
&lt;/h4&gt;

&lt;p&gt;Only enabled after user consent.&lt;/p&gt;

&lt;p&gt;Provides:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;funnels&lt;/li&gt;
&lt;li&gt;campaign tracking&lt;/li&gt;
&lt;li&gt;deeper engagement insights&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  Why this approach works
&lt;/h3&gt;

&lt;p&gt;This architecture gives you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;✔ visibility into real product usage&lt;/li&gt;
&lt;li&gt;✔ compliance with privacy regulations&lt;/li&gt;
&lt;li&gt;✔ a cleaner mental model&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Instead of choosing between:&lt;/p&gt;

&lt;p&gt;privacy OR analytics&lt;/p&gt;

&lt;p&gt;you get:&lt;/p&gt;

&lt;p&gt;privacy AND useful analytics&lt;/p&gt;




&lt;h3&gt;
  
  
  This is becoming standard practice
&lt;/h3&gt;

&lt;p&gt;This hybrid approach is increasingly used in:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;SaaS products&lt;/li&gt;
&lt;li&gt;developer tools&lt;/li&gt;
&lt;li&gt;modern web platforms&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Where understanding product behaviour is essential. &lt;/p&gt;




&lt;h3&gt;
  
  
  Key takeaway
&lt;/h3&gt;

&lt;p&gt;If you rely only on Google Analytics with cookie consent:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;you are likely missing a large portion of user behaviour&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Modern web apps solve this by combining:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;privacy-first analytics for all users&lt;/li&gt;
&lt;li&gt;Google Analytics only after consent&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  What’s next
&lt;/h3&gt;

&lt;p&gt;In the next article, we’ll go deeper into:&lt;/p&gt;

&lt;p&gt;👉 Google Consent Mode&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;what it actually does&lt;/li&gt;
&lt;li&gt;Basic vs Advanced Consent Mode&lt;/li&gt;
&lt;li&gt;how to implement it in React + TypeScript&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  Final thought
&lt;/h3&gt;

&lt;p&gt;Analytics is no longer just a marketing tool.&lt;/p&gt;

&lt;p&gt;For modern web applications, it’s a &lt;strong&gt;product development tool&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Understanding how users move through your app helps you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;improve onboarding&lt;/li&gt;
&lt;li&gt;identify friction&lt;/li&gt;
&lt;li&gt;build better features&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The challenge is doing that &lt;strong&gt;without violating user trust&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;That’s exactly what this series is about.&lt;/p&gt;




&lt;h3&gt;
  
  
  Series: Privacy-First Analytics for Modern Web Apps
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Why Cookie Consent Breaks Your Analytics (this article)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/anjab/google-consent-mode-explained-react-typescript-2ld6"&gt;Google Consent Mode Explained (React + TypeScript)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/anjab/ads-tracking-and-the-legal-reality-in-the-eu-and-uk-lcn"&gt;Ads, Tracking, and the Legal Reality in the EU and UK&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/anjab/the-hybrid-analytics-architecture-4chc"&gt;The Hybrid Analytics Architecture&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;




&lt;h3&gt;
  
  
  About this series
&lt;/h3&gt;

&lt;p&gt;This series is based on real-world work building &lt;strong&gt;CSSEXY&lt;/strong&gt;, a visual UI platform where understanding user behaviour is essential for improving the product.&lt;/p&gt;

&lt;p&gt;All articles are also available on CSSEXY and there in the Gallery.&lt;/p&gt;

</description>
      <category>privacy</category>
      <category>react</category>
      <category>analytics</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Stop Rebuilding Modals: A Deep Dive into the &lt;dialog&gt; Element</title>
      <dc:creator>Anja Beisel</dc:creator>
      <pubDate>Sun, 22 Feb 2026 15:16:30 +0000</pubDate>
      <link>https://dev.to/anjab/stop-rebuilding-modals-a-deep-dive-into-the-element-gko</link>
      <guid>https://dev.to/anjab/stop-rebuilding-modals-a-deep-dive-into-the-element-gko</guid>
      <description>&lt;h3&gt;
  
  
  Introduction
&lt;/h3&gt;

&lt;p&gt;For more than a decade, building modals on the web meant re-implementing infrastructure:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;focus traps&lt;/li&gt;
&lt;li&gt;scroll locking&lt;/li&gt;
&lt;li&gt;ARIA wiring&lt;/li&gt;
&lt;li&gt;z-index battles&lt;/li&gt;
&lt;li&gt;accessibility edge cases&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Frameworks solved it with layers of abstraction, but the browser had no native solution.&lt;/p&gt;

&lt;p&gt;That changed.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;&amp;lt;dialog&amp;gt;&lt;/code&gt; element is no longer an experimental curiosity. Since 2022–2023, it has become a fully viable, cross-browser modal primitive — complete with focus management, top-layer rendering, backdrop handling, and built-in accessibility.&lt;/p&gt;

&lt;p&gt;This article explains what &lt;code&gt;&amp;lt;dialog&amp;gt;&lt;/code&gt; really does, how it differs from &lt;code&gt;show()&lt;/code&gt; and &lt;code&gt;showModal()&lt;/code&gt;, how it compares to &lt;code&gt;&amp;lt;details&amp;gt;&lt;/code&gt; and the &lt;code&gt;Popover API&lt;/code&gt;, and when you should (and shouldn’t) replace your custom modals with it.&lt;/p&gt;

&lt;h3&gt;
  
  
  How to implement
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;dialog&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"myDialog"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
   &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;Hello world&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
   &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;onclick=&lt;/span&gt;&lt;span class="s"&gt;"myDialog.close()"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Close&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/dialog&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;onclick=&lt;/span&gt;&lt;span class="s"&gt;"myDialog.showModal()"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Open&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
   JavaScript API
&lt;/h3&gt;

&lt;p&gt;The real power of &lt;code&gt;&amp;lt;dialog&amp;gt;&lt;/code&gt; is in its methods:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;.showModal()&lt;/code&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;sets &lt;code&gt;open&lt;/code&gt; attribute&lt;/li&gt;
&lt;li&gt;promotes dialog into “top layer”&lt;/li&gt;
&lt;li&gt;creates &lt;code&gt;::backdrop&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;applies &lt;code&gt;inert&lt;/code&gt; to rest of document&lt;/li&gt;
&lt;li&gt;exposes the dialog as &lt;code&gt;aria-modal="true"&lt;/code&gt; to assistive technology&lt;/li&gt;
&lt;li&gt;moves focus&lt;/li&gt;
&lt;li&gt;registers ESC listener&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The “top layer” is not just used by dialog, but also by Fullscreen API, Popovers, and Context menus. It sits above normal stacking context. That’s why z-index does not affect modal dialogs at all: they are rendered in a separate top layer above the normal stacking context.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;.show()&lt;/code&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;sets &lt;code&gt;open&lt;/code&gt; attribute&lt;/li&gt;
&lt;li&gt;keeps the dialog in normal layout and stacking context&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When you open the dialog with &lt;code&gt;show()&lt;/code&gt; there is no &lt;code&gt;inert&lt;/code&gt;, &lt;code&gt;backdrop&lt;/code&gt;, &lt;code&gt;focus trap&lt;/code&gt;, &lt;code&gt;aria-modal&lt;/code&gt;, just the visible dialog.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;.close()&lt;/code&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Closes dialog.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Backdrop Styling
&lt;/h3&gt;

&lt;p&gt;When opened with &lt;code&gt;.showModal()&lt;/code&gt;, the browser creates a backdrop as a special pseudo-element. You style it like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nt"&gt;dialog&lt;/span&gt;&lt;span class="nd"&gt;::backdrop&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;rgba&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Common Mistakes When Using &lt;code&gt;&amp;lt;dialog&amp;gt;&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Even though &lt;code&gt;&amp;lt;dialog&amp;gt;&lt;/code&gt; simplifies modal behavior, there are a few pitfalls that developers frequently run into.&lt;/p&gt;

&lt;h4&gt;
  
  
  Using &lt;code&gt;open&lt;/code&gt; Instead of &lt;code&gt;showModal()&lt;/code&gt;
&lt;/h4&gt;

&lt;p&gt;Setting &lt;code&gt;&amp;lt;dialog open&amp;gt;&lt;/code&gt; or &lt;code&gt;dialog.open = true&lt;/code&gt; does not make the dialog modal. It only makes it visible.&lt;/p&gt;

&lt;p&gt;There is no focus trap, no backdrop, no inert background, no top-layer behavior.&lt;/p&gt;

&lt;p&gt;If you need modal behavior, always use &lt;code&gt;dialog.showModal()&lt;/code&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  Forgetting to Label the Dialog
&lt;/h4&gt;

&lt;p&gt;A dialog without an accessible name is confusing for screen reader users. &lt;/p&gt;

&lt;p&gt;Always provide one of &lt;code&gt;&amp;lt;dialog aria-labelledby="titleId"&amp;gt;&lt;/code&gt; or &lt;code&gt;&amp;lt;dialog aria-label="Delete item"&amp;gt;&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;If the dialog has no heading and no label, it will simply be announced as “dialog”, which provides no context.&lt;/p&gt;

&lt;h4&gt;
  
  
  Animating the open Attribute Incorrectly
&lt;/h4&gt;

&lt;p&gt;A closed dialog is effectively &lt;code&gt;display: none&lt;/code&gt;. That means transitions often don’t run when the dialog first opens.&lt;/p&gt;

&lt;p&gt;To animate correctly, you either use &lt;code&gt;@starting-style&lt;/code&gt; (modern CSS), or apply a class in the next animation frame after calling &lt;code&gt;showModal()&lt;/code&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  Expecting &lt;code&gt;show()&lt;/code&gt; to behave like &lt;code&gt;showModal()&lt;/code&gt;
&lt;/h4&gt;

&lt;p&gt;&lt;code&gt;show()&lt;/code&gt; only makes the dialog visible. It does not move it to the top layer, trap focus, create a backdrop, make the background &lt;code&gt;inert&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;If you need blocking interaction, use &lt;code&gt;showModal()&lt;/code&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  Opening Multiple Modal Dialogs
&lt;/h4&gt;

&lt;p&gt;Only one modal dialog can be open at a time. Calling &lt;code&gt;showModal()&lt;/code&gt; on a second dialog while one is already open will throw an error. If you need stacked interactions, close the current dialog before opening another.&lt;/p&gt;

&lt;h4&gt;
  
  
  Re-Implementing What the Browser Already Does
&lt;/h4&gt;

&lt;p&gt;Some developers still add their own focus trap, add &lt;code&gt;role="dialog"&lt;/code&gt; manually, add &lt;code&gt;aria-modal="true"&lt;/code&gt; manually, lock body scroll manually.&lt;/p&gt;

&lt;p&gt;When using &lt;code&gt;showModal()&lt;/code&gt;, this is unnecessary and can even conflict with native behavior. Let the platform handle it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Accessibility Notes
&lt;/h3&gt;

&lt;p&gt;By default a &lt;code&gt;showModal()&lt;/code&gt; dialog&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;behaves like &lt;code&gt;role="dialog"&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;has &lt;code&gt;aria-modal="true"&lt;/code&gt; set&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You still need accessible labeling. Screen readers need to know what the dialog is about. So this is good&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;dialog&lt;/span&gt; &lt;span class="na"&gt;aria-labelledby=&lt;/span&gt;&lt;span class="s"&gt;"dialogTitle"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;h2&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"dialogTitle"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Delete item&lt;span class="nt"&gt;&amp;lt;/h2&amp;gt;&lt;/span&gt;
  ...
&lt;span class="nt"&gt;&amp;lt;/dialog&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;or&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;dialog&lt;/span&gt; &lt;span class="na"&gt;aria-labelledby=&lt;/span&gt;&lt;span class="s"&gt;"confirm-title"&lt;/span&gt; &lt;span class="na"&gt;aria-describedby=&lt;/span&gt;&lt;span class="s"&gt;"confirm-desc"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;h2&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"confirm-title"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Delete file?&lt;span class="nt"&gt;&amp;lt;/h2&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;p&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"confirm-desc"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;This cannot be undone.&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
  ...
&lt;span class="nt"&gt;&amp;lt;/dialog&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;or&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;dialog&lt;/span&gt; &lt;span class="na"&gt;aria-label=&lt;/span&gt;&lt;span class="s"&gt;"Delete item"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;: If your dialog contains no heading at all, you MUST provide a label via &lt;code&gt;aria-label&lt;/code&gt;. Because a dialog without a name is bad for screen readers.&lt;/p&gt;

&lt;h3&gt;
  
  
  What &lt;code&gt;inert&lt;/code&gt; actually does
&lt;/h3&gt;

&lt;p&gt;When you call &lt;code&gt;dialog.showModal()&lt;/code&gt; the browser internally makes everything outside the dialog &lt;code&gt;inert&lt;/code&gt;. &lt;code&gt;inert&lt;/code&gt; is an HTML attribute that means this subtree is not interactive and not focusable. It does three things&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;removes elements from the tab order&lt;/li&gt;
&lt;li&gt;blocks pointer events&lt;/li&gt;
&lt;li&gt;removes the subtree from the accessibility tree and prevents user interaction&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;What's the difference to &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;display: none&lt;/code&gt;: removes from layout and accessibility tree&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;visibility: hidden&lt;/code&gt;: hides visually but still affects layout&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;inert&lt;/code&gt;: keeps layout visible but disables interaction + accessibility&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That’s why background content is still visible but inaccessible.&lt;/p&gt;

&lt;h3&gt;
  
  
  How Focus Trapping Works Without JS
&lt;/h3&gt;

&lt;p&gt;With &lt;code&gt;&amp;lt;dialog&amp;gt;&lt;/code&gt; when opened then &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the browser moves focus inside the dialog&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Tab&lt;/code&gt; cycles only inside dialog&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Shift+Tab&lt;/code&gt; also cycles inside&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ESC&lt;/code&gt; triggers cancel&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;On &lt;code&gt;close&lt;/code&gt; the previous focus is restored. No JS required.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Tip&lt;/em&gt;: Initial focus&lt;/p&gt;

&lt;p&gt;Browser will focus on the first focusable element inside dialog. If none exist,  it may focus the dialog itself. Best practice is to set the focus on an element in the dialog, e.g. &lt;code&gt;&amp;lt;button autofocus&amp;gt;Cancel&amp;lt;/button&amp;gt;&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  All the work you do not need to do
&lt;/h3&gt;

&lt;p&gt;Before &lt;code&gt;&amp;lt;dialog&amp;gt;&lt;/code&gt;, modals required:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;focus trap logic&lt;/li&gt;
&lt;li&gt;scroll locking&lt;/li&gt;
&lt;li&gt;ARIA wiring&lt;/li&gt;
&lt;li&gt;z-index management&lt;/li&gt;
&lt;li&gt;ESC handling&lt;/li&gt;
&lt;li&gt;backdrop click logic&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now: &lt;code&gt;dialog.showModal()&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Should you replace all my modals with &lt;code&gt;&amp;lt;dialog&amp;gt;&lt;/code&gt;?
&lt;/h3&gt;

&lt;p&gt;Not automatically. But the new modals &lt;code&gt;&amp;lt;dialog&amp;gt;&lt;/code&gt; is often the cleanest option if it fits your UX.&lt;/p&gt;

&lt;h4&gt;
  
  
  How new really is the &lt;code&gt;&amp;lt;dialog&amp;gt;&lt;/code&gt; HTML element?
&lt;/h4&gt;

&lt;p&gt;Although  was introduced around 2012 and implemented in Chrome in 2014, it wasn’t fully viable cross-browser until 2022–2023 when Safari and Firefox shipped consistent modal behavior, &lt;code&gt;::backdrop&lt;/code&gt;, and &lt;code&gt;inert&lt;/code&gt;. That alignment marked its real turning point.&lt;/p&gt;

&lt;p&gt;But it still feels new now, because around 2022–2024 &lt;code&gt;Popover&lt;/code&gt; API shipped, &lt;code&gt;Inert&lt;/code&gt; shipped, Anchor positioning started appearing, Container queries became stable. So the platform shifted toward native UI primitives. &lt;code&gt;&amp;lt;dialog&amp;gt;&lt;/code&gt; suddenly made sense again.&lt;/p&gt;

&lt;p&gt;Before &lt;code&gt;&amp;lt;dialog&amp;gt;&lt;/code&gt;, modals were accessibility nightmares, focus-trap hacks, scroll-lock hacks, z-index wars, screen-reader inconsistencies. The spec authors wanted a built-in, accessibility-safe modal primitive. It just took years to reach consistent implementation.&lt;/p&gt;

&lt;h4&gt;
  
  
  So which of my custom modals should I replace straight away?
&lt;/h4&gt;

&lt;p&gt;When &lt;code&gt;&amp;lt;dialog&amp;gt;&lt;/code&gt; is a great replacement&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You want a standard centered modal&lt;/li&gt;
&lt;li&gt;You want correct focus trap + restore with minimal code&lt;/li&gt;
&lt;li&gt;You don’t need fancy portal stacking logic&lt;/li&gt;
&lt;li&gt;You’re happy with “platform behavior” (ESC, backdrop, top layer)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When you might not want &lt;code&gt;&amp;lt;dialog&amp;gt;&lt;/code&gt; everywhere&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Your modal is actually a drawer / side panel (sometimes better as a “region” or popover, depending)&lt;/li&gt;
&lt;li&gt;You rely on a UI library modal that already handles everything well (e.g. AntD Modal)&lt;/li&gt;
&lt;li&gt;You need very custom stacking / nested modals / toasts interacting with modals&lt;/li&gt;
&lt;li&gt;You need full control over close timing/animations (still possible, but a bit more work)&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  A pragmatic recommendation
&lt;/h4&gt;

&lt;p&gt;Migrate gradually. Replace simple confirm/alert/small-form modals first. Keep complex stacked/drawer-style modals where a framework abstraction makes more sense.&lt;/p&gt;

&lt;h3&gt;
  
  
  Last not least, what's the alternative? A comparison of &lt;code&gt;&amp;lt;dialog&amp;gt;&lt;/code&gt; vs &lt;code&gt;&amp;lt;details&amp;gt;&lt;/code&gt; vs &lt;code&gt;popover&lt;/code&gt;
&lt;/h3&gt;

&lt;h4&gt;
  
  
  &lt;code&gt;&amp;lt;details&amp;gt;&lt;/code&gt; (2010s)
&lt;/h4&gt;

&lt;p&gt;Purpose: simple disclosure / expand-collapse.&lt;/p&gt;

&lt;p&gt;Behavior: built-in toggle, &lt;code&gt;open&lt;/code&gt; attribute, keyboard support&lt;/p&gt;

&lt;p&gt;Adoption story: adoption was slow, but it was eventually widely accepted and is now commonly used&lt;/p&gt;

&lt;p&gt;Pattern: declarative + small built-in state machine.&lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;code&gt;&amp;lt;dialog&amp;gt;&lt;/code&gt; (viable 2022)
&lt;/h4&gt;

&lt;p&gt;Purpose: modal / interaction interruption&lt;/p&gt;

&lt;p&gt;Behavior: modal state machine, focus trap, inert background, backdrop, top layer&lt;/p&gt;

&lt;p&gt;Adoption story: long stagnation, suddenly viable once Safari + Firefox implemented fully, but still catching up in ecosystem mindset&lt;/p&gt;

&lt;p&gt;Pattern: semantic + heavy interaction behavior.&lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;code&gt;popover&lt;/code&gt; (2023+)
&lt;/h4&gt;

&lt;p&gt;Purpose: lightweight floating UI&lt;/p&gt;

&lt;p&gt;Behavior: top layer, light dismiss, ESC handling, no focus trap&lt;/p&gt;

&lt;p&gt;Adoption: very new, likely to replace many custom dropdown libraries&lt;/p&gt;

&lt;p&gt;Pattern: attribute-based primitive, not a new HTML element.&lt;/p&gt;

&lt;h4&gt;
  
  
  When should you use dialog and when is it a popup?
&lt;/h4&gt;

&lt;p&gt;Use a &lt;strong&gt;modal&lt;/strong&gt; &lt;code&gt;&amp;lt;dialog&amp;gt;&lt;/code&gt; (&lt;code&gt;showModal()&lt;/code&gt;) when you need to stop the user from continuing until they respond. Typical signals:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Decision required: confirm / cancel (“Delete snippet?”, “Discard changes?”)&lt;/li&gt;
&lt;li&gt;Blocking error: “Session expired — sign in again”&lt;/li&gt;
&lt;li&gt;Wizard / required form: must finish or explicitly dismiss&lt;/li&gt;
&lt;li&gt;Critical info where continuing without acknowledging is risky&lt;/li&gt;
&lt;li&gt;You need focus trap + inert background (keyboard can’t escape to the page)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Examples: confirm delete, unsaved changes prompt, payment / security step, required “choose one option to continue”.&lt;/p&gt;

&lt;p&gt;Use a &lt;strong&gt;non-modal&lt;/strong&gt; dialog / popup when it’s supplementary, doesn’t require a decision, and the user should be able to keep working. This includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Tooltips (“This field must be unique”)&lt;/li&gt;
&lt;li&gt;Info popovers (“What’s this?” help bubble)&lt;/li&gt;
&lt;li&gt;Context menus (right-click style menu)&lt;/li&gt;
&lt;li&gt;Inline pickers (color picker, date picker, emoji picker)&lt;/li&gt;
&lt;li&gt;Autocomplete dropdowns&lt;/li&gt;
&lt;li&gt;Toast notifications (not a dialog at all)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Conclusion
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;&amp;lt;dialog&amp;gt;&lt;/code&gt; is not just another HTML element. It represents a shift in the web platform: moving interaction infrastructure from JavaScript frameworks into the browser itself. &lt;/p&gt;

&lt;p&gt;For years, modal behavior required custom state machines. Today, a single call to &lt;code&gt;dialog.showModal()&lt;/code&gt; gives you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Focus trapping&lt;/li&gt;
&lt;li&gt;Background inerting&lt;/li&gt;
&lt;li&gt;Backdrop rendering&lt;/li&gt;
&lt;li&gt;Escape handling&lt;/li&gt;
&lt;li&gt;Top-layer stacking&lt;/li&gt;
&lt;li&gt;Accessible semantics&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That doesn’t mean you should rewrite every modal overnight. But it does mean that for simple, blocking interactions, the platform now provides a robust, accessible default.&lt;/p&gt;

&lt;p&gt;Just as &lt;code&gt;&amp;lt;details&amp;gt;&lt;/code&gt; simplified disclosure widgets, and &lt;code&gt;popover&lt;/code&gt; is simplifying floating UI, &lt;code&gt;&amp;lt;dialog&amp;gt;&lt;/code&gt; simplifies modal interaction.&lt;/p&gt;

&lt;p&gt;The web platform is catching up with the patterns we’ve been re-implementing for years.&lt;/p&gt;

&lt;p&gt;And this time, it’s doing it right.&lt;/p&gt;

&lt;h3&gt;
  
  
  Also available on CSSEXY
&lt;/h3&gt;

&lt;p&gt;This article is based on real-world work building &lt;strong&gt;CSSEXY&lt;/strong&gt;, a visual UI platform helping you using the most up to date HTML, CSS and SVG in the typical CSSEXY way, what is extreme and easy.&lt;/p&gt;

&lt;p&gt;The articles is also available in the CSSEXY Gallery with examples. From there you can download it and use it as template to build your own dialogs. &lt;/p&gt;

</description>
      <category>webdev</category>
      <category>frontend</category>
      <category>a11y</category>
      <category>html</category>
    </item>
  </channel>
</rss>
