<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>CSS-Tricks</title>
	<atom:link href="https://css-tricks.com/feed/" rel="self" type="application/rss+xml" />
	<link>https://css-tricks.com</link>
	<description>Tips, Tricks, and Techniques on using Cascading Style Sheets.</description>
	<lastBuildDate>Thu, 02 Apr 2026 21:14:28 +0000</lastBuildDate>
	<language>en-US</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	

<image>
	<url>https://i0.wp.com/css-tricks.com/wp-content/uploads/2021/07/star.png?fit=32%2C32&#038;ssl=1</url>
	<title>CSS-Tricks</title>
	<link>https://css-tricks.com</link>
	<width>32</width>
	<height>32</height>
</image> 
<site xmlns="com-wordpress:feed-additions:1">45537868</site>	<item>
		<title>Making Complex CSS Shapes Using shape()</title>
		<link>https://css-tricks.com/complex-css-shapes-with-shape-function/</link>
					<comments>https://css-tricks.com/complex-css-shapes-with-shape-function/#comments</comments>
		
		<dc:creator><![CDATA[Temani Afif]]></dc:creator>
		<pubDate>Thu, 02 Apr 2026 13:58:24 +0000</pubDate>
				<category><![CDATA[Articles]]></category>
		<category><![CDATA[clip-path]]></category>
		<category><![CDATA[CSS functions]]></category>
		<category><![CDATA[css shapes]]></category>
		<category><![CDATA[shapes]]></category>
		<guid isPermaLink="false">https://css-tricks.com/?p=392986</guid>

					<description><![CDATA[<p>Creating rectangles, circles, and rounded rectangles is the basic of CSS. Creating more complex CSS shapes such as triangles, hexagons, stars, hearts, etc. is more challenging but still a simple task if we rely on modern features.</p>
<hr />
<p><small><a rel="nofollow" href="https://css-tricks.com/complex-css-shapes-with-shape-function/">Making Complex CSS Shapes Using shape()</a> originally published on <a rel="nofollow" href="https://css-tricks.com">CSS-Tricks</a>, which is part of the <a href="https://try.digitalocean.com/css-tricks/?utm_medium=rss&amp;utm_source=css-tricks.com&amp;utm_campaign=family_&amp;utm_content=">DigitalOcean</a> family. You should <a href="https://css-tricks.com/newsletters/">get the newsletter</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p>Creating rectangles, circles, and rounded rectangles is the basic of CSS. Creating more complex <a href="https://css-shape.com/" rel="noopener">CSS shapes</a> such as triangles, hexagons, stars, hearts, etc. is more challenging but still a simple task if we rely on modern features.</p>



<p>But what about those shapes having a bit of randomness and many curves?</p>



<span id="more-392986"></span>



<figure class="wp-block-image size-full"><img data-recalc-dims="1" fetchpriority="high" decoding="async" width="1141" height="325" src="https://i0.wp.com/css-tricks.com/wp-content/uploads/2026/03/s_8249102E6BD5A7D94BC02F00D913B3B576539AF394F7402EF7DE7C968CBF8D3E_1773225348617_image.png?resize=1141%2C325" alt="Three rectangular shapes with jagged, non-creating edges. the first is blue, then orange, then green." class="wp-image-392987" srcset="https://i0.wp.com/css-tricks.com/wp-content/uploads/2026/03/s_8249102E6BD5A7D94BC02F00D913B3B576539AF394F7402EF7DE7C968CBF8D3E_1773225348617_image.png?w=1141&amp;ssl=1 1141w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2026/03/s_8249102E6BD5A7D94BC02F00D913B3B576539AF394F7402EF7DE7C968CBF8D3E_1773225348617_image.png?resize=300%2C85&amp;ssl=1 300w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2026/03/s_8249102E6BD5A7D94BC02F00D913B3B576539AF394F7402EF7DE7C968CBF8D3E_1773225348617_image.png?resize=1024%2C292&amp;ssl=1 1024w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2026/03/s_8249102E6BD5A7D94BC02F00D913B3B576539AF394F7402EF7DE7C968CBF8D3E_1773225348617_image.png?resize=768%2C219&amp;ssl=1 768w" sizes="(min-width: 735px) 864px, 96vw" /></figure>



<p>A lot of names may apply here: random wavy, wiggly, blob, squiggly, ragged, torn, etc. Whatever you call them, we all agree that they are not trivial to create, and they generally belong to the SVG world or are created with tools and used as images. Thanks to the new <a href="https://css-tricks.com/almanac/functions/s/shape/"><code>shape()</code></a> function, we can now build them using CSS.</p>



<p>I won’t tell you they are easy to create. They are indeed a bit tricky as they require a lot of math and calculation. For this reason, I built a few generators from which you can easily grab the code for the different shapes.</p>



<ul class="wp-block-list">
<li><a href="https://css-generators.com/wavy-divider/" rel="noopener">Wavy Divider generator</a></li>



<li><a href="https://css-generators.com/fancy-frame/" rel="noopener">Fancy Frame generator</a></li>



<li><a href="https://css-generators.com/blob/" rel="noopener">Blob generator</a></li>
</ul>



<p>All you have to do is adjust the settings and get the code in no time. As simple as that!</p>



<p>While most of you may be tempted to bookmark the CSS generators and leave this article, I advise you to continue reading. Having the generators is good, but understanding the logic behind them is even better. You may want to manually tweak the code to create more shape variations. We will also see a few interesting examples, so stay until the end!</p>



<p class="is-style-explanation"><strong>Notice:</strong> If you are new to <code>shape()</code>, I highly recommend reading <a href="https://css-tricks.com/better-css-shapes-using-shape-part-1-lines-and-arcs/">my</a> <a href="https://css-tricks.com/better-css-shapes-using-shape-part-1-lines-and-arcs/">four</a><a href="https://css-tricks.com/better-css-shapes-using-shape-part-1-lines-and-arcs/">-part series</a> where I explain the basics. It will help you better understand what we are doing here.</p>


<h3 class="wp-block-heading" id="how-does-it-work-">How does it work?</h3>


<p>While many of the shapes you can create with my generators look different, all of them rely on the same technique: a lot of <code>curve</code> commands. The main trick is to ensure two adjacent <code>curve</code> create a smooth curvature so that the full shape appears as one continuous curve.</p>



<p>Here is a figure of what one curve command can draw. I will be using only one control point:</p>



<figure class="wp-block-image size-full"><img data-recalc-dims="1" decoding="async" width="919" height="279" src="https://i0.wp.com/css-tricks.com/wp-content/uploads/2026/03/s_8249102E6BD5A7D94BC02F00D913B3B576539AF394F7402EF7DE7C968CBF8D3E_1773222807669_image.png?resize=919%2C279" alt="A normal curve with a control point in the very center. The second shows another curve with control point veering towards the left, contorting the curve." class="wp-image-392988" srcset="https://i0.wp.com/css-tricks.com/wp-content/uploads/2026/03/s_8249102E6BD5A7D94BC02F00D913B3B576539AF394F7402EF7DE7C968CBF8D3E_1773222807669_image.png?w=919&amp;ssl=1 919w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2026/03/s_8249102E6BD5A7D94BC02F00D913B3B576539AF394F7402EF7DE7C968CBF8D3E_1773222807669_image.png?resize=300%2C91&amp;ssl=1 300w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2026/03/s_8249102E6BD5A7D94BC02F00D913B3B576539AF394F7402EF7DE7C968CBF8D3E_1773222807669_image.png?resize=768%2C233&amp;ssl=1 768w" sizes="(min-width: 735px) 864px, 96vw" /></figure>



<p>Now, let’s put two curves next to each other:</p>



<figure class="wp-block-image size-full"><img data-recalc-dims="1" decoding="async" width="770" height="404" src="https://i0.wp.com/css-tricks.com/wp-content/uploads/2026/03/s_8249102E6BD5A7D94BC02F00D913B3B576539AF394F7402EF7DE7C968CBF8D3E_1773223335364_image.png?resize=770%2C404" alt="A wavy curve with two control points, one point up and the other down forming a wave along three points." class="wp-image-392989" srcset="https://i0.wp.com/css-tricks.com/wp-content/uploads/2026/03/s_8249102E6BD5A7D94BC02F00D913B3B576539AF394F7402EF7DE7C968CBF8D3E_1773223335364_image.png?w=770&amp;ssl=1 770w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2026/03/s_8249102E6BD5A7D94BC02F00D913B3B576539AF394F7402EF7DE7C968CBF8D3E_1773223335364_image.png?resize=300%2C157&amp;ssl=1 300w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2026/03/s_8249102E6BD5A7D94BC02F00D913B3B576539AF394F7402EF7DE7C968CBF8D3E_1773223335364_image.png?resize=768%2C403&amp;ssl=1 768w" sizes="(min-width: 735px) 864px, 96vw" /></figure>



<p>The ending point of the first curve, E1, is the starting point of the second curve, S2. That point is placed within the segment formed by both the control points C1 and C2. That’s the criterion for having an overall smooth curve. If we don’t have that, we get a discontinued “bad” curve.</p>



<figure class="wp-block-image size-full"><img data-recalc-dims="1" loading="lazy" decoding="async" width="733" height="398" src="https://i0.wp.com/css-tricks.com/wp-content/uploads/2026/03/s_8249102E6BD5A7D94BC02F00D913B3B576539AF394F7402EF7DE7C968CBF8D3E_1773223685809_image.png?resize=733%2C398" alt="A wavy curve with two control points. The second point is moved down and toward the right, bending the curves second wav in an undesired way." class="wp-image-392991" srcset="https://i0.wp.com/css-tricks.com/wp-content/uploads/2026/03/s_8249102E6BD5A7D94BC02F00D913B3B576539AF394F7402EF7DE7C968CBF8D3E_1773223685809_image.png?w=733&amp;ssl=1 733w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2026/03/s_8249102E6BD5A7D94BC02F00D913B3B576539AF394F7402EF7DE7C968CBF8D3E_1773223685809_image.png?resize=300%2C163&amp;ssl=1 300w" sizes="auto, (min-width: 735px) 864px, 96vw" /></figure>



<p>All we have to do is to randomly generate different curves while respecting the previous criterion between two consecutive curves. For the sake of simplicity, I will consider the common point between two curves to be the midpoint of the control points to have less randomness to deal with.</p>


<h3 class="wp-block-heading" id="creating-the-shapes">Creating the shapes</h3>


<p>Let’s start with the easiest shape, a random wavy divider. A random curve on one side.</p>



<figure class="wp-block-image size-full"><img data-recalc-dims="1" loading="lazy" decoding="async" width="1418" height="432" src="https://i0.wp.com/css-tricks.com/wp-content/uploads/2026/03/s_8249102E6BD5A7D94BC02F00D913B3B576539AF394F7402EF7DE7C968CBF8D3E_1773225933449_image.png?resize=1418%2C432&#038;ssl=1" alt="A long blue rectangle with a jagged bottom edge." class="wp-image-392992" srcset="https://i0.wp.com/css-tricks.com/wp-content/uploads/2026/03/s_8249102E6BD5A7D94BC02F00D913B3B576539AF394F7402EF7DE7C968CBF8D3E_1773225933449_image.png?w=1418&amp;ssl=1 1418w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2026/03/s_8249102E6BD5A7D94BC02F00D913B3B576539AF394F7402EF7DE7C968CBF8D3E_1773225933449_image.png?resize=300%2C91&amp;ssl=1 300w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2026/03/s_8249102E6BD5A7D94BC02F00D913B3B576539AF394F7402EF7DE7C968CBF8D3E_1773225933449_image.png?resize=1024%2C312&amp;ssl=1 1024w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2026/03/s_8249102E6BD5A7D94BC02F00D913B3B576539AF394F7402EF7DE7C968CBF8D3E_1773225933449_image.png?resize=768%2C234&amp;ssl=1 768w" sizes="auto, (min-width: 735px) 864px, 96vw" /></figure>



<p>Two variables will control the shape: the granularity and the size. The granularity defines how many curves we will have (it will be an integer). The size defines the space where the curves will be drawn.</p>



<figure class="wp-block-image size-full"><img data-recalc-dims="1" loading="lazy" decoding="async" width="1129" height="342" src="https://i0.wp.com/css-tricks.com/wp-content/uploads/2026/03/s_8249102E6BD5A7D94BC02F00D913B3B576539AF394F7402EF7DE7C968CBF8D3E_1773226919188_image.png?resize=1129%2C342" alt="The same blue renctangle in two versions with two different jagged bottom edges, marked in red to show the shape. The first is labeled Granularity 8 and the second, with more and deeper jags, is labeled Granularity 18." class="wp-image-392994" srcset="https://i0.wp.com/css-tricks.com/wp-content/uploads/2026/03/s_8249102E6BD5A7D94BC02F00D913B3B576539AF394F7402EF7DE7C968CBF8D3E_1773226919188_image.png?w=1129&amp;ssl=1 1129w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2026/03/s_8249102E6BD5A7D94BC02F00D913B3B576539AF394F7402EF7DE7C968CBF8D3E_1773226919188_image.png?resize=300%2C91&amp;ssl=1 300w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2026/03/s_8249102E6BD5A7D94BC02F00D913B3B576539AF394F7402EF7DE7C968CBF8D3E_1773226919188_image.png?resize=1024%2C310&amp;ssl=1 1024w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2026/03/s_8249102E6BD5A7D94BC02F00D913B3B576539AF394F7402EF7DE7C968CBF8D3E_1773226919188_image.png?resize=768%2C233&amp;ssl=1 768w" sizes="auto, (min-width: 735px) 864px, 96vw" /></figure>



<p>The first step is to create N points and evenly place them at the bottom of the element (N is the granularity).</p>



<figure class="wp-block-image size-full"><img data-recalc-dims="1" loading="lazy" decoding="async" width="665" height="244" src="https://i0.wp.com/css-tricks.com/wp-content/uploads/2026/03/s_8249102E6BD5A7D94BC02F00D913B3B576539AF394F7402EF7DE7C968CBF8D3E_1773227352470_image.png?resize=665%2C244" alt="A white rectangle with a black border and seven control points evenly spaced along the bottom edge." class="wp-image-392996" srcset="https://i0.wp.com/css-tricks.com/wp-content/uploads/2026/03/s_8249102E6BD5A7D94BC02F00D913B3B576539AF394F7402EF7DE7C968CBF8D3E_1773227352470_image.png?w=665&amp;ssl=1 665w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2026/03/s_8249102E6BD5A7D94BC02F00D913B3B576539AF394F7402EF7DE7C968CBF8D3E_1773227352470_image.png?resize=300%2C110&amp;ssl=1 300w" sizes="auto, (min-width: 735px) 864px, 96vw" /></figure>



<p>Then, we randomly offset the vertical position of the points using the size variable. Each point will have an offset equal to a random value within the range <code>[0 size]</code>.</p>



<figure class="wp-block-image size-full"><img data-recalc-dims="1" loading="lazy" decoding="async" width="713" height="226" src="https://i0.wp.com/css-tricks.com/wp-content/uploads/2026/03/s_8249102E6BD5A7D94BC02F00D913B3B576539AF394F7402EF7DE7C968CBF8D3E_1773228225993_image.png?resize=713%2C226" alt="A white rectangle with a black border and seven control points evenly spaced in a wavy formation along the bottom edge. A red label saying Size indicates the vertical height between the highest point and lowest point." class="wp-image-392997" srcset="https://i0.wp.com/css-tricks.com/wp-content/uploads/2026/03/s_8249102E6BD5A7D94BC02F00D913B3B576539AF394F7402EF7DE7C968CBF8D3E_1773228225993_image.png?w=713&amp;ssl=1 713w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2026/03/s_8249102E6BD5A7D94BC02F00D913B3B576539AF394F7402EF7DE7C968CBF8D3E_1773228225993_image.png?resize=300%2C95&amp;ssl=1 300w" sizes="auto, (min-width: 735px) 864px, 96vw" /></figure>



<p>From there, we take two adjacent points and define their midpoint. We get more points.</p>



<figure class="wp-block-image size-full"><img data-recalc-dims="1" loading="lazy" decoding="async" width="715" height="226" src="https://i0.wp.com/css-tricks.com/wp-content/uploads/2026/03/s_8249102E6BD5A7D94BC02F00D913B3B576539AF394F7402EF7DE7C968CBF8D3E_1773228252968_image.png?resize=715%2C226&#038;ssl=1" alt="A white rectangle with a black border and thirteen control points evenly spaced in a wavy formation along the bottom edge. A red label saying Size indicates the vertical height between the highest point and lowest point. Every even point is marked in blue." class="wp-image-392998" srcset="https://i0.wp.com/css-tricks.com/wp-content/uploads/2026/03/s_8249102E6BD5A7D94BC02F00D913B3B576539AF394F7402EF7DE7C968CBF8D3E_1773228252968_image.png?w=715&amp;ssl=1 715w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2026/03/s_8249102E6BD5A7D94BC02F00D913B3B576539AF394F7402EF7DE7C968CBF8D3E_1773228252968_image.png?resize=300%2C95&amp;ssl=1 300w" sizes="auto, (min-width: 735px) 864px, 96vw" /></figure>



<p>Do you start to see the idea? A first set of points is randomly placed while a second set is placed in a way that meets the criterion we defined previously. From there, we draw all the curves, and we get our shape.</p>



<p>The CSS code will look like this:</p>



<pre rel="CSS" class="wp-block-csstricks-code-block language-css" data-line=""><code markup="tt">.shape {
  clip-path: shape(from Px1 Py1,
    curve to Px2 Py2 with Cx1 Cy1,
    curve to Px3 Py3 with Cx2 Cy2,
    /* ... */
    curve to Pxi Pyi with Cx(i-1) Cy(i-1)
    /* ... */
  )
}</code></pre>



<p>The <code>Ci</code> are the points we randomly place (the control points) and <code>Pi</code> are the midpoints.</p>



<p>From there, we apply the same logic to the different sides to get different variation (bottom, top, bottom-top, all sides, etc.).</p>



<figure class="wp-block-image size-full"><img data-recalc-dims="1" loading="lazy" decoding="async" width="726" height="525" src="https://i0.wp.com/css-tricks.com/wp-content/uploads/2026/03/s_8249102E6BD5A7D94BC02F00D913B3B576539AF394F7402EF7DE7C968CBF8D3E_1773229121842_image.png?resize=726%2C525" alt="A two-by-two grid of the same blue rectangle with different configurations of wavy edges. The first on the bottom, the second on the top, the third on the top and bottom, and the fourth all along the shape." class="wp-image-393000" srcset="https://i0.wp.com/css-tricks.com/wp-content/uploads/2026/03/s_8249102E6BD5A7D94BC02F00D913B3B576539AF394F7402EF7DE7C968CBF8D3E_1773229121842_image.png?w=726&amp;ssl=1 726w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2026/03/s_8249102E6BD5A7D94BC02F00D913B3B576539AF394F7402EF7DE7C968CBF8D3E_1773229121842_image.png?resize=300%2C217&amp;ssl=1 300w" sizes="auto, (min-width: 735px) 864px, 96vw" /></figure>



<p>As for <a href="https://css-generators.com/blob/" rel="noopener">the blob</a>, the logic is slightly different. Instead of considering a rectangular shape and straight lines, we use a circle.</p>



<figure class="wp-block-image size-full"><img data-recalc-dims="1" loading="lazy" decoding="async" width="1138" height="463" src="https://i0.wp.com/css-tricks.com/wp-content/uploads/2026/03/s_8249102E6BD5A7D94BC02F00D913B3B576539AF394F7402EF7DE7C968CBF8D3E_1773229324543_image.png?resize=1138%2C463&#038;ssl=1" alt="Two white circles with black borders that contain a smaller circle with a dashed border. The first circle has eight black control points around the outer circle evenly spaced. The second has 15 control points around it, even other one in blue and positioned between the outer and inner circles to form a wavy shape." class="wp-image-393002" srcset="https://i0.wp.com/css-tricks.com/wp-content/uploads/2026/03/s_8249102E6BD5A7D94BC02F00D913B3B576539AF394F7402EF7DE7C968CBF8D3E_1773229324543_image.png?w=1138&amp;ssl=1 1138w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2026/03/s_8249102E6BD5A7D94BC02F00D913B3B576539AF394F7402EF7DE7C968CBF8D3E_1773229324543_image.png?resize=300%2C122&amp;ssl=1 300w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2026/03/s_8249102E6BD5A7D94BC02F00D913B3B576539AF394F7402EF7DE7C968CBF8D3E_1773229324543_image.png?resize=1024%2C417&amp;ssl=1 1024w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2026/03/s_8249102E6BD5A7D94BC02F00D913B3B576539AF394F7402EF7DE7C968CBF8D3E_1773229324543_image.png?resize=768%2C312&amp;ssl=1 768w" sizes="auto, (min-width: 735px) 864px, 96vw" /></figure>



<p>We evenly place the points around the circle (the one formed by the element if it has <code>border-radius: 50%</code>). Then, we randomly offset them closer to the center. Finally, we add the midpoints and draw the shape.</p>



<figure class="wp-block-image size-full"><img data-recalc-dims="1" loading="lazy" decoding="async" width="549" height="341" src="https://i0.wp.com/css-tricks.com/wp-content/uploads/2026/03/s_8249102E6BD5A7D94BC02F00D913B3B576539AF394F7402EF7DE7C968CBF8D3E_1773229605074_image.png?resize=549%2C341" alt="A large green blob shape." class="wp-image-393003" srcset="https://i0.wp.com/css-tricks.com/wp-content/uploads/2026/03/s_8249102E6BD5A7D94BC02F00D913B3B576539AF394F7402EF7DE7C968CBF8D3E_1773229605074_image.png?w=549&amp;ssl=1 549w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2026/03/s_8249102E6BD5A7D94BC02F00D913B3B576539AF394F7402EF7DE7C968CBF8D3E_1773229605074_image.png?resize=300%2C186&amp;ssl=1 300w" sizes="auto, (min-width: 735px) 864px, 96vw" /></figure>



<p>We can still go fancier and combine the first technique with the circular one to consider a rectangle with rounded corners.</p>



<figure class="wp-block-image size-full"><img data-recalc-dims="1" loading="lazy" decoding="async" width="1114" height="348" src="https://i0.wp.com/css-tricks.com/wp-content/uploads/2026/03/s_8249102E6BD5A7D94BC02F00D913B3B576539AF394F7402EF7DE7C968CBF8D3E_1773230703420_image.png?resize=1114%2C348" alt="A blue rounded rectangle next to another version of itself with a large number of jagged edges all around it." class="wp-image-393005" srcset="https://i0.wp.com/css-tricks.com/wp-content/uploads/2026/03/s_8249102E6BD5A7D94BC02F00D913B3B576539AF394F7402EF7DE7C968CBF8D3E_1773230703420_image.png?w=1114&amp;ssl=1 1114w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2026/03/s_8249102E6BD5A7D94BC02F00D913B3B576539AF394F7402EF7DE7C968CBF8D3E_1773230703420_image.png?resize=300%2C94&amp;ssl=1 300w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2026/03/s_8249102E6BD5A7D94BC02F00D913B3B576539AF394F7402EF7DE7C968CBF8D3E_1773230703420_image.png?resize=1024%2C320&amp;ssl=1 1024w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2026/03/s_8249102E6BD5A7D94BC02F00D913B3B576539AF394F7402EF7DE7C968CBF8D3E_1773230703420_image.png?resize=768%2C240&amp;ssl=1 768w" sizes="auto, (min-width: 735px) 864px, 96vw" /></figure>



<p>This was the trickiest one to implement as I had to deal with each corner, each side, and work with different granularities. However, the result was quite satisfying as it allows us to create a lot <a href="https://css-generators.com/fancy-frame/" rel="noopener">of fancy frames</a>!</p>


<h3 class="wp-block-heading" id="show-me-the-cool-demos-">Show me the cool demos!</h3>


<p>Enough theory, let’s see some cool examples and how to simply use the generators to create complex-looking shapes and animations.</p>



<p>We start with a classic layout featuring numerous wavy dividers!</p>



<div class="wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper"><iframe id="cp_embed_EayNMgo" src="//codepen.io/anon/embed/EayNMgo?height=450&amp;theme-id=1&amp;slug-hash=EayNMgo&amp;default-tab=result" height="450" scrolling="no" frameborder="0" allowfullscreen allowpaymentrequest name="CodePen Embed EayNMgo" title="CodePen Embed EayNMgo" class="cp_embed_iframe" style="width:100%;overflow:hidden">CodePen Embed Fallback</iframe></div>



<p>We have four shapes in that demo, and all of them are a simple copy/paste from <a href="https://css-generators.com/wavy-divider/" rel="noopener">the wavy divider generator</a>. The header uses the bottom configuration, the footer uses the top configuration and the other elements use the top + bottom configuration.</p>



<p>Let’s get fancy and add some animation.</p>



<div class="wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper"><iframe id="cp_embed_yyaVpMy/180506c933b34c7113fc26650622b300" src="//codepen.io/anon/embed/yyaVpMy/180506c933b34c7113fc26650622b300?height=450&amp;theme-id=1&amp;slug-hash=yyaVpMy/180506c933b34c7113fc26650622b300&amp;default-tab=result" height="450" scrolling="no" frameborder="0" allowfullscreen allowpaymentrequest name="CodePen Embed yyaVpMy/180506c933b34c7113fc26650622b300" title="CodePen Embed yyaVpMy/180506c933b34c7113fc26650622b300" class="cp_embed_iframe" style="width:100%;overflow:hidden">CodePen Embed Fallback</iframe></div>



<p>Each element will have the following code:</p>



<pre rel="CSS" class="wp-block-csstricks-code-block language-css" data-line=""><code markup="tt">@media screen and (prefers-reduced-motion: no-preference) {
  .element {
    --s1: shape( ... );
    --s2: shape( ... );
    animation: dance linear 1.6s infinite alternate;
  }

  @keyframes dance {
    0% {clip-path: var(--s1)}
    to {clip-path: var(--s2)}
  }
}</code></pre>



<p>From the generator, you fix the granularity and size, then you generate two different shapes for each one of the variables (<code>--s1</code> and <code>--s2</code>). The number of curves will be the same, which means the browser can have an interpolation between both shapes, hence we get a nice animation!</p>



<p>And what about introducing scroll-driven animation to have the animation based on the scroll? All you have to do is add <a href="https://css-tricks.com/almanac/functions/s/scroll/"><code>animation-timeline: scroll()</code></a> and it’s done.</p>



<div class="wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper"><iframe id="cp_embed_yyaVpxO/b326aeaf99c07d19f05bac209aa49d5a" src="//codepen.io/anon/embed/yyaVpxO/b326aeaf99c07d19f05bac209aa49d5a?height=450&amp;theme-id=1&amp;slug-hash=yyaVpxO/b326aeaf99c07d19f05bac209aa49d5a&amp;default-tab=result" height="450" scrolling="no" frameborder="0" allowfullscreen allowpaymentrequest name="CodePen Embed yyaVpxO/b326aeaf99c07d19f05bac209aa49d5a" title="CodePen Embed yyaVpxO/b326aeaf99c07d19f05bac209aa49d5a" class="cp_embed_iframe" style="width:100%;overflow:hidden">CodePen Embed Fallback</iframe></div>



<p>Here is the same effect with a sticky header.</p>



<div class="wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper"><iframe id="cp_embed_RNRKjBm/ebcd464305e40a043f4390705afe541d" src="//codepen.io/anon/embed/RNRKjBm/ebcd464305e40a043f4390705afe541d?height=450&amp;theme-id=1&amp;slug-hash=RNRKjBm/ebcd464305e40a043f4390705afe541d&amp;default-tab=result" height="450" scrolling="no" frameborder="0" allowfullscreen allowpaymentrequest name="CodePen Embed RNRKjBm/ebcd464305e40a043f4390705afe541d" title="CodePen Embed RNRKjBm/ebcd464305e40a043f4390705afe541d" class="cp_embed_iframe" style="width:100%;overflow:hidden">CodePen Embed Fallback</iframe></div>



<p>For this one, you play with the size. You fix the granularity and the shape ID then you consider a size equal to <code>0</code> for the initial shape (a rectangle) and a size different from <code>0</code> for the wavy one. Then you let the browser animate between both.</p>



<p>Do you see all the possibilities we have? You can either use the shapes as static decorations or create fancy animations between two (or more) by using the same granularity and adjusting the other settings (size and shape ID).</p>



<p>What cool demo can you create using those tricks? Share it in the comment section.</p>



<p>I will leave you with more examples you can use as inspiration.</p>



<p>A bouncing hover effect with blob shapes:</p>



<div class="wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper"><iframe id="cp_embed_PwwJgyr" src="//codepen.io/anon/embed/PwwJgyr?height=450&amp;theme-id=1&amp;slug-hash=PwwJgyr&amp;default-tab=result" height="450" scrolling="no" frameborder="0" allowfullscreen allowpaymentrequest name="CodePen Embed PwwJgyr" title="CodePen Embed PwwJgyr" class="cp_embed_iframe" style="width:100%;overflow:hidden">CodePen Embed Fallback</iframe></div>



<div class="wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper"><iframe id="cp_embed_yyyPONb" src="//codepen.io/anon/embed/yyyPONb?height=450&amp;theme-id=1&amp;slug-hash=yyyPONb&amp;default-tab=result" height="450" scrolling="no" frameborder="0" allowfullscreen allowpaymentrequest name="CodePen Embed yyyPONb" title="CodePen Embed yyyPONb" class="cp_embed_iframe" style="width:100%;overflow:hidden">CodePen Embed Fallback</iframe></div>



<p>A <a href="https://css-tip.com/squishy-button/" rel="noopener">squishy button</a> with a hover and click effect:</p>



<div class="wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper"><iframe id="cp_embed_ZYpLGvX" src="//codepen.io/anon/embed/ZYpLGvX?height=450&amp;theme-id=1&amp;slug-hash=ZYpLGvX&amp;default-tab=result" height="450" scrolling="no" frameborder="0" allowfullscreen allowpaymentrequest name="CodePen Embed ZYpLGvX" title="CodePen Embed ZYpLGvX" class="cp_embed_iframe" style="width:100%;overflow:hidden">CodePen Embed Fallback</iframe></div>



<p>A<a href="https://css-tip.com/wobbling-animation/" target="_blank" rel="noreferrer noopener">&nbsp;wobbling frame animation</a>:</p>



<div class="wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper"><iframe id="cp_embed_zxKzrKe" src="//codepen.io/anon/embed/zxKzrKe?height=450&amp;theme-id=1&amp;slug-hash=zxKzrKe&amp;default-tab=result" height="450" scrolling="no" frameborder="0" allowfullscreen allowpaymentrequest name="CodePen Embed zxKzrKe" title="CodePen Embed zxKzrKe" class="cp_embed_iframe" style="width:100%;overflow:hidden">CodePen Embed Fallback</iframe></div>



<p>A&nbsp;<a href="https://css-tip.com/sliding-liquid/" target="_blank" rel="noreferrer noopener">liquid reveal effect</a>:</p>



<div class="wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper"><iframe id="cp_embed_OPRZBxY" src="//codepen.io/anon/embed/OPRZBxY?height=450&amp;theme-id=1&amp;slug-hash=OPRZBxY&amp;default-tab=result" height="450" scrolling="no" frameborder="0" allowfullscreen allowpaymentrequest name="CodePen Embed OPRZBxY" title="CodePen Embed OPRZBxY" class="cp_embed_iframe" style="width:100%;overflow:hidden">CodePen Embed Fallback</iframe></div>



<p>And a set of <a href="https://css-loaders.com/squishy/" rel="noopener">fancy CSS loaders</a> you can find at my site.</p>


<h3 class="wp-block-heading" id="conclusion">Conclusion</h3>


<p>Do you see all the potential of the new <code>shape()</code> function? We now have the opportunity to create complex-looking shapes without resorting to SVG or images. In addition to that, we can easily have nice transition/animation.</p>



<p>Don’t forget to bookmark my <a href="https://css-generators.com/" rel="noopener">CSS Generators</a> website, from where you can get the code of the shapes we studied and more. I also have the <a href="https://css-shape.com/" rel="noopener">CSS Shape</a> website which I will soon update to utilize the new <code>shape()</code> for most of the shapes and optimize a lot of old code!</p>



<p>What about you? Can you think about a complex shape we can create using <code>shape()</code>? Perhaps you can give me the idea for my next generator!</p>
<hr />
<p><small><a rel="nofollow" href="https://css-tricks.com/complex-css-shapes-with-shape-function/">Making Complex CSS Shapes Using shape()</a> originally published on <a rel="nofollow" href="https://css-tricks.com">CSS-Tricks</a>, which is part of the <a href="https://try.digitalocean.com/css-tricks/?utm_medium=rss&amp;utm_source=css-tricks.com&amp;utm_campaign=family_&amp;utm_content=">DigitalOcean</a> family. You should <a href="https://css-tricks.com/newsletters/">get the newsletter</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://css-tricks.com/complex-css-shapes-with-shape-function/feed/</wfw:commentRss>
			<slash:comments>3</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">392986</post-id>	</item>
		<item>
		<title>Front-End Fools: Top 10 April Fools’ UI Pranks of All Time</title>
		<link>https://css-tricks.com/front-end-april-fools-top-10/</link>
					<comments>https://css-tricks.com/front-end-april-fools-top-10/#comments</comments>
		
		<dc:creator><![CDATA[Lee Meyer]]></dc:creator>
		<pubDate>Wed, 01 Apr 2026 14:00:26 +0000</pubDate>
				<category><![CDATA[Articles]]></category>
		<category><![CDATA[news]]></category>
		<guid isPermaLink="false">https://css-tricks.com/?p=392794</guid>

					<description><![CDATA[<p>These are the historical pranks I consider the top 10 most noteworthy, rather than the “best.” You’ll see that some of them crossed the line and/or backfired.</p>
<hr />
<p><small><a rel="nofollow" href="https://css-tricks.com/front-end-april-fools-top-10/">Front-End Fools: Top 10 April Fools’ UI Pranks of All Time</a> originally published on <a rel="nofollow" href="https://css-tricks.com">CSS-Tricks</a>, which is part of the <a href="https://try.digitalocean.com/css-tricks/?utm_medium=rss&amp;utm_source=css-tricks.com&amp;utm_campaign=family_&amp;utm_content=">DigitalOcean</a> family. You should <a href="https://css-tricks.com/newsletters/">get the newsletter</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p>April Fools’ Day pranks on the web imply that we’re not trying to fool each other every day in web design anyway. Indeed, one of my <a href="https://css-tricks.com/scrollytelling-on-steroids-with-scroll-state-queries/#comment-1883701">favorite comments</a> I received on an article was, “I can’t believe my eyes!” You shouldn’t, since <a href="https://www.interaction-design.org/literature/topics/illusions-in-design?srsltid=AfmBOorUffn89UTjtk5jgvNVMfKFhVTuSlTciyAOjyGon8dDF0rbgUU_#1._understand_the_principles_of_perception-11" rel="noopener">web design relies on fooling the user’s brain</a> by manipulating the way we process visual information via <a href="https://www.interaction-design.org/literature/topics/gestalt-principles" rel="noopener">Gestalt laws</a>, which make a website feel real.</p>



<p>April Fools’ Day on the web exemplifies what philosopher <a href="https://en.wikipedia.org/wiki/Jean_Baudrillard" rel="noopener">Jean Baudrillard</a> called a <a href="https://www.ipl.org/essay/Jean-Boaudillards-Theory-Of-Jean-Baaudrillard-Theory-FKL6SC5K6CED6" rel="noopener">deterrence machine</a> — a single day on the calendar to celebrate funny fake news is like a theme park designed to make the fake constructs beyond its gates seem real by comparison. And oftentimes, the online pranks on April 1st are indistinguishable from the <a href="https://www.bbc.com/news/uk-56597184" rel="noopener">bizarreness</a> that ensues all year round in the “real” virtual world.</p>



<span id="more-392794"></span>


<h3 class="wp-block-heading" id="real-things-that-looked-like-april-fools-pranks">Real things that looked like April Fools’ pranks</h3>


<p>Tech has a history of April Fools’ Day announcements that remind me of what Philip K. Dick called “fake fakes,” emerging every year like <a href="https://philipdick.com/mirror/essays/How_to_Build_a_Universe.pdf" rel="noopener">real animals surreptitiously replacing the fake ones at Disneyland</a>.</p>



<p>For instance, in 2004, people famously thought Gmail <a href="https://en.wikipedia.org/wiki/List_of_Google_April_Fools%27_Day_jokes#2004" rel="noopener">was an April Fools’ joke</a> since it was announced on April 1st.</p>



<p>And on April Fools’ Day in 2013, long before the current generation of AI, <a href="https://tom7.org/" rel="noopener">Tom Murphy</a> announced <a href="https://tom7.org/mario/" rel="noopener">an AI that learns to play NES games</a>. It was the real deal, even though he published the research paper and source code on “<a href="http://sigbovik.org/2013" rel="noopener">SIGBOVIK 2013</a>, an April 1st conference that usually publishes fake research. Mine is real!” In Tom’s <a href="https://www.youtube.com/watch?v=xOCurBYI_gY" rel="noopener">demo</a>, the AI even devised the strategy of indefinitely pausing Tetris, because in that game on NES, “The only way to win is <a href="https://en.wikipedia.org/wiki/Tetris_(NES_video_game)#Gameplay" rel="noopener">not to play</a>.”</p>



<p>To give a more personal example of real tech that could be mistaken for an April Fools’ joke, my article on <a href="https://css-tricks.com/worlds-collide-keyframe-collision-detection-using-style-queries/">pure CSS collision detection</a> was published on April 1st, 2025, <a href="https://www.google.com/search?q=march+31st+US+time+in+AEST&amp;newwindow=1&amp;sca_esv=3d646b66db581d96&amp;biw=1163&amp;bih=605&amp;sxsrf=ANbL-n5k_h4tZKn5GJXplIYnR5lxWqmjKg%3A1772227081965&amp;ei=CQqiabnZOraTseMP86WBwAk&amp;ved=0ahUKEwj5gbqtzPqSAxW2SWwGHfNSAJgQ4dUDCBE&amp;uact=5&amp;oq=march+31st+US+time+in+AEST&amp;gs_lp=Egxnd3Mtd2l6LXNlcnAiGm1hcmNoIDMxc3QgVVMgdGltZSBpbiBBRVNUMgUQIRigATIFECEYoAEyBRAhGKABMgQQIRgVSMtdUJEZWO9ZcAl4AZABAJgB-wGgAe4pqgEGMC4yNi41uAEDyAEA-AEBmAIkoAL2KsICCxAuGIAEGJECGIoFwgILEAAYgAQYkQIYigXCAgsQABiABBixAxiDAcICCBAAGIAEGLEDwgIOEC4YgAQYsQMYgwEYigXCAg4QLhiABBixAxjRAxjHAcICDhAAGIAEGLEDGIMBGIoFwgIKEC4YgAQYQxiKBcICChAAGIAEGEMYigXCAhcQLhiABBixAxiDARjHARiKBRiOBRivAcICFBAuGIAEGLEDGIMBGMcBGI4FGK8BwgIFEC4YgATCAggQLhiABBixA8ICDRAuGIAEGLEDGEMYigXCAhMQLhiABBixAxjRAxhDGMcBGIoFwgILEC4YgAQYsQMYgwHCAhEQABiABBiRAhixAxiDARiKBcICEBAAGIAEGLEDGEMYgwEYigXCAgQQABgDwgIFEAAYgATCAgYQABgWGB7CAgcQIRigARgKmAMAiAYBkgcGNS4yNS42oAfPjQKyBwYwLjI1Lja4B-AqwgcGMS4yNi45yAdXgAgA&amp;sclient=gws-wiz-serp" rel="noopener">my local time</a>. I was amused when someone <a href="https://css-tricks.com/worlds-collide-keyframe-collision-detection-using-style-queries/#comment-1882402">commented</a> that using <code>min</code> to detect if a paddle was in range of a ball seemed like a clever hack that &#8220;brings up the question: <em>Should game logic be done in CSS?”</em> Of course it shouldn’t! I wasn’t seriously proposing this as the future of web game development.</p>



<p>I replied that if the commenter can take the idea seriously for a minute, it’s a testament to how far CSS has come as a language. It seems even funnier in hindsight, now that <a href="https://css-tricks.com/the-range-syntax-has-come-to-container-style-queries-and-if/">the range syntax has come to style queries</a>, meaning we no longer need the <code>min</code> hack. So, maybe everyone should make games in CSS now, if the <code>min</code> hack was the only deal breaker (I kid because I love).</p>



<p><a href="https://codepen.io/leemeyer/pen/ZYEJQNO" rel="noopener">My CSS collision detection demo</a> had a resurgence in popularity recently, when Chris Coyier <a href="https://x.com/CodePen/status/2006806143213973723">chose it as a picked Pen</a>. And in that CodePen, a comment again made me laugh: “Can it be multiplayer/online?” Yet, once I stopped laughing, I found myself trying to get a multiplayer mode working. Whether I can or not, I guess the joke’s on me for taking CSS hacking too seriously.</p>



<p>The thing is, much of what <a href="https://rentahuman.ai/" rel="noopener">we have</a> on the web this year seemed unthinkable last year.</p>



<p>Even the story of the origin of April Fool’s Day sounds like a geeky April Fools’ joke — the leading theory is that the 15th-century equivalent of the <a href="https://en.wikipedia.org/wiki/Year_2000_problem" rel="noopener">Y2K bug</a> had some foolish people incorrectly celebrating the new year on April 1st when the Pope changed the calendars in France from the Julian Calendar to the Gregorian Calendar. And — April Fools’ again! — <a href="https://www.snopes.com/fact-check/april-fools39-day-origins/" rel="noopener">that’s a legend nobody has been able to prove happened</a>.</p>



<p>But whichever way you feel about the constant disruptions at the heart of the evolution of tech, the disruptions work like pranks by flipping common narratives on their heads in the same way April Fools&#8217; Day does. With that in mind, let’s go through history with an eye for exploring <a href="https://hcspire.com/2025/03/28/why-theres-a-little-truth-in-every-joke/" rel="noopener">the core of truth inside the jokes</a> of April Fools’ Days passed.</p>



<p class="is-style-explanation"><strong>Note:</strong> These are the historical pranks I consider the top 10 most noteworthy, rather than the “best.” You’ll see that some of them crossed the line and/or backfired.</p>


<h3 class="wp-block-heading" id="google-april-fools-games">Google April Fools’ games</h3>


<p>Google is <a href="https://en.wikipedia.org/wiki/List_of_Google_April_Fools%27_Day_jokes" rel="noopener">famous for its April Fools’ pranks</a>, but they’ve also historically blurred the line between pranks and features. For example, on April 1st 2019, Google introduced a <a href="https://au.lifehacker.com/news/79649/how-to-turn-google-calendar-into-space-invaders" rel="noopener">temporary easter egg</a> that transformed Google Calendar into a Space Invaders game. It was such a cool “joke” that nowadays, there’s a <a href="https://eieio.games/blog/breaktime/" rel="noopener">Chrome extension</a> that offers a similar experience, turning your Google Calendar into a Breakout game. This extension also offers the option to actually delete items that your ball hit from your calendar at the end of a game.</p>



<p>On April Fools&#8217; Day the same year as the original calendar game, Google also released a feature that allowed Google Maps users to <a href="https://youtu.be/LRfGo7LTjro?si=BXLRGG7pSuMAgP_4&amp;t=68" rel="noopener">play Snake on maps</a>.</p>



<p class="is-style-explanation"><strong>Personal Sidenote:</strong> The Google gag inspired an unreleased game I once made with an overworld that’s a gamified calendar, in which your character is trying to avoid an abusive partner by creating excuses not to be at home at the same time as their partner, but that’s a little dark for April Fools’.</p>


<h3 class="wp-block-heading" id="prank-npm-packages">Prank npm packages</h3>


<p>In March 2016, a legit — if arguably trivial — eleven-line package was deleted from the npm registry after its creator decided to boycott npm. Turns out that deletion <a href="https://en.wikipedia.org/wiki/Npm_left-pad_incident" rel="noopener">disrupted big companies whose code relied on the <code>left-pad</code> package</a> and this prompted npm to <a href="https://en.wikipedia.org/wiki/Npm_left-pad_incident#Reactions" rel="noopener">change its policies on which packages can be deleted</a>. I mention this because the humour of the npm packages released as jokes often revolves around poking fun at JavaScript developers’ overuse of dependencies that might not be needed.</p>



<p><a href="https://www.npmjs.com/package/vanilla-javascript" rel="noopener">Here is a 0kb npm package called <code>vanilla-javascript</code></a> and a <a href="http://vanilla-js.com/" rel="noopener">page for the Vanilla JS “framework”</a> that is always 0kb, no matter which features you add to the “bundle.&#8221; It lists all the JavaScript frameworks as “plugins.” Some of the dependent packages for <code>vanilla-javascript</code> are quite funny. I like <a href="https://www.npmjs.com/package/@falsejs/falsejs" rel="noopener">false-js</a>, which ensures <code>true</code> and <code>false</code> are defined properly. The library can be initialized with the settings <code>disableAprilFoolsSideEffects</code>, <code>definitelyDisableAprilFoolsSideEffects</code>, and <code>strictDisableAprilFoolsSideEffectsCheck</code>. If you read the source code, there is a <a href="https://github.com/10xly/FalseJS/blob/universe/aprilFoolsCalculateFalse.js" rel="noopener">comment</a> saying, “Haha, this code is obfuscated, you&#8217;ll never figure out what happens on April Fools.”</p>



<p>There is also this useless <a href="https://www.npmjs.com/package/get-current-day" rel="noopener">library</a> to get the current day. It seems plausible till you look carefully at the <a href="https://marmelab.com/get-current-day/" rel="noopener">website</a> and the description: “<em>This package is ephemeral for April Fools&#8217; Day and will be removed at some point.</em>“ The testimonials from fictional time-traveling characters are also a bit of a giveaway, and you have to love that he updated it every day for months, “because&#8230; why not? &#x1f937;&#x200d;&#x2642;&#xfe0f;”</p>



<p>More &#8220;terrible npm packages&#8221; for April Fools&#8217; are <a href="https://davidlozzi.com/2022/04/01/completely-terrible-npm-packages/" rel="noopener">here</a>.</p>


<h3 class="wp-block-heading" id="-aprilfools-css-https-gist-github-com-steveosoule-5295646-">aprilFools.css</h3>


<p>There’s another category of dependencies that are functional but used for <em>playing</em> April Fools pranks. For instance, <a href="https://gist.github.com/steveosoule/5295646" rel="noopener">aprilFools.css</a> by Wes Bos, which has a comment at the top saying:</p>



<pre rel="CSS" class="wp-block-csstricks-code-block language-css" data-line=""><code markup="tt">/*
  I assume no responsibility for angry co-workers or lost productivity
  Put these CSS definitons into your co-workers Custom.css file.
  They will be applied to every website they visit as well as their developer tools.
*/</code></pre>



<p>It does things like use <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Properties/transform" rel="noopener">CSS transforms</a> to turn the page upside down.</p>



<p>It strikes me that following the advice in the comments could be a slippery slope to a dark place of workplace bullying, if you were to try it on the wrong coworker, just because they left their computer unlocked. As Chris Coyier pointed out in <a href="https://css-tricks.com/practical-jokes-in-the-browser/">his post on practical jokes in the browser</a>:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p>&#8220;Fair warning on this stuff&#8230; you gotta be tasteful. Putting someone’s stapler in the jello is pretty hilarious unless it’s somehow a family heirloom, or it’s someone who’s been the target of a little too much office prankery to the point it isn’t funny anymore.&#8221;</p>
</blockquote>


<h3 class="wp-block-heading" id="april-fool-s-pranks-using-vs-code-extensions">April Fool’s pranks using VS Code Extensions</h3>


<p>While we’re on the topic of behavior that blurs the line between pranks and workplace bullying, let’s talk about <a href="https://justcodeit.medium.com/april-fools-pranks-with-vs-code-extensions-701a0d35647a" rel="noopener">this list of VS Code Extensions</a> that could be used to prank a coworker by causing their code editor UI to behave unexpectedly. Most of the examples sound funny and harmless, like having the IDE intermittently pop up “Dad Jokes” or make funny sounds when typing. Changing the code editor to resemble Slack using a theme is also funny.</p>



<p>Then there’s the last example that made me do a double-take: “Imagine hitting <kbd>CTRL</kbd> + <kbd>S</kbd> to save your work and then it gets erased!” Yeah, if I were interviewing someone and they mentioned they consider this a funny joke, I would end the interview there. And if anyone ever does this to me, I’m going to HR.</p>


<h3 class="wp-block-heading" id="pranks-by-the-w3c">Pranks by the W3C</h3>


<p>I don’t think of the W3C as having a sense of humor, although I guess getting me excited about <a href="https://www.w3.org/TR/html-imports/" rel="noopener">HTML imports</a> back in the day, only to discontinue them, was funny in hindsight, if you have a dark sense of humor. Nevertheless, they have posted pranks on their official website, such as restyling to make their <a href="https://www.youtube.com/watch?v=gbiv-b5uVso" rel="noopener">page look like a nineties GeoCities website in 2012</a>, or claiming they were <a href="https://www.w3.org/press-releases/2021/blink/" rel="noopener">reviving the <code>&lt;blink&gt;</code> tag in 2021</a>. There’s a theme of playing on the nostalgia of people my age who want these things to be real.</p>



<p class="is-style-explanation"><strong>Sidenote:</strong> If you want more Nineties internet experiences, the game <a href="https://www.youtube.com/watch?v=i5EWQSW4Nd8" rel="noopener">Hypnospace Outlaw</a>, set on a retro internet in an alternative 1999, might be up your alley.</p>



<p>Other sites over the years have played a similar joke, which can never fail to charm an old-timer like me who remembers using a web like this at the public library, back when the internet was too expensive for my family to afford at home.</p>


<h3 class="wp-block-heading" id="stackoverflow-retro-restyle">StackOverflow retro restyle</h3>


<p>I can’t get enough of these nostalgia trips, so here’s what StackOverflow looked like on <a href="https://www.reddit.com/r/web_design/s/eKBc3NkvpT" rel="noopener">April Fools&#8217; Day in 2019</a>. They turned the site &#8220;full GeoCities&#8221; for fun. Yet everything comes full circle. Now StackOverflow itself <a href="https://www.ericholscher.com/blog/2025/jan/21/stack-overflows-decline/" rel="noopener">seems destined to become as fossilized as GeoCities</a>. Even so, the site is currently attempting a new, real redesign to <a href="https://stackoverflow.blog/2026/02/25/your-sneak-peek-at-the-redesigned-stack-overflow/" rel="noopener">survive rather than for fun</a>. It&#8217;s sobering to consider that maybe the only StackOverflow experience for the next generation of coders will be if ChatGPT gets a StackOverflow restyle on a future April Fools&#8217;.</p>


<h3 class="wp-block-heading" id="stack-egg">Stack Egg</h3>


<p>While we’re on the topic of StackOverflow, their <a href="https://balpha.de/2015/04/the-making-of-stackegg/" rel="noopener">Stack Egg prank</a> from 2015 was very cool, but it might win my award for the most over-engineered April Fools’ prank that caused the most serious problems for a website. The premise was another Nineties throwback, this time to the nineties <a href="https://en.wikipedia.org/wiki/Tamagotchi" rel="noopener">Tamagotchi</a> craze.</p>



<p>The idea, as the creator describes it, was that every site on the Stack Exchange network would have its own “Stack Egg,” representing that site. The goal was to collaboratively keep your metaphorical “site” alive using hypothetical actions named after real actions on the site, such as upvotes to feed the Tamagotchi, and review actions to clean up the poop so the Tamagotchi doesn’t get sick.</p>



<p>It was a nifty concept, although like Google’s April Fools’ games, it’s more neat than laugh-out-loud funny. The part that does make me laugh — I don’t feel too guilty saying it since it was more than a decade ago — was that this is a game about keeping the websites alive, and it inadvertently <a href="https://en.wikipedia.org/wiki/Denial-of-service_attack" rel="noopener">DDoS-ed</a> its <a href="https://meta.stackoverflow.com/questions/289044/is-stackegg-causing-me-problems-or-something-else/289045#289045" rel="noopener">own websites and took down the whole StackExchange network</a>.</p>



<p>And yet, the creators thought the fact that they had the foresight to implement a feature flag that allowed switching it off meant this was a case study in “<a href="https://cacm.acm.org/practice/operational-excellence-in-april-fools-pranks/" rel="noopener">Operational Excellence in AFPs (April Fools’ Pranks)</a>.” Yep, that is an actual article published in a peer-reviewed journal. According to the article, the engineers involved pushed a fix about two hours later to salvage the prank. <a href="https://meta.stackexchange.com/a/252403" rel="noopener">Code Golf was the winner of the game</a>, in case you’re wondering. According to the same post that announced the winner, “it&#8217;s by no means designed to withstand exploits,” and in the two days the feature was live, users discovered a vulnerability that was “close to <a href="https://blog.stackoverflow.com/2008/12/vote-fraud-and-you/" rel="noopener">voting fraud</a>.”</p>



<p>I mentioned the over-engineering, so here’s the part that makes the unintentional punchline even funnier: rather than investing more time guarding against the basics, such as not bringing down the website and considering security, the creator spent time making his own <a href="https://en.wikipedia.org/wiki/Turing_completeness" rel="noopener">Turing-complete</a> language to handle the LCD-style <a href="https://stackexchange.github.io/stackegg/" rel="noopener">animations</a>, <a href="https://stackexchange.github.io/stackegg/" rel="noopener"></a>“because I wanted to! Creating a programming language is fun.”</p>



<p>That’s such a classically geeky way to prioritize!</p>


<h3 class="wp-block-heading" id="google-mic-drop">Google Mic Drop</h3>


<p>If Stack Egg created the most issues I’ve ever heard of for a website that created the prank, the most mean-spirited high-profile UI prank — which caused the most problems for users — has to be <a href="https://blog.google/products-and-platforms/products/gmail/introducing-gmail-mic-drop/" rel="noopener">Google Mic Drop</a>. It dropped (pun intended) on April Fools’ Day 2016, shortly after Google <a href="https://www.engadget.com/2015-10-02-alphabet-do-the-right-thing.html" rel="noopener">changed its motto</a> from “don’t be evil” to “do the right thing.&#8221; Then, they promptly redefined the &#8220;right thing&#8221; as sabotaging people’s professional reputations with a minion GIF.</p>



<p>Google added a button, nice and close to the regular “Send” button in Gmail, that would send a farewell message to the recipient with an animated Minion dropping a mic then <strong>block all emails from that recipient permanently, without prompting the sender to confirm first</strong>. Better still, there was a bug that meant the recipient could receive that &#8220;GIF of death&#8221; and the block, even if the sender managed to press the correct “Send” button in the confusing new UI.</p>



<p>The <a href="https://www.9news.com.au/technology/google-april-fools-day-prank-backfires-man-loses-job/4a9699dc-06ba-426a-a84e-aad1f0eae313" rel="noopener">“hilarity” that ensued</a> included:</p>



<ul class="wp-block-list">
<li>A funeral home accidentally sent a mic drop and block to a grieving family.</li>



<li><a href="https://www.9news.com.au/technology/google-april-fools-day-prank-backfires-man-loses-job/4a9699dc-06ba-426a-a84e-aad1f0eae313" rel="noopener"></a>A man posted on the Gmail help forum, “Thanks to Mic Drop, I just lost my job.”</li>
</ul>



<p>Google <a href="https://blog.google/products-and-platforms/products/gmail/introducing-gmail-mic-drop/" rel="noopener">disabled the feature</a> before the end of April Fools’ Day and issued an apology saying, “It looks like we pranked ourselves this year.” I am not sure how the joke was on Google, so much as the people whose livelihoods and relationships were destroyed.</p>



<p>Remember when I said in the intro that April Fools’ is a distraction from how the joke is on us for believing that the web is what it seems? This Google prank was a reminder that if you believe an <a href="https://about.google/company-info/how-our-business-works/" rel="noopener">advertising company masquerading as a search company</a> has the judgment and ethics to prioritize your interests, when they <a href="https://www.digitaltrends.com/computing/googles-new-policy-tracks-all-your-devices-with-no-opt-out/" rel="noopener">hoard your personal data</a> and <a href="https://www.wheresyoured.at/the-men-who-killed-google/" rel="noopener">don’t actually care if you can find anything</a>, the real mic drop moment is when you realize that your career and relationships are a data point in Google&#8217;s next <a href="https://www.reddit.com/r/YoutubeMusic/comments/15b4adh/come_on_google_stop_being_so_ridiculous_with_the/" rel="noopener">A/B test</a>.</p>


<h3 class="wp-block-heading" id="prank-ui-ux-research-articles">Prank UI/UX research articles</h3>


<p>The funniest part of <a href="https://www.nngroup.com/topic/ux-humor/" rel="noopener">these April Fools&#8217; UI/UX advice articles</a> is that they&#8217;re published by a serious, high-profile consultancy and research group, so the authors work hard to make it obvious these are April Fools&#8217; hoaxes. In each article, “APRIL FOOLS” is in the title in ALL CAPS. And in the first paragraph of the newer hoax articles: &#8220;<em>This article was published as an April Fool&#8217;s hoax and does not contain real recommendations.&#8221;</em> I like to imagine the marketing department thought this was a great idea, and then the authors of the articles tried their best not to make fools of themselves. I noticed the group stopped posting hoax content after 2022.</p>



<p class="is-style-explanation"><strong>Sidenote:</strong> Educational resources people rely on as a source may not be the best place for prank posts. It reminds me of this peer-reviewed <a href="https://radiopaedia.org/" rel="noopener">radiology website</a> that on April Fools &#8216; Day 2015 posted a hoax X-ray image under the title <em>“Ectopia cordis interna &#8211; <a href="https://en.wikipedia.org/wiki/Tin_Man_(Oz)" rel="noopener">Tin(Man</a>) syndrome.”</em> Over the years, medical professionals circulated the image unaware it was a hoax, and then, in 2025, <a href="https://retractionwatch.com/2025/09/02/tin-man-syndrome-five-other-case-studies-retracted-following-retraction-watch-coverage/" rel="noopener">six medical journal case studies involving the made-up condition had to be retracted</a>.</p>



<p>Actually, the hoax UI/UX articles are educational, in a <a href="https://ui-patterns.com/blog/User-Interface-AntiPatterns" rel="noopener">UI antipatterns</a> kind of way, such as “<a href="https://www.nngroup.com/articles/users-love-change/" rel="noopener">Users Love Change: Combatting a UX Myth</a>,” which advocates redesigning the UI as often as possible for the heck of it — except I can’t help but feel <a href="https://community.atlassian.com/forums/Jira-questions/Why-are-there-so-many-UI-changes-happening-continuously/qaq-p/939786" rel="noopener">JIRA took that advice literally</a>. The “<a href="https://www.nngroup.com/articles/dog-ux/" rel="noopener">Canine UX</a>” article teaches ideas of user personas and design in a fun way. And “<a href="https://www.nngroup.com/articles/ux-public-bathrooms/" rel="noopener">The User Experience of Public Bathrooms</a>” reads as if <a href="https://www.reddit.com/r/seinfeld/comments/v7xfiw/georges_obsession_with_bathrooms/" rel="noopener">George Costanza</a> from <em>Seinfeld</em> turned his toilet obsession into a lesson in usability.</p>


<h3 class="wp-block-heading" id="digitalocean-buys-codepen-io">DigitalOcean buys codepen.io</h3>


<p>Regular readers of CSS-Tricks know that the founder, Chris Coyier, <a href="https://css-tricks.com/css-tricks-is-joining-digitalocean/">really did decide in 2022 to sell the website to our current stewards, DigitalOcean</a>, so that he could focus on his other projects, such as <a href="https://codepen.io/" rel="noopener">CodePen</a>. Therefore, the <a href="https://youtu.be/dQw4w9WgXcQ?si=Jx-MFGEMeBp6nyan" rel="noopener">announcement</a> on CodePen that DigitalOcean was also buying that website seemed maddeningly plausible. The level of detail in the hoax announcement increased verisimilitude. For instance, the claim that users could use custom domain names on CodePen for free, as long as the domain was DigitalOcean-hosted. In fact, the only sign it was a prank is that nobody anywhere announced anything like this, unless you count me posting it today on a DigitalOcean-owned website.</p>



<p><em>Happy April Fools’ Day, everyone!</em></p>
<hr />
<p><small><a rel="nofollow" href="https://css-tricks.com/front-end-april-fools-top-10/">Front-End Fools: Top 10 April Fools’ UI Pranks of All Time</a> originally published on <a rel="nofollow" href="https://css-tricks.com">CSS-Tricks</a>, which is part of the <a href="https://try.digitalocean.com/css-tricks/?utm_medium=rss&amp;utm_source=css-tricks.com&amp;utm_campaign=family_&amp;utm_content=">DigitalOcean</a> family. You should <a href="https://css-tricks.com/newsletters/">get the newsletter</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://css-tricks.com/front-end-april-fools-top-10/feed/</wfw:commentRss>
			<slash:comments>3</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">392794</post-id>	</item>
		<item>
		<title>Sniffing Out the CSS Olfactive API</title>
		<link>https://css-tricks.com/css-olfactive-api/</link>
					<comments>https://css-tricks.com/css-olfactive-api/#respond</comments>
		
		<dc:creator><![CDATA[John Rhea]]></dc:creator>
		<pubDate>Wed, 01 Apr 2026 13:54:47 +0000</pubDate>
				<category><![CDATA[Articles]]></category>
		<category><![CDATA[api]]></category>
		<guid isPermaLink="false">https://css-tricks.com/?p=393070</guid>

					<description><![CDATA[<p>A deep sniff of the new CSS Olfactive API, a set of proposed features for immersive user experiences using smell.</p>
<hr />
<p><small><a rel="nofollow" href="https://css-tricks.com/css-olfactive-api/">Sniffing Out the CSS Olfactive API</a> originally published on <a rel="nofollow" href="https://css-tricks.com">CSS-Tricks</a>, which is part of the <a href="https://try.digitalocean.com/css-tricks/?utm_medium=rss&amp;utm_source=css-tricks.com&amp;utm_campaign=family_&amp;utm_content=">DigitalOcean</a> family. You should <a href="https://css-tricks.com/newsletters/">get the newsletter</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p>A lot has happened in CSS in the last few years, but there’s nothing we needed less than the upcoming Olfactive API. Now, I know what you’re going to say, expanding the web in a more immersive way is a good thing, and in general I’d agree, but there’s no generalized hardware support for this yet and, in my opinion, it’s too much, too early.</p>



<span id="more-393070"></span>



<p>First let’s look at the hardware. Disney World and other theme parks have done some niche so-called 4D movies (which is nonsense since there isn’t a fourth dimensional aspect, and if you consider time the fourth dimension then every movie is fourth dimensional). And a few startups have tried to bring <a href="https://en.wikipedia.org/wiki/Olfactory_system" rel="noopener">olfactory senses</a> into the modern day, but as of this writing, the hardware isn’t consumer-ready yet. That said, it’s in active development and one startup assured me the technology would be available within the year. (And startups never, ever lie about when their products will launch, right?)</p>



<p>Even if it does come out within the year, would we even want this? I mean <a href="https://en.wikipedia.org/wiki/Smell-O-Vision" rel="noopener">Smell-O-Vision</a> totally caught on, right? It’s definitely not considered one of the worst inventions of all time… But, alas, no one cares about the ravings of a mad man, at least, not <em>this</em> mad man, so the API rolls on.</p>



<p>Alright, I’m going to step off my soap box now and try to focus on the technology and how it works.</p>


<h3 class="wp-block-heading" id="smell-tech">Smell Tech</h3>


<p>One of the fights currently going on in the CSS Working Group is whether we should limit smells to those considered pleasing by the perfume industry or whether to open websites to a much wider variety. For instance, while everyone’s olfactory sense is different, the perfume industry has centered on a selection of fragrances that will be pleasing to a wide swath of people.</p>



<p>That said, there are a large number of pleasing fragrances that would not be included in this, such as food-based smells: fresh baked bread etc. Fragrances that the Big Food Lobby is itching to include in their advertisements. As of now the CSS Olfactive API only includes the twelve general categories used by the perfume industry, but just like there are ways to expand the color gamut, the system is built to allow for expanded smells in the future should the number of available fragrance fragments increase.</p>


<h3 class="wp-block-heading" id="smelly-families">Smelly Families</h3>


<p>You don’t have to look far online to find something called the <a href="https://en.wikipedia.org/wiki/Fragrance_wheel" rel="noopener">Scent Wheel</a> (alternately called the Fragrance Wheel or the Wheel of Smell-Tune, but that last one is only used by me). There are four larger families of smell:</p>



<ul class="wp-block-list">
<li>Floral</li>



<li>Amber (previously called Oriental)</li>



<li>Woody</li>



<li>Fresh</li>
</ul>



<p>These four are each subdivided into additional categories though there are overlaps between where one of the larger families begins/ends and the sub families begin/end</p>



<ul class="wp-block-list">
<li>Floral:
<ul class="wp-block-list">
<li>Floral (<code>fl</code>)</li>



<li>Soft Floral (<code>sf</code>)</li>



<li>Floral Amber (<code>fa</code>)</li>
</ul>
</li>



<li>Amber:
<ul class="wp-block-list">
<li>Soft Amber (<code>sa</code>)</li>



<li>Amber (<code>am</code>)</li>



<li>Woody Amber (<code>wa</code>)</li>
</ul>
</li>



<li>Woody:
<ul class="wp-block-list">
<li>Woods (<code>wo</code>)</li>



<li>Mossy Woods (<code>mw</code>)</li>



<li>Dry Woods (<code>dw</code>)</li>
</ul>
</li>



<li>Fresh (<code>fr</code>)
<ul class="wp-block-list">
<li>Aromatic (<code>ar</code>)</li>



<li>Citrus (<code>ct</code>)</li>



<li>Water (<code>ho</code>)</li>



<li>Green (<code>gr</code>)</li>



<li>Fruity (<code>fu</code>)</li>
</ul>
</li>
</ul>



<p>It’s from these fifteen fragrance categories that a scent can be made by mixing different amounts using the two letter identifiers. (We’ll talk about this when we discuss the <code>scent()</code> function later on. Note that “Fresh” is the only large family with its own identifier (<code>fr</code>) as the other larger families are duplicated in the sub-families)</p>


<h3 class="wp-block-heading" id="implementation">Implementation</h3>


<p>First of all, its implemented (wisely) in HTML in much the same way video and audio are with the addition of the <code>&lt;scent&gt;</code> element, and <code>&lt;source&gt;</code> was again used to give the browser different options for wafting the scent toward your sniffer. Three competing file formats are being developed <code>.smll</code>, <code>.arma</code>, and, I kid you not, <code>.smly</code>. One by Google, one by Mozilla, and one, again, not kidding, by Frank’s Fine Fragrances who intends to jump on this “fourth dimension of the web.”</p>



<pre rel="HTML" class="wp-block-csstricks-code-block language-markup" data-line=""><code markup="tt">&lt;scent controls autosmell="none">
  &lt;source src=“mossywoods.smll” type=“scent/smll”>
  &lt;source src=“mossywoods.arma” type=“scent/arma”>
  &lt;source src=“mossywoods.smly” type=“scent/smly”>
  &lt;a href=“mossywoods.smll”>Smell our Mossy Woods scent&lt;/a>
&lt;/scent></code></pre>



<p>For accessibility, be sure that you set the <code>autosmell</code> attribute to <code>none</code>. In theory, this isn’t required, but some of the current hardware has a bug that turns on the wafter even if a smell hasn’t been activated.</p>



<p>However, similar to how you can use an image or video in the background of an element, you can also attach a scent profile to an element using the new <code>scent-profile</code> property.</p>



<p><code>scent-profile</code> can take one of three things.</p>



<p>The keyword <code>none</code> (default):</p>



<pre rel="CSS" class="wp-block-csstricks-code-block language-css" data-line=""><code markup="tt">scent-profile: none;</code></pre>



<p>A <code>url()</code> function and the path to a file e.g.:</p>



<pre rel="CSS" class="wp-block-csstricks-code-block language-css" data-line=""><code markup="tt">scent-profile: url(mossywoods.smll);</code></pre>



<p>Or a set of aromatic identifiers using the <code>scent()</code> function:</p>



<pre rel="CSS" class="wp-block-csstricks-code-block language-css" data-line=""><code markup="tt">scent-profile: scent(wo, ho, fu);</code></pre>



<p>This produces a scent that has notes of woody, water, and fruity which was described to me as “an orchard in the rain” but to me smelled more like “a wooden bowl of watered-down applesauce.” Please take that with a grain of salt, though, as I have been told I have “the nasal palette of a dead fish.”</p>



<p>You can add up to five scent sub-families at once. This is an arbitrary limit, but more than that would likely muddle the scent. Equal amounts of each will be used, but you can use the new <code>whf</code> unit to adjust how much of each is used. <code>100whf</code> is the most potent an aroma can be. Unlike most units, your implementation, must add up to <code>100whf</code> or less. If your numbers add up to more than 100, the browser will take the first <code>100whf</code>s it gets and ignore everything afterward.</p>



<pre rel="CSS" class="wp-block-csstricks-code-block language-css" data-line=""><code markup="tt">scent-profile: scent(wo 20whf, ho 13whf, fu 67whf);</code></pre>



<p>&#8230;or you could reduce the overall scent by choosing <code>whf</code>s less than 100:</p>



<pre rel="CSS" class="wp-block-csstricks-code-block language-css" data-line=""><code markup="tt">scent-profile: scent(wo 5whf, ho 2whf, fu 14whf);</code></pre>



<p>In the future, should other fragrances be allowed, they would simply need to add some new fragrance fragments from which to construct the aromatic air.</p>


<h3 class="wp-block-heading" id="sniffing-out-limitations">Sniffing Out Limitations</h3>


<p>One large concern for the working group was that some developer would go crazy placing <code>scent-profile</code>s on every single element, both overwhelming the user and muddling each scent used.</p>



<p>As such it was decided that the browser will only allow one <code>scent-profile</code> to be set per the parent element’s sub tree. This basically means that once you set a <code>scent-profile</code> on a particular element you cannot add a scent profile to any of its descendants, nor can you add a scent profile to any of its siblings. In this way, a scent profile set on a hungry selector (e.g. <code>*</code> or <code>div</code>) will create a fraction of the scent profiles than what might otherwise be created. While there are clearly easy ways to maliciously get around this limitation, it was thought that this should at least prevent a developer from accidentally overwhelming the user.</p>


<h3 class="wp-block-heading" id="aromatic-accessibility">Aromatic Accessibility</h3>


<p>Since aromas can be overpowering they’ve also added a media-query:</p>



<pre rel="CSS" class="wp-block-csstricks-code-block language-css" data-line=""><code markup="tt">.reeks {
  scent-profile: scent(fl, fa, fu);
}

@media (prefers-reduced-pungency: reduce) {
  .reeks {
    scent-profile: scent(fl 10whf, fa 10whf, fu 10whf);
  }
}

@media (prefers-reduced-pungency: remove) {
  .reeks {
    scent-profile: none;
  }
}</code></pre>


<h3 class="wp-block-heading" id="browser-support">Browser Support</h3>


<p>Surprisingly, despite Chrome Canary literally being named after a bird who would smell gas in the mine, Chrome has not yet begun experimenting with it. The only browser you can test things out on, as of this writing, is the KaiOS Browser.</p>


<h3 class="wp-block-heading" id="conclusion">Conclusion</h3>


<p>There you have it. I still don’t think we need this, but with the continuing march of technology it’s probably not something we can stop. So let&#8217;s make an agreement between you reading this and me here writing this that you’ll always use your new-found olfactory powers for good&#8230; <em>and</em> that you won’t ever say this article stinks.</p>



<p>Learn more about the <a href="https://css-tricks.com/front-end-april-fools-top-10/">CSS Olfactive API</a>.</p>
<hr />
<p><small><a rel="nofollow" href="https://css-tricks.com/css-olfactive-api/">Sniffing Out the CSS Olfactive API</a> originally published on <a rel="nofollow" href="https://css-tricks.com">CSS-Tricks</a>, which is part of the <a href="https://try.digitalocean.com/css-tricks/?utm_medium=rss&amp;utm_source=css-tricks.com&amp;utm_campaign=family_&amp;utm_content=">DigitalOcean</a> family. You should <a href="https://css-tricks.com/newsletters/">get the newsletter</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://css-tricks.com/css-olfactive-api/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">393070</post-id>	</item>
		<item>
		<title>What’s !important #8: Light/Dark Favicons, @mixin, object-view-box, and More</title>
		<link>https://css-tricks.com/whats-important-8/</link>
					<comments>https://css-tricks.com/whats-important-8/#comments</comments>
		
		<dc:creator><![CDATA[Daniel Schwarz]]></dc:creator>
		<pubDate>Tue, 31 Mar 2026 14:14:07 +0000</pubDate>
				<category><![CDATA[Articles]]></category>
		<category><![CDATA[news]]></category>
		<guid isPermaLink="false">https://css-tricks.com/?p=393306</guid>

					<description><![CDATA[<p>Short n’ sweet but ever so neat, this issue covers light/dark favicons, @mixin, anchor-interpolated morphing, object-view-box, new web features, and more.</p>
<hr />
<p><small><a rel="nofollow" href="https://css-tricks.com/whats-important-8/">What’s !important #8: Light/Dark Favicons, @mixin, object-view-box, and More</a> originally published on <a rel="nofollow" href="https://css-tricks.com">CSS-Tricks</a>, which is part of the <a href="https://try.digitalocean.com/css-tricks/?utm_medium=rss&amp;utm_source=css-tricks.com&amp;utm_campaign=family_&amp;utm_content=">DigitalOcean</a> family. You should <a href="https://css-tricks.com/newsletters/">get the newsletter</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p>Short n’ sweet but ever so neat, this issue covers light/dark favicons, <code>@mixin</code>, anchor-interpolated morphing, <code>object-view-box</code>, new web features, and more.</p>



<span id="more-393306"></span>


<h3 class="wp-block-heading" id="svg-favicons-that-respect-the-color-scheme">SVG favicons that respect the color scheme</h3>


<p>I’m a sucker for colorful logos with about 50% lightness that look awesome on light <em>and</em> dark backgrounds, but not all logos can be like that. Paweł Grzybek showed us <a href="https://pawelgrzybek.com/svg-favicons-that-respect-theme-preference/" rel="noopener">how to implement SVG favicons that respect the color scheme</a>, enabling us to display favicons conditionally, but the behavior isn’t consistent across web browsers. It’s an interesting read and there appears to be a campaign to get it working correctly.</p>



<p>And once that happens, here’s a <a href="https://bsky.app/profile/freefrontend.bsky.social/post/3mhxjk6qnqc2j" rel="noopener">skeuomorphic egg-themed CSS toggle</a> that I found last week. Perfect timing, honestly.</p>



<blockquote class="bluesky-embed" data-bluesky-uri="at://did:plc:7wef2htaxqe5rkxf62htfkun/app.bsky.feed.post/3mhxjk6qnqc2j" data-bluesky-cid="bafyreigzryvjpdpibnbjwxppugl676omwxr62llsdruwfnoh4lxilzmry4" data-bluesky-embed-color-mode="system"><p lang="en">Skeuomorphic Egg Toggle Switch [HTML + CSS + JS]

Organic mechanics. Complex box-shadow layering and border-radius manipulation. Tactile feedback through depth. Source code: freefrontend.com/code/skeuomo&#8230;<br><br><a href="https://bsky.app/profile/did:plc:7wef2htaxqe5rkxf62htfkun/post/3mhxjk6qnqc2j?ref_src=embed" rel="noopener">[image or embed]</a></p>&mdash; FreeFrontend (<a href="https://bsky.app/profile/did:plc:7wef2htaxqe5rkxf62htfkun?ref_src=embed" rel="noopener">@freefrontend.bsky.social</a>) <a href="https://bsky.app/profile/did:plc:7wef2htaxqe5rkxf62htfkun/post/3mhxjk6qnqc2j?ref_src=embed" rel="noopener">Mar 26, 2026 at 11:42</a></blockquote><script async src="https://embed.bsky.app/static/embed.js" charset="utf-8"></script>


<h3 class="wp-block-heading" id="help-the-css-wg-shape-mixin">Help the CSS WG shape <code>@mixin</code></h3>


<p>It seems that <code>@mixin</code> is taking a step forward. <a href="https://github.com/LeaVerou/blog/discussions/137" rel="noopener">Lea Verou showed us a code snippet and asked what we think of it</a>.</p>



<blockquote class="bluesky-embed" data-bluesky-uri="at://did:plc:eagnfcoqnbtzpkglrtej6ayg/app.bsky.feed.post/3mhyr2rw2ls2r" data-bluesky-cid="bafyreih5m66lqr7bgq64utaupzjkdu3hrqtwmhn2loqlfrbkeiv3zqq3ve" data-bluesky-embed-color-mode="system"><p lang="en">&#x1f6a8; Want mixins in CSS? Help the CSS WG by telling us what feels natural to you! Look at the code in the screenshot. What resulting widths would *you* find least surprising for each of div, div &gt; h2, div + p? Polls: ┣ Github: github.com/LeaVerou/blo&#8230; ┗ Mastodon: front-end.social/@leaverou/11&#8230;<br><br><a href="https://bsky.app/profile/did:plc:eagnfcoqnbtzpkglrtej6ayg/post/3mhyr2rw2ls2r?ref_src=embed" rel="noopener">[image or embed]</a></p>&mdash; Lea Verou, PhD (<a href="https://bsky.app/profile/did:plc:eagnfcoqnbtzpkglrtej6ayg?ref_src=embed" rel="noopener">@lea.verou.me</a>) <a href="https://bsky.app/profile/did:plc:eagnfcoqnbtzpkglrtej6ayg/post/3mhyr2rw2ls2r?ref_src=embed" rel="noopener">Mar 26, 2026 at 23:29</a></blockquote>


<h3 class="wp-block-heading" id="anchorinterpolated-morphing-tutorial">Anchor-interpolated morphing tutorial</h3>


<p>Chris Coyier showed us <a href="https://frontendmasters.com/blog/image-gallery-with-popovers-and-aim-anchor-interpolated-morph/" rel="noopener">how to build an image gallery using popovers and something called AIM</a> (Anchor-Interpolated Morphing). I’m only hearing about this now but <a href="https://argyle.ink/anchor-interpolated-morphing/" rel="noopener">Adam Argyle talked about AIM</a> back in January. It’s not a new CSS feature but rather the idea of animating something from its starting position to an anchored position. Don’t miss this one.</p>



<div class="wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper"><iframe id="cp_embed_019cb5ec-807e-727a-b18a-5eb41b0fc901" src="//codepen.io/editor/anon/embed/019cb5ec-807e-727a-b18a-5eb41b0fc901?height=450&amp;theme-id=1&amp;slug-hash=019cb5ec-807e-727a-b18a-5eb41b0fc901&amp;default-tab=result" height="450" scrolling="no" frameborder="0" allowfullscreen allowpaymentrequest name="CodePen Embed 019cb5ec-807e-727a-b18a-5eb41b0fc901" title="CodePen Embed 019cb5ec-807e-727a-b18a-5eb41b0fc901" class="cp_embed_iframe" style="width:100%;overflow:hidden">CodePen Embed Fallback</iframe></div>



<p>Also, do you happen to remember <a href="https://codepen.io/t_afif/pen/wBWWKxP" rel="noopener">Temani’s demo</a> that I shared a few weeks ago? Well, Frontend Masters have published <a href="https://frontendmasters.com/blog/two-circles-one-arrow-and-anchor-positioning/" rel="noopener">the tutorial for that</a> too!</p>



<div class="wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper"><iframe id="cp_embed_wBWWKxP" src="//codepen.io/anon/embed/wBWWKxP?height=450&amp;theme-id=1&amp;slug-hash=wBWWKxP&amp;default-tab=result" height="450" scrolling="no" frameborder="0" allowfullscreen allowpaymentrequest name="CodePen Embed wBWWKxP" title="CodePen Embed wBWWKxP" class="cp_embed_iframe" style="width:100%;overflow:hidden">CodePen Embed Fallback</iframe></div>


<h3 class="wp-block-heading" id="remember-objectviewbox-me-neither">Remember <code>object-view-box</code>? Me neither</h3>


<p>CSS <code>object-view-box</code> allows an element to be zoomed, cropped, or framed in a way that resembles how SVG’s <code>viewBox</code> works, but since Chrome implemented it back in August 2022, there’s been no mention of it. To be honest, I don’t remember it at all, which is a shame because it sounds useful. In a Bluesky thread, Victor Ponamariov explains <a href="https://bsky.app/profile/vpon.me/post/3mhsxu4bcpd23" rel="noopener">how <code>object-view-box</code> works</a>. Hopefully, Safari and Firefox implement it soon.</p>



<blockquote class="bluesky-embed" data-bluesky-uri="at://did:plc:k5j6zg5xqnaady5e77smdiaz/app.bsky.feed.post/3mhsxu4bcpd23" data-bluesky-cid="bafyreia5jttfh5ohjyqwsekwycahokao3qlvvmvc73eyvsudsmtitp3cqe" data-bluesky-embed-color-mode="system"><p lang="en">Wouldn&#x27;t it be great to have native image cropping in CSS? It actually exists: object-view-box.<br><br><a href="https://bsky.app/profile/did:plc:k5j6zg5xqnaady5e77smdiaz/post/3mhsxu4bcpd23?ref_src=embed" rel="noopener">[image or embed]</a></p>&mdash; Victor (<a href="https://bsky.app/profile/did:plc:k5j6zg5xqnaady5e77smdiaz?ref_src=embed" rel="noopener">@vpon.me</a>) <a href="https://bsky.app/profile/did:plc:k5j6zg5xqnaady5e77smdiaz/post/3mhsxu4bcpd23?ref_src=embed" rel="noopener">Mar 24, 2026 at 16:15</a></blockquote>


<h3 class="wp-block-heading" id="cornershape-for-everyday-ui-elements"><code>corner-shape</code> for everyday UI elements</h3>


<p>Much has been said about <a href="https://css-tricks.com/?s=corner-shape">CSS <code>corner-shape</code></a>, by us and the wider web dev community, despite only being supported by Chrome for now. It’s such a fun feature, offering so many ways to turn boxes into interesting shapes, but <a href="https://www.smashingmagazine.com/2026/03/beyond-border-radius-css-corner-shape-property-ui/" rel="noopener">Brecht De Ruyte’s <code>corner-shape</code> article</a> focuses more on how we might use <code>corner-shape</code> for everyday UI elements/components.</p>



<figure class="wp-block-image size-large"><img data-recalc-dims="1" loading="lazy" decoding="async" width="1024" height="576" src="https://i0.wp.com/css-tricks.com/wp-content/uploads/2026/03/1-1024x576.png?resize=1024%2C576&#038;ssl=1" alt="An interface design titled Buttons and Tags showcasing various UI component shapes using the corner-shape property. The display includes a row of solid buttons in different colors labeled Bevel, Superellipse, Squircle, Notch, and Scoop, followed by a set of outlined buttons and a series of decorative status tags like Shipped and Pending. Below these are directional tags with arrow shapes and a row of notification badges featuring icons for a bell, message, and alert with numerical counters." class="wp-image-393307" srcset="https://i0.wp.com/css-tricks.com/wp-content/uploads/2026/03/1.png?resize=1024%2C576&amp;ssl=1 1024w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2026/03/1.png?resize=300%2C169&amp;ssl=1 300w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2026/03/1.png?resize=768%2C432&amp;ssl=1 768w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2026/03/1.png?resize=1536%2C864&amp;ssl=1 1536w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2026/03/1.png?w=1920&amp;ssl=1 1920w" sizes="auto, (min-width: 735px) 864px, 96vw" /><figcaption class="wp-element-caption">Source: <a href="https://www.smashingmagazine.com/2026/03/beyond-border-radius-css-corner-shape-property-ui/" rel="noopener">Smashing Magazine</a>.</figcaption></figure>


<h3 class="wp-block-heading" id="the-layout-maestro">The Layout Maestro</h3>


<p>Ahmad Shadeed’s course — <a href="https://thelayoutmaestro.com/" rel="noopener">The Layout Maestro</a> — teaches you how to plan and build CSS layouts using modern techniques. Plus, you can learn how to master building the bones of websites using an extended trial of the web development browser, <a href="https://polypane.app/" rel="noopener">Polypane</a>, which comes free with the course.</p>



<figure class="wp-block-image size-large"><img data-recalc-dims="1" loading="lazy" decoding="async" width="1024" height="419" src="https://i0.wp.com/css-tricks.com/wp-content/uploads/2026/03/2-1024x419.png?resize=1024%2C419&#038;ssl=1" alt="A bento grid layout featuring multiple rounded rectangular panels in a very light lavender hue. The central panel displays a logo consisting of a purple stylized window icon and the text The Layout Maestro in black and purple sans-serif font, accented by small purple sparkles. The surrounding empty panels vary in size and aspect ratio, creating a clean and modern asymmetrical composition against a white background." class="wp-image-393308" srcset="https://i0.wp.com/css-tricks.com/wp-content/uploads/2026/03/2-scaled.png?resize=1024%2C419&amp;ssl=1 1024w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2026/03/2-scaled.png?resize=300%2C123&amp;ssl=1 300w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2026/03/2-scaled.png?resize=768%2C315&amp;ssl=1 768w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2026/03/2-scaled.png?resize=1536%2C629&amp;ssl=1 1536w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2026/03/2-scaled.png?resize=2048%2C839&amp;ssl=1 2048w" sizes="auto, (min-width: 735px) 864px, 96vw" /><figcaption class="wp-element-caption">Source: <a href="https://thelayoutmaestro.com/" rel="noopener">The Layout Maestro</a>.</figcaption></figure>


<h3 class="wp-block-heading" id="new-web-platform-features">New web platform features</h3>


<p>Firefox and Safari shipped new features (none baseline, sadly):</p>



<ul class="wp-block-list">
<li><a href="https://developer.mozilla.org/en-US/docs/Mozilla/Firefox/Releases/149" rel="noopener">Firefox 149</a>
<ul class="wp-block-list">
<li><a href="https://una.im/popover-hint/" rel="noopener"><code>popover=hint</code></a> (also supported by Chrome)</li>



<li>Name-only <a href="https://css-tricks.com/css-container-queries/">containers</a> (e.g., <code>@container name { }</code>)</li>
</ul>
</li>



<li><a href="https://developer.apple.com/documentation/safari-release-notes/safari-26_4-release-notes" rel="noopener">Safari 26.4</a>
<ul class="wp-block-list">
<li>Name-only containers (as above)</li>



<li><a href="https://css-tricks.com/masonry-layout-is-now-grid-lanes/"><code>display: grid-lanes</code></a> and <a href="https://www.w3.org/TR/css-grid-3/#placement-tolerance" rel="noopener"><code>flow-tolerance</code></a></li>
</ul>
</li>



<li><a href="https://developer.apple.com/documentation/safari-technology-preview-release-notes/stp-release-240" rel="noopener">Safari TP 240</a>
<ul class="wp-block-list">
<li><a href="https://github.com/WebKit/WebKit/commit/cdf824701b8c4d6c2047d7318deb2a9da0e0fbd2" rel="noopener"><code>revert-rule</code></a></li>
</ul>
</li>
</ul>



<p>Also, Bramus said that Chrome 148 will have <a href="https://www.bram.us/2026/03/15/at-rule/" rel="noopener">at-rule feature queries</a>, while Chrome 148 and Firefox 150 will allow <a href="https://www.bram.us/2026/03/19/more-easy-light-dark-mode-switching-light-dark-is-about-to-support-images/" rel="noopener"><code>background-image</code> to support <code>light-dark()</code></a>. In any case, there’s a new website called <a href="https://basewatch.dev/" rel="noopener">BaseWatch</a> that tracks baseline status for all of these CSS features.</p>



<p>Ciao!</p>
<hr />
<p><small><a rel="nofollow" href="https://css-tricks.com/whats-important-8/">What’s !important #8: Light/Dark Favicons, @mixin, object-view-box, and More</a> originally published on <a rel="nofollow" href="https://css-tricks.com">CSS-Tricks</a>, which is part of the <a href="https://try.digitalocean.com/css-tricks/?utm_medium=rss&amp;utm_source=css-tricks.com&amp;utm_campaign=family_&amp;utm_content=">DigitalOcean</a> family. You should <a href="https://css-tricks.com/newsletters/">get the newsletter</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://css-tricks.com/whats-important-8/feed/</wfw:commentRss>
			<slash:comments>1</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">393306</post-id>	</item>
		<item>
		<title>Form Automation Tips for Happier User and Clients</title>
		<link>https://css-tricks.com/form-automation-tips-for-happier-user-and-clients/</link>
					<comments>https://css-tricks.com/form-automation-tips-for-happier-user-and-clients/#respond</comments>
		
		<dc:creator><![CDATA[Iqra Naaem]]></dc:creator>
		<pubDate>Mon, 30 Mar 2026 14:12:14 +0000</pubDate>
				<category><![CDATA[Articles]]></category>
		<category><![CDATA[forms]]></category>
		<guid isPermaLink="false">https://css-tricks.com/?p=391993</guid>

					<description><![CDATA[<p>That gap between "the form works" and "the business works" is something we don't really tend to discuss much as front-enders. We focus a great deal on user experience, validation methods, and accessibility, yet we overlook what the data does once it leaves our control</p>
<hr />
<p><small><a rel="nofollow" href="https://css-tricks.com/form-automation-tips-for-happier-user-and-clients/">Form Automation Tips for Happier User and Clients</a> originally published on <a rel="nofollow" href="https://css-tricks.com">CSS-Tricks</a>, which is part of the <a href="https://try.digitalocean.com/css-tricks/?utm_medium=rss&amp;utm_source=css-tricks.com&amp;utm_campaign=family_&amp;utm_content=">DigitalOcean</a> family. You should <a href="https://css-tricks.com/newsletters/">get the newsletter</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p>I deployed a contact form that last month that, in my opinion, was well executed. It had all the right semantics, seamless validation, and great keyboard support. You know, all of the features you&#8217;d want in your portfolio.</p>



<p>But&#8230; a mere two weeks after deployment, my client called.&nbsp;<q>We lost a referral because it was sitting in your inbox over the weekend.</q></p>



<p>The form worked perfectly. The workflow didn&#8217;t.</p>



<span id="more-391993"></span>


<h3 class="wp-block-heading" id="the-problem-nobody-talks-about">The Problem Nobody Talks About</h3>


<p>That gap between &#8220;the form works&#8221; and &#8220;the business works&#8221; is something we don&#8217;t really tend to discuss much as front-enders. We focus a great deal on user experience, validation methods, and accessibility, yet we overlook what the data does once it leaves our control. That is exactly where things start to fall apart in the real world.</p>



<p>Here&#8217;s what I learned from that experience that would have made for a much better form component.</p>


<h3 class="wp-block-heading" id="why-send-email-on-submit-fails">Why &#8220;Send Email on Submit&#8221; Fails</h3>


<p>The pattern we all use looks something like this:</p>



<pre rel="JavaScript" class="wp-block-csstricks-code-block language-javascript" data-line=""><code markup="tt">fetch('/api/contact', {
  method: 'POST',
  body: JSON.stringify(formData)
})

// Email gets sent and we call it done</code></pre>



<p>I have seen duplicate submissions cause confusion, specifically when working with CRM systems, like Salesforce. For example, I have encountered inconsistent formatting that hinders automated imports. I have also experienced weekend queries that were overlooked until Monday morning. I have debugged queries where copying and pasting lost decimal places for quotes. There have also been &#8220;required&#8221; fields for which &#8220;required&#8221; was simply a misleading label.</p>



<p>I had an epiphany: the reality was that having a working form was just the starting line, not the end. The fact is that the email is not a notification; rather, it&#8217;s a handoff. If it&#8217;s treated merely as a notification, it puts us into a bottleneck with our own code. In fact, Litmus, as shown in their&nbsp;<a href="https://www.litmus.com/state-of-email-reports" rel="noopener">2025 State of Email Marketing Report</a>&nbsp;(sign-up required), found inbox-based workflows result in lagging follow-ups, particularly with sales teams that rely on lead generation.</p>



<figure class="wp-block-image size-full is-resized ticss-1f641bd8"><img data-recalc-dims="1" loading="lazy" decoding="async" width="1142" height="1476" src="https://i0.wp.com/css-tricks.com/wp-content/uploads/2026/02/auto-forms-2.png?resize=1142%2C1476" alt="Detailing a broken workflow for a submitted form. User submits form, email reaches inbox, manual spreadsheet entries, formatting errors, and delays." class="wp-image-392005" style="width:500px" srcset="https://i0.wp.com/css-tricks.com/wp-content/uploads/2026/02/auto-forms-2.png?w=1142&amp;ssl=1 1142w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2026/02/auto-forms-2.png?resize=232%2C300&amp;ssl=1 232w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2026/02/auto-forms-2.png?resize=792%2C1024&amp;ssl=1 792w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2026/02/auto-forms-2.png?resize=768%2C993&amp;ssl=1 768w" sizes="auto, (min-width: 735px) 864px, 96vw" /></figure>


<h3 class="wp-block-heading" id="designing-forms-for-automation">Designing Forms for Automation</h3>


<p><strong>The bottom line is that front-end decisions directly influence back-end automation.</strong>&nbsp;In recent research from HubSpot, data at the front-end stage (i.e., the user interaction) makes or breaks what is coming next.</p>



<p>These are the practical design decisions that changed how I build forms:</p>


<h4 class="wp-block-heading" id="required-vs-optional-fields">Required vs. Optional Fields</h4>


<p>Ask yourself:&nbsp;<q>What does the business rely on the data for?</q>&nbsp;Are phone calls the primary method for following up with a new lead? Then let&#8217;s make that field required. Is the lead&#8217;s professional title a crucial context for following up? If not, make it optional. This takes some interpersonal collaboration before we even begin marking up code.</p>



<p>For example, I made an incorrect assumption that a phone number field was an optional piece of information, but the CRM required it. The result? My submissions were invalidated and the CRM flat-out rejected them.</p>



<p>Now I know to drive my coding decisions from a business process perspective, not just my assumptions about what the user experience ought to be.</p>


<h4 class="wp-block-heading" id="normalize-data-early">Normalize Data Early</h4>


<p>Does the data need to be formatted in a specific way once it&#8217;s submitted? It&#8217;s a good idea to ensure that some data, like phone numbers, are formatted consistently so that the person on the receiving has an easier time scanning the information. Same goes when it comes to trimming whitespace and title casing.</p>



<p>Why? Downstream tools are dumb. They are utterly unable to make the correlation that &#8220;John Wick&#8221; and &#8220;john wick&#8221; are related submissions. I once watched a client manually clean up 200 CRM entries because inconsistent casing had created duplicate records. That&#8217;s the kind of pain that five minutes of front-end code prevents.</p>


<h4 class="wp-block-heading" id="prevent-duplicate-entries-from-the-front-end">Prevent Duplicate Entries From the Front End</h4>


<p>Something as simple as disabling the Submit button on click can save the headache of sifting through duplicative submissions. Show clear &#8220;submission states&#8221; like a loading indicator that an action is being processed. Store a flag that a submission is in progress.</p>



<p>Why? Duplicate CRM entries cost real money to clean up. Impatient users on slow networks will absolutely click that button multiple times if you let them.</p>


<h4 class="wp-block-heading" id="success-and-error-states-that-matter">Success and Error States That Matter</h4>


<p>What should the user know once the form is submitted? I think it&#8217;s super common to do some sort of default &#8220;Thanks!&#8221; on a successful submission, but how much context does that really provide? Where did the submission go? When will the team follow up? Are there resources to check out in the meantime? That&#8217;s all valuable context that not only sets expectations for the lead, but gives the team a leg up when following up.</p>



<p>Error messages should help the business, too. Like, if we&#8217;re dealing with a duplicate submission, it&#8217;s way more helpful to say something like,&nbsp;<em>&#8220;This email is already in our system&#8221;</em>&nbsp;than some generic&nbsp;<em>&#8220;Something went wrong&#8221;</em>&nbsp;message.</p>



<figure class="wp-block-image size-full"><img data-recalc-dims="1" loading="lazy" decoding="async" width="1202" height="818" src="https://i0.wp.com/css-tricks.com/wp-content/uploads/2026/02/auto-forms-3.png?resize=1202%2C818" alt="Comparing two types of submitted raw data. Formatting problems displayed on the left and properly formatted data on the right." class="wp-image-392006" srcset="https://i0.wp.com/css-tricks.com/wp-content/uploads/2026/02/auto-forms-3.png?w=1202&amp;ssl=1 1202w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2026/02/auto-forms-3.png?resize=300%2C204&amp;ssl=1 300w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2026/02/auto-forms-3.png?resize=1024%2C697&amp;ssl=1 1024w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2026/02/auto-forms-3.png?resize=768%2C523&amp;ssl=1 768w" sizes="auto, (min-width: 735px) 864px, 96vw" /></figure>


<h3 class="wp-block-heading" id="a-better-workflow">A Better Workflow</h3>


<p>So, how exactly would I approach form automation next time? Here are the crucial things I missed last time that I&#8217;ll be sure to hit in the future.</p>


<h4 class="wp-block-heading" id="better-validation-before-submission">Better Validation Before Submission</h4>


<p>Instead of simply checking if fields exist:</p>



<pre rel="JavaScript" class="wp-block-csstricks-code-block language-javascript" data-line=""><code markup="tt">const isValid = email &amp;&amp; name &amp;&amp; message;</code></pre>



<p>Check if they&#8217;re actually usable:</p>



<pre rel="JavaScript" class="wp-block-csstricks-code-block language-javascript" data-line=""><code markup="tt">function validateForAutomation(data) {
  return {
    email: /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(data.email),
    name: data.name.trim().length >= 2,
    phone: !data.phone || /^\d{10,}$/.test(data.phone.replace(/\D/g, ''))
  };
}</code></pre>



<p><strong>Why this matters:</strong>&nbsp;CRMs will reject malformed emails. Your error handling should catch this before the user clicks submit, not after they&#8217;ve waited two seconds for a server response.</p>



<p>At the same time, it&#8217;s worth noting that the phone validation here covers common cases, but is not bulletproof for things like international formats. For production use, consider a library like&nbsp;<a href="https://github.com/google/libphonenumber" rel="noopener">libphonenumber</a>&nbsp;for comprehensive validation.</p>


<h4 class="wp-block-heading" id="consistent-formatting">Consistent Formatting</h4>


<p>Format things before it sends rather than assuming it will be handled on the back end:</p>



<pre rel="JavaScript" class="wp-block-csstricks-code-block language-javascript" data-line=""><code markup="tt">function normalizeFormData(data) {
  return {
    name: data.name.trim()
      .split(' ')
      .map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
      .join(' '),
    email: data.email.trim().toLowerCase(),
    phone: data.phone.replace(/\D/g, ''), // Strip to digits
    message: data.message.trim()
  };
}</code></pre>



<p><strong>Why I do this:</strong>&nbsp;Again, I&#8217;ve seen a client manually fix over 200 CRM entries because &#8220;JOHN SMITH&#8221; and &#8220;john smith&#8221; created duplicate records. Fixing this takes five minutes to write and saves hours downstream.</p>



<p>There&#8217;s a caveat to this specific approach. This name-splitting logic will trip up on single names, hyphenated surnames, and edge cases like &#8220;McDonald&#8221; or names with multiple spaces. If you need rock-solid name handling, consider asking for separate first name and last name fields instead.</p>


<h4 class="wp-block-heading" id="prevent-double-submissions">Prevent Double Submissions</h4>


<p>We can do that by disabling the Submit button on click:</p>



<pre rel="JavaScript" class="wp-block-csstricks-code-block language-javascript" data-line=""><code markup="tt">let submitting = false;
  async function handleSubmit(e) {
    e.preventDefault();
    if (submitting) return;
    submitting = true;

const button = e.target.querySelector('button[type="submit"]');
button.disabled = true;
button.textContent = 'Sending...';

try {
  await sendFormData();
    // Success handling
  } catch (error) {
    submitting = false; // Allow retry on error
    button.disabled = false;
    button.textContent = 'Send Message';
  }
}</code></pre>



<p><strong>Why this pattern works:</strong>&nbsp;Impatient users double-click. Slow networks make them click again. Without this guard, you&#8217;re creating duplicate leads that cost real money to clean up.</p>


<h4 class="wp-block-heading" id="structuring-data-for-automation">Structuring Data for Automation</h4>


<p>Instead of this:</p>



<pre rel="JavaScript" class="wp-block-csstricks-code-block language-javascript" data-line=""><code markup="tt">const formData = new FormData(form);</code></pre>



<p>Be sure to structure the data:</p>



<pre rel="JavaScript" class="wp-block-csstricks-code-block language-javascript" data-line=""><code markup="tt">const structuredData = {
  contact: {
    firstName: formData.get('name').split(' ')[0],
    lastName: formData.get('name').split(' ').slice(1).join(' '),
    email: formData.get('email'),
    phone: formData.get('phone')
  },
  inquiry: {
    message: formData.get('message'),
    source: 'website_contact_form',
    timestamp: new Date().toISOString(),
    urgency: formData.get('urgent') ? 'high' : 'normal'
  }
};</code></pre>



<p><strong>Why structured data matters:</strong>&nbsp;Tools like Zapier, Make, and even custom webhooks expect it. When you send a flat object, someone has to write logic to parse it. When you send it pre-structured, automation &#8220;just works.&#8221; This mirrors Zapier&#8217;s own recommendations for building more reliable, maintainable workflows rather than fragile single-step &#8220;simple zaps.&#8221;</p>



<p><a href="https://youtu.be/yhYOFVXr_lY?si=i-tkWdoumcjXeIHm" rel="noopener">Watch How Zapier Works</a>&nbsp;(YouTube) to see what happens after your form submits.</p>



<figure class="wp-block-image size-full"><img data-recalc-dims="1" loading="lazy" decoding="async" width="1528" height="1014" src="https://i0.wp.com/css-tricks.com/wp-content/uploads/2026/02/auto-forms-4.png?resize=1528%2C1014" alt="Comparing flat JSON data on the left with properly structured JSON data." class="wp-image-392007" srcset="https://i0.wp.com/css-tricks.com/wp-content/uploads/2026/02/auto-forms-4.png?w=1528&amp;ssl=1 1528w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2026/02/auto-forms-4.png?resize=300%2C199&amp;ssl=1 300w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2026/02/auto-forms-4.png?resize=1024%2C680&amp;ssl=1 1024w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2026/02/auto-forms-4.png?resize=768%2C510&amp;ssl=1 768w" sizes="auto, (min-width: 735px) 864px, 96vw" /></figure>


<h3 class="wp-block-heading" id="care-about-what-happens-after-submit">Care About What Happens After Submit</h3>


<p>An ideal flow would be:</p>



<ol class="wp-block-list">
<li>User submits form&nbsp;</li>



<li>Data arrives at your endpoint (or form service)&nbsp;</li>



<li>Automatically creates CRM contact&nbsp;</li>



<li>A Slack/Discord notification is sent to the sales team&nbsp;</li>



<li>A follow-up sequence is triggered&nbsp;</li>



<li>Data is logged in a spreadsheet for reporting</li>
</ol>



<p>Your choices for the front end make this possible:</p>



<ul class="wp-block-list">
<li>Consistency in formatting = Successful imports in CRM&nbsp;</li>



<li>Structured data = Can be automatically populated using automation tools&nbsp;</li>



<li>De-duplication = No messy cleanup tasks required&nbsp;</li>



<li>Validation = Less &#8220;invalid entry&#8221; errors</li>
</ul>



<p><strong>Actual experience from my own work:</strong>&nbsp;After re-structuring a lead quote form, my client&#8217;s automated quote success rate increased from 60% to 98%. The change? Instead of sending&nbsp;<code>{ "amount": "$1,500.00"}</code>, I now send&nbsp;<code>{ "amount": 1500}</code>. Their Zapier integration couldn&#8217;t parse the currency symbol.</p>



<figure class="wp-block-image size-full"><img data-recalc-dims="1" loading="lazy" decoding="async" width="1530" height="1024" src="https://i0.wp.com/css-tricks.com/wp-content/uploads/2026/02/auto-forms-5.png?resize=1530%2C1024" alt="Showing the change in rate of success after implementation automation, from 60% to 98% with an example of a parsed error and an accepted value below based on formatting money in dollars versus a raw number." class="wp-image-392009" srcset="https://i0.wp.com/css-tricks.com/wp-content/uploads/2026/02/auto-forms-5.png?w=1530&amp;ssl=1 1530w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2026/02/auto-forms-5.png?resize=300%2C201&amp;ssl=1 300w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2026/02/auto-forms-5.png?resize=1024%2C685&amp;ssl=1 1024w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2026/02/auto-forms-5.png?resize=768%2C514&amp;ssl=1 768w" sizes="auto, (min-width: 735px) 864px, 96vw" /></figure>


<h3 class="wp-block-heading" id="my-set-of-best-practices-for-form-submissions">My Set of Best Practices for Form Submissions</h3>


<p>These lessons have taught me the following about form design:</p>



<ol class="wp-block-list">
<li><strong>Ask about the workflow early.</strong>&nbsp;&#8220;What happens after someone fills this out?&#8221; needs to be the very first question to ask. This surfaces exactly what really needs to go where, what data needs to come in with a specific format, and integrations to use.&nbsp;</li>



<li><strong>Test with Real Data.</strong>&nbsp;I am also using my own input to fill out forms with extraneous spaces and strange character strings, such as mobile phone numbers and bad uppercase and lowercase letter strings. You might be surprised by the number of edge cases that can come about if you try inputting &#8220;JOHN SMITH &#8221; instead of &#8220;John Smith.&#8221;&nbsp;</li>



<li><strong>Add timestamp and source.</strong>&nbsp;It makes sense to design it into the system, even though it doesn&#8217;t necessarily seem to be necessary. Six months into the future, it&#8217;s going to be helpful to know when it was received.&nbsp;</li>



<li><strong>Make it redundant.</strong>&nbsp;Trigger an email&nbsp;<em>and</em>&nbsp;a webhook. When sending via email, it often goes silent, and you won&#8217;t realize it until someone asks,&nbsp;<em>&#8220;Did you get that message we sent you?&#8221;</em></li>



<li><strong>Over-communicate success.</strong>&nbsp;Setting the lead&#8217;s expectations is crucial to a more delightful experience.&nbsp;<em>&#8220;Your message has been sent. Sarah from sales will answer within 24 hours.&#8221;</em>&nbsp;is much better than a plain old&nbsp;<em>&#8220;Success!&#8221;</em></li>
</ol>


<h3 class="wp-block-heading" id="the-real-finish-line">The Real Finish Line</h3>


<p>This is what I now advise other developers: &#8220;Your job doesn&#8217;t stop when a form posts without errors. Your job doesn&#8217;t stop until you have confidence that your business can act upon this form submission.&#8221;</p>



<p>That means:</p>



<ul class="wp-block-list">
<li>No &#8220;copy paste&#8221; allowed&nbsp;</li>



<li>No &#8220;I&#8217;ll check my email later&#8221;&nbsp;</li>



<li>No duplicate entries to clean up&nbsp;</li>



<li>No formatting fixes needed</li>
</ul>



<p>The code itself is not all that difficult. The switch in attitude comes from understanding that a form is actually part of a larger system and not a standalone object. Once you think about forms this way, you think differently about them in terms of planning, validation, and data.</p>



<p>The next time you&#8217;re putting together a form, ask yourself:&nbsp;<q>What happens when this data goes out of my hands?</q>&nbsp;Answering that question makes you a better front-end developer.</p>



<p>The following CodePen demo is a side-by-side comparison of a standard form versus an automation-ready form. Both look identical to users, but the console output shows the dramatic difference in data quality.</p>



<div class="wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper"><iframe id="cp_embed_PwzpPWL" src="//codepen.io/anon/embed/preview/PwzpPWL?height=1000&amp;theme-id=1&amp;slug-hash=PwzpPWL&amp;default-tab=result" height="1000" scrolling="no" frameborder="0" allowfullscreen allowpaymentrequest name="CodePen Embed PwzpPWL" title="CodePen Embed PwzpPWL" class="cp_embed_iframe" style="width:100%;overflow:hidden">CodePen Embed Fallback</iframe></div>


<h3 class="wp-block-heading" id="references-further-reading">References &amp; Further Reading</h3>


<ul class="wp-block-list">
<li><a href="https://www.litmus.com/resources/state-of-email/" rel="noopener">&#8220;2025 State of Email Marketing Report&#8221;</a>&nbsp;(Litmus)&nbsp;</li>



<li>&#8220;<a href="https://blog.hubspot.com/marketing/form-design-best-practices" rel="noopener">Form Design Best Practices for Lead Capture</a>&#8221; (HubSpot)&nbsp;</li>



<li><a href="https://www.youtube.com/watch?v=h5qqmE83Tes" rel="noopener">&#8220;How to set custom error messages for your HTML forms&#8221;</a>&nbsp;(Kevin Powell, YouTube)</li>
</ul>
<hr />
<p><small><a rel="nofollow" href="https://css-tricks.com/form-automation-tips-for-happier-user-and-clients/">Form Automation Tips for Happier User and Clients</a> originally published on <a rel="nofollow" href="https://css-tricks.com">CSS-Tricks</a>, which is part of the <a href="https://try.digitalocean.com/css-tricks/?utm_medium=rss&amp;utm_source=css-tricks.com&amp;utm_campaign=family_&amp;utm_content=">DigitalOcean</a> family. You should <a href="https://css-tricks.com/newsletters/">get the newsletter</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://css-tricks.com/form-automation-tips-for-happier-user-and-clients/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">391993</post-id>	</item>
		<item>
		<title>Generative UI Notes</title>
		<link>https://css-tricks.com/generative-ui-notes/</link>
					<comments>https://css-tricks.com/generative-ui-notes/#respond</comments>
		
		<dc:creator><![CDATA[Geoff Graham]]></dc:creator>
		<pubDate>Thu, 26 Mar 2026 14:59:55 +0000</pubDate>
				<category><![CDATA[Articles]]></category>
		<category><![CDATA[artificial intelligence]]></category>
		<guid isPermaLink="false">https://css-tricks.com/?p=391438</guid>

					<description><![CDATA[<p>Looking at research and experiments that are designed to automatically generate user interfaces based on user preferences.</p>
<hr />
<p><small><a rel="nofollow" href="https://css-tricks.com/generative-ui-notes/">Generative UI Notes</a> originally published on <a rel="nofollow" href="https://css-tricks.com">CSS-Tricks</a>, which is part of the <a href="https://try.digitalocean.com/css-tricks/?utm_medium=rss&amp;utm_source=css-tricks.com&amp;utm_campaign=family_&amp;utm_content=">DigitalOcean</a> family. You should <a href="https://css-tricks.com/newsletters/">get the newsletter</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p>I&#8217;m really interested in this emerging idea that the future of web design is <strong>Generative UI Design</strong>. We see hints of this already in products, like <a href="https://www.figma.com/sites/" rel="noopener">Figma Sites</a>, that tout being able to create websites on the fly with prompts.</p>



<p>Putting aside the clear downsides of <a href="https://adrianroselli.com/2025/05/do-not-publish-your-designs-on-the-web-with-figma-sites.html" rel="noopener">shipping half-baked technology as a production-ready product</a> (which is hard to do), the angle I&#8217;m particularly looking at is research aimed at using Generative AI (or GenAI) to output personalized interfaces. It&#8217;s wild because it completely flips the way we think about UI design on its head. Rather than anticipating user needs and designing around them, GenAI sees the user needs and produces an interface custom-tailored to them. In a sense, a website becomes a snowflake where no two experiences with it are the same.</p>



<p>Again, it&#8217;s wild. I&#8217;m not here to speculate, opine, or preach on Generative UI Design (let&#8217;s call it GenUI for now). Just loose notes that I&#8217;ll update as I continue learning about it.</p>



<span id="more-391438"></span>


<h3 class="wp-block-heading" id="defining-genui">Defining GenUI</h3>


<p><a href="https://generativeui.github.io/static/pdfs/paper.pdf" rel="noopener">Google Research</a> (PDF):</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p>Generative UI is a new modality where the AI model generates not only content, but the entire user experience. This results in custom interactive experiences, including rich formatting, images, maps, audio and even simulations and games, in response to any prompt (instead of the widely adopted “walls-of-text”).</p>
</blockquote>



<p><a href="https://www.nngroup.com/articles/generative-ui/" rel="noopener">NN/Group</a>:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p>A&nbsp;<strong>generative UI</strong>&nbsp;(genUI) is a user interface that is dynamically generated in real time by artificial intelligence to provide an experience customized to fit the user’s needs and context.</p>
</blockquote>



<p><a href="https://uxdesign.cc/an-introduction-to-generative-uis-01dcf6bca808" rel="noopener">UX Collective</a>:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p>A Generative User Interface (GenUI) is an interface that adapts to, or processes, context such as inputs, instructions, behaviors, and preferences through the use of generative AI models (e.g. LLMs) in order to enhance the user experience.</p>



<p>Put simply, a GenUI interface displays different components, information, layouts, or styles, based on who’s using it and what they need at that moment.</p>
</blockquote>



<figure class="wp-block-image size-large"><img data-recalc-dims="1" loading="lazy" decoding="async" width="1024" height="485" src="https://i0.wp.com/css-tricks.com/wp-content/uploads/2026/01/12v0P-BgWWazAx_FWeoP7zg.webp?resize=1024%2C485" alt="Tree diagram showing three users, followed by inputs instructions, behaviors, and preferences, which output different webpage layouts." class="wp-image-391449" srcset="https://i0.wp.com/css-tricks.com/wp-content/uploads/2026/01/12v0P-BgWWazAx_FWeoP7zg.webp?resize=1024%2C485&amp;ssl=1 1024w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2026/01/12v0P-BgWWazAx_FWeoP7zg.webp?resize=300%2C142&amp;ssl=1 300w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2026/01/12v0P-BgWWazAx_FWeoP7zg.webp?resize=768%2C364&amp;ssl=1 768w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2026/01/12v0P-BgWWazAx_FWeoP7zg.webp?w=1300&amp;ssl=1 1300w" sizes="auto, (min-width: 735px) 864px, 96vw" /><figcaption class="wp-element-caption">Credit: <a href="Credit: UX Collective">UX Collective</a></figcaption></figure>


<h3 class="wp-block-heading" id="generative-vs-predictive-ai">Generative vs. Predictive AI</h3>


<p>It&#8217;s easy to dump &#8220;AI&#8221; into one big bucket, but it&#8217;s often distinguished as two different types: <strong>predictive</strong> and <strong>generative</strong>.</p>



<figure class="wp-block-table"><table><thead><tr><th></th><th>Predictive AI</th><th>Generative AI</th></tr></thead><tbody><tr><td><strong>Inputs</strong></td><td>Uses smaller, more targeted datasets as input data. (<a href="https://www.smashingmagazine.com/2024/01/guide-retrieval-augmented-generation-language-models/" rel="noopener">Smashing Magazine</a>)</td><td>Trained on large datasets containing millions of sample content. (<a href="https://www.congress.gov/crs_external_products/IF/PDF/IF12426/IF12426.5.pdf" rel="noopener">U.S. Congress</a>, PDF)</td></tr><tr><td><strong>Outputs</strong></td><td>Forecasts future events and outcomes. (<a href="https://www.ibm.com/think/topics/generative-ai-vs-predictive-ai-whats-the-difference" rel="noopener">IBM</a>)</td><td>New content, including audio, code, images, text, simulations, and videos. (<a href="https://www.mckinsey.com/featured-insights/mckinsey-explainers/what-is-generative-ai" rel="noopener">McKinsey</a>)</td></tr><tr><td><strong>Examples</strong></td><td>ChatGPT, Claude</td><td>Sora, Suno, Cursor</td></tr></tbody></table></figure>



<p>So, when we&#8217;re talking about GenAI, we&#8217;re talking about the ability to <em>create</em> new materials trained on existing materials. And when we&#8217;re talking specifically about GenUI, it&#8217;s about <strong>generating a user interface based on what the AI knows about the user.</strong></p>


<h3 class="wp-block-heading" id="accessibility">Accessibility</h3>


<p>And I should note that what I&#8217;m talking about here is not strictly GenUI in how we&#8217;ve defined it so far as UI output that adapts to individual user experiences, but rather &#8220;developing&#8221; generated interfaces. These so-called AI website builders do not adapt to the individual user, but it&#8217;s easy to see it heading in that direction.</p>



<p>The thing I&#8217;m most interested in — concerned with, frankly — is to what extent GenUI can <em>reliably</em> output experiences that cater to <em>all</em> users, regardless of impairment, be it aural, visual, physical, etc. There are a lot of different inputs to consider here, and <a href="https://vimeo.com/1088341217" rel="noopener">we&#8217;ve seen just how awful the early results have been</a>.</p>



<p>That last link is a big poke at Figma Sites. They&#8217;re easy to poke because they made <a href="https://www.figma.com/blog/introducing-figma-sites/" rel="noopener">the largest commercial push</a> into GenUI-based web development. To their credit (perhaps?), they received the severe pushback and decided to do something about it, <a href="https://www.figma.com/release-notes/?title=more-ways-to-share-customize-and-expand-your-reach-for-sites" rel="noopener">announcing updates</a> and <a href="https://help.figma.com/hc/en-us/articles/31242789265431-Improve-the-accessibility-of-your-site" rel="noopener">publishing a guide</a> for improving accessibility on Figma-generated sites. But even those <a href="https://adrianroselli.com/2025/05/do-not-publish-your-designs-on-the-web-with-figma-sites.html#Updates" rel="noopener">have their limitations</a> that make the effort and advice seem less useful and more about saving face.</p>



<p>Anyway. There are plenty of other players to jump into the game, notably <a href="https://wordpress.com/ai-website-builder/" rel="noopener">WordPress</a>, but also others like <a href="https://v0.app/" rel="noopener">Vercel</a>, <a href="https://www.squarespace.com/websites/ai-website-builder" rel="noopener">Squarespace</a>, <a href="https://www.wix.com/ai-website-builder" rel="noopener">Wix</a>, <a href="https://www.godaddy.com/ai-for-small-businesses" rel="noopener">GoDaddy</a>, <a href="https://lovable.dev" rel="noopener">Lovable</a>, and <a href="https://readdy.ai" rel="noopener">Reeady</a>.</p>



<p>Some folks are more optimistic than others that GenUI is not only capable of producing accessible experiences, but will replace accessibility practitioners altogether as the technology evolves. <a href="https://jakobnielsenphd.substack.com/p/accessibility-generative-ui" rel="noopener">Jakob Nielsen famously made that claim in 2024</a> which <a href="https://buttondown.com/practicaltips/archive/we-need-to-talk-about-jakob/" rel="noopener">drew</a> <a href="https://uxdesign.cc/how-the-king-of-usability-became-vulnerable-to-naive-tech-optimism-0de7836aa9a8" rel="noopener">fierce</a> <a href="https://bethdeconinck.com/2024/03/01/jakob-nielsens-bad-ideas-about-accessibility/" rel="noopener">criticism</a> <a href="https://axbom.com/nielsen-generative-ui-failure/" rel="noopener">from</a> <a href="https://cerovac.com/a11y/2024/03/accessibility-has-not-failed-it-has-not-even-started-for-real/" rel="noopener">the</a> <a href="https://ericwbailey.design/published/on-jakob-nielsen-ai-hype-and-accessibility/" rel="noopener">community</a>. Nielsen <a href="https://www.uxtigers.com/post/ai-agents" rel="noopener">walked that back a year later</a>, but not much.</p>



<p>I&#8217;m not even remotely qualified to offer best practices, opine on the future of accessibility practice, or speculate on future developments and capabilities. But as I look at <a href="https://pair.withgoogle.com/guidebook/" rel="noopener">Google&#8217;s People + AI Guidebook</a>, I see no mention at all of accessibility despite dripping with &#8220;human-centered&#8221; design principles.</p>



<p>Accessibility is a lagging consideration to the hype, at least to me. That has to change if GenUI is truly the &#8220;future&#8221; of web design and development.</p>


<h3 class="wp-block-heading" id="examples-amp-resources">Examples &amp; Resources</h3>


<p><a href="https://generativeui.github.io" rel="noopener">Google has a repository of examples</a> showing how user input can be used to render a variety of interfaces. Going a step further is Google&#8217;s <a href="https://labs.google/projectgenie" rel="noopener">Project Genie</a> that bills itself as creating &#8220;interactive worlds&#8221; that are &#8220;generated in real-time.&#8221; I couldn&#8217;t get an invite to try it out, but maybe you can.</p>



<p>In addition to that, <a href="https://docs.flutter.dev/ai/genui/get-started" rel="noopener">Google has a GenUI SDK</a> designed to integrate into Flutter apps. So, yeah. Connect to your LLM provider and let it rip to create adaptive interfaces.</p>



<p><a href="https://www.thesys.dev/" rel="noopener">Thesys</a> is another one in the adaptive GenUI space. <a href="https://github.com/CopilotKit/generative-ui-playground" rel="noopener">Copilot</a>, too.</p>


<h3 class="wp-block-heading" id="references">References</h3>


<ul class="wp-block-list">
<li><a href="https://www.figma.com/sites/" rel="noopener">Figma Sites</a></li>



<li><a href="https://adrianroselli.com/2025/05/do-not-publish-your-designs-on-the-web-with-figma-sites.html#Updates" rel="noopener">&#8220;Do Not Publish Your Designs on the Web with Figma Sites…&#8221;</a> (Adrian Roselli)</li>



<li><a href="https://generativeui.github.io/static/pdfs/paper.pdf" rel="noopener">&#8220;Generative UI: LLMs are Effective UI Generators&#8221;</a> (Google Research, PDF)</li>



<li><a href="https://www.nngroup.com/articles/generative-ui/" rel="noopener">&#8220;Generative UI and Outcome-Oriented Design&#8221;</a> (NN/Group)</li>



<li><a href="https://uxdesign.cc/an-introduction-to-generative-uis-01dcf6bca808" rel="noopener">&#8220;An introduction to Generative UIs&#8221;</a> (UX Collective)</li>



<li><a href="https://www.smashingmagazine.com/2024/01/guide-retrieval-augmented-generation-language-models/" rel="noopener">&#8220;A Simple Guide To Retrieval Augmented Generation Language Models&#8221;</a> (Joas Pambou)</li>



<li><a href="https://www.congress.gov/crs_external_products/IF/PDF/IF12426/IF12426.5.pdf" rel="noopener">&#8220;Generative Artificial Intelligence: Overview, Issues, and Considerations for Congress&#8221;</a> (U.S. Congress, PDF)</li>



<li><a href="https://www.ibm.com/think/topics/generative-ai-vs-predictive-ai-whats-the-difference" rel="noopener">&#8220;Generative AI vs. predictive AI: What’s the difference?&#8221;</a> (IBM)</li>



<li><a href="https://www.mckinsey.com/featured-insights/mckinsey-explainers/what-is-generative-ai" rel="noopener">&#8220;What is generative AI?&#8221;</a> (McKinsey &amp; Company)</li>



<li><a href="https://vimeo.com/1088341217" rel="noopener">&#8220;Introducing: Webbed Sites&#8221;</a> (Heydon Pickering, Video)</li>



<li><a href="https://www.figma.com/blog/introducing-figma-sites/" rel="noopener">&#8220;Publish your designs on the web with Figma Sites&#8221;</a> (Figma)</li>



<li><a href="https://www.figma.com/release-notes/?title=more-ways-to-share-customize-and-expand-your-reach-for-sites" rel="noopener">&#8220;Figma Sites on Starter and Education, with more ways to share, customize, and expand your reach for Sites&#8221;</a> (Figma)</li>



<li><a href="https://help.figma.com/hc/en-us/articles/31242789265431-Improve-the-accessibility-of-your-site" rel="noopener">&#8220;Improve the accessibility of your site&#8221;</a> (Figma Learn)</li>



<li>&#8220;<a href="https://jakobnielsenphd.substack.com/p/accessibility-generative-ui" rel="noopener">Accessibility Has Failed: Try Generative UI = Individualized UX&#8221;</a> (Jakob Nielsen)</li>



<li><a href="https://www.uxtigers.com/post/ai-agents" rel="noopener">&#8220;Hello AI Agents: Goodbye UI Design, RIP Accessibility&#8221;</a> (Jakob Nielsen)</li>



<li><a href="https://pair.withgoogle.com/guidebook/" rel="noopener">&#8220;The People + AI Guidebook&#8221;</a> (Google)</li>



<li><a href="https://labs.google/projectgenie" rel="noopener">Project Genie</a> (Google Labs)\</li>



<li><a href="https://docs.flutter.dev/ai/genui/get-started" rel="noopener">&#8220;Get started with the GenUI SDK for Flutter&#8221;</a> (Flutter Docs)</li>
</ul>



<p></p>
<hr />
<p><small><a rel="nofollow" href="https://css-tricks.com/generative-ui-notes/">Generative UI Notes</a> originally published on <a rel="nofollow" href="https://css-tricks.com">CSS-Tricks</a>, which is part of the <a href="https://try.digitalocean.com/css-tricks/?utm_medium=rss&amp;utm_source=css-tricks.com&amp;utm_campaign=family_&amp;utm_content=">DigitalOcean</a> family. You should <a href="https://css-tricks.com/newsletters/">get the newsletter</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://css-tricks.com/generative-ui-notes/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">391438</post-id>	</item>
		<item>
		<title>Experimenting With Scroll-Driven corner-shape Animations</title>
		<link>https://css-tricks.com/experimenting-with-scroll-driven-corner-shape-animations/</link>
					<comments>https://css-tricks.com/experimenting-with-scroll-driven-corner-shape-animations/#comments</comments>
		
		<dc:creator><![CDATA[Daniel Schwarz]]></dc:creator>
		<pubDate>Mon, 23 Mar 2026 13:51:15 +0000</pubDate>
				<category><![CDATA[Articles]]></category>
		<category><![CDATA[animation]]></category>
		<category><![CDATA[corner-shape]]></category>
		<category><![CDATA[Scroll Driven Animation]]></category>
		<guid isPermaLink="false">https://css-tricks.com/?p=392503</guid>

					<description><![CDATA[<p>The new CSS <code>corner-shape()</code> property is mathematical, so it’s easily animated. Author Daniel Schwarz pokes at animating the property for interesting UI effects.</p>
<hr />
<p><small><a rel="nofollow" href="https://css-tricks.com/experimenting-with-scroll-driven-corner-shape-animations/">Experimenting With Scroll-Driven corner-shape Animations</a> originally published on <a rel="nofollow" href="https://css-tricks.com">CSS-Tricks</a>, which is part of the <a href="https://try.digitalocean.com/css-tricks/?utm_medium=rss&amp;utm_source=css-tricks.com&amp;utm_campaign=family_&amp;utm_content=">DigitalOcean</a> family. You should <a href="https://css-tricks.com/newsletters/">get the newsletter</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p>Over the last few years, there’s been a lot of talk about and experimentation with <a href="https://css-tricks.com/unleash-the-power-of-scroll-driven-animations/">scroll-driven animations</a>. It’s a very shiny feature for sure, and as soon as it’s supported in Firefox (without a flag), it’ll be baseline. It’s part of <a href="https://css-tricks.com/interop-2026/">Interop 2026</a>, so that should be relatively soon. Essentially, scroll-driven animations tie an <em>animation timeline’s position</em> to a <em>scroll position</em>, so if you were 50% scrolled then you’d also be 50% into the animation, and they’re surprisingly easy to set up too.</p>



<p>I’ve been seeing significant interest in the new CSS <code><a href="https://css-tricks.com/almanac/properties/c/corner-shape/">corner-shape</a></code> property as well, even though it only works in Chrome for now. This enables us to create corners that aren’t as rounded, or aren’t even rounded at all, allowing for some <a href="https://css-tricks.com/what-can-we-actually-do-with-corner-shape/">intriguing shapes</a> that take little-to-no effort to create. What’s even more intriguing though is that <code>corner-shape</code> is mathematical, so it’s easily animated.</p>



<p>Hence, say hello to scroll-driven <code>corner-shape</code> animations (requires Chrome 139+ to work fully):</p>



<span id="more-392503"></span>



<div class="wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper"><iframe id="cp_embed_wBWXdKq" src="//codepen.io/anon/embed/preview/wBWXdKq?height=450&amp;theme-id=1&amp;slug-hash=wBWXdKq&amp;default-tab=result" height="450" scrolling="no" frameborder="0" allowfullscreen allowpaymentrequest name="CodePen Embed wBWXdKq" title="CodePen Embed wBWXdKq" class="cp_embed_iframe" style="width:100%;overflow:hidden">CodePen Embed Fallback</iframe></div>



<figure class="wp-block-video"><video height="1440" style="aspect-ratio: 2292 / 1440;" width="2292" controls src="https://css-tricks.com/wp-content/uploads/2026/02/corner-shape-amimation-1.mp4" playsinline></video></figure>


<h3 class="wp-block-heading" id="-corner-shape-in-a-nutshell"><code>corner-shape</code> in a nutshell</h3>


<p>Real quick — the different values for <code>corner-shape</code>:</p>



<figure class="wp-block-table"><table class="has-fixed-layout"><thead><tr><th><code>corner-shape</code> <strong>keyword</strong></th><th><code>superellipse()</code> <strong>equivalent</strong></th></tr></thead><tbody><tr><td><code>square</code></td><td><code>superellipse(infinity)</code></td></tr><tr><td><code>squircle</code></td><td><code>superellipse(2)</code></td></tr><tr><td><code>round</code></td><td><code>superellipse(1)</code></td></tr><tr><td><code>bevel</code></td><td><code>superellipse(0)</code></td></tr><tr><td><code>scoop</code></td><td><code>superellipse(-1)</code></td></tr><tr><td><code>notch</code></td><td><code>superellipse(-infinity)</code></td></tr></tbody></table></figure>



<div class="wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper"><iframe id="cp_embed_EagYxLj" src="//codepen.io/anon/embed/preview/EagYxLj?height=450&amp;theme-id=1&amp;slug-hash=EagYxLj&amp;default-tab=result" height="450" scrolling="no" frameborder="0" allowfullscreen allowpaymentrequest name="CodePen Embed EagYxLj" title="CodePen Embed EagYxLj" class="cp_embed_iframe" style="width:100%;overflow:hidden">CodePen Embed Fallback</iframe></div>



<figure class="wp-block-image size-full"><img data-recalc-dims="1" loading="lazy" decoding="async" width="2474" height="1500" src="https://i0.wp.com/css-tricks.com/wp-content/uploads/2026/02/s_4A82C1A16CF8BD3E78A39757A7700A0B223569FCA13B21C3FBDA9426FB071038_1771786670629_1.5.png?resize=2474%2C1500&#038;ssl=1" alt="Showing the same magenta-colored rectangle with the six difference CSS corner-shape property values applied to it in a three-by-three grid." class="wp-image-392511" srcset="https://i0.wp.com/css-tricks.com/wp-content/uploads/2026/02/s_4A82C1A16CF8BD3E78A39757A7700A0B223569FCA13B21C3FBDA9426FB071038_1771786670629_1.5.png?w=2474&amp;ssl=1 2474w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2026/02/s_4A82C1A16CF8BD3E78A39757A7700A0B223569FCA13B21C3FBDA9426FB071038_1771786670629_1.5.png?resize=300%2C182&amp;ssl=1 300w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2026/02/s_4A82C1A16CF8BD3E78A39757A7700A0B223569FCA13B21C3FBDA9426FB071038_1771786670629_1.5.png?resize=1024%2C621&amp;ssl=1 1024w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2026/02/s_4A82C1A16CF8BD3E78A39757A7700A0B223569FCA13B21C3FBDA9426FB071038_1771786670629_1.5.png?resize=768%2C466&amp;ssl=1 768w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2026/02/s_4A82C1A16CF8BD3E78A39757A7700A0B223569FCA13B21C3FBDA9426FB071038_1771786670629_1.5.png?resize=1536%2C931&amp;ssl=1 1536w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2026/02/s_4A82C1A16CF8BD3E78A39757A7700A0B223569FCA13B21C3FBDA9426FB071038_1771786670629_1.5.png?resize=2048%2C1242&amp;ssl=1 2048w" sizes="auto, (min-width: 735px) 864px, 96vw" /></figure>



<p>But what’s this <a href="https://css-tricks.com/almanac/functions/s/superellipse/"><code>superellipse()</code> function</a> all about? Well, basically, these keyword values are the result of this function. For example, <code>superellipse(2)</code> creates corners that aren’t quite squared but aren’t quite rounded either (the &#8220;<code>squircle</code>”). Whether you use a keyword or the <code>superellipse()</code> function directly, a mathematical equation is used either way, which is what makes it animatable. With that in mind, let’s dive into that demo above.</p>


<h3 class="wp-block-heading" id="animating-corner-shape-">Animating <code>corner-shape</code></h3>


<p>The demo isn’t too complicated, so I’ll start off by dropping the CSS here, and then I’ll explain how it works line-by-line:</p>



<pre rel="CSS" class="wp-block-csstricks-code-block language-css" data-line=""><code markup="tt">@keyframes bend-it-like-beckham {
  from {
    corner-shape: superellipse(notch);
    /* or */
    corner-shape: superellipse(-infinity);
  }

  to {
    corner-shape: superellipse(square);
    /* or */
    corner-shape: superellipse(infinity);
  }
}

body::before {
  /* Fill viewport */
  content: "";
  position: fixed;
  inset: 0;

  /* Enable click-through */
  pointer-events: none;

  /* Invert underlying layer */
  mix-blend-mode: difference;
  background: white;

  /* Don’t forget this! */
  border-bottom-left-radius: 100%;

  /* Animation settings */
  animation: bend-it-like-beckham;
  animation-timeline: scroll();
}

/* Added to cards */
.no-filter {
  isolation: isolate;
}</code></pre>



<div class="wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper"><iframe id="cp_embed_NPrVGOW" src="//codepen.io/anon/embed/preview/NPrVGOW?height=450&amp;theme-id=1&amp;slug-hash=NPrVGOW&amp;default-tab=result" height="450" scrolling="no" frameborder="0" allowfullscreen allowpaymentrequest name="CodePen Embed NPrVGOW" title="CodePen Embed NPrVGOW" class="cp_embed_iframe" style="width:100%;overflow:hidden">CodePen Embed Fallback</iframe></div>



<p>In the code snippet above, <code>body::before</code> combined with <code>content: ""</code> creates a pseudo-element of the <code>&lt;body&gt;</code> with no content that is then fixed to every edge of the viewport. Also, since this animating shape will be on top of the content, <code>pointer-events: none</code> ensures that we can still interact with said content.</p>



<p>For the shape’s color I’m using <code>mix-blend-mode: difference</code> with <code>background: white</code>, which inverts the underlying layer, a trendy effect that <strong>to some degree only</strong> maintains the same level of color contrast. You won’t want to apply this effect to everything, so here’s a utility class to exclude the effect as needed:</p>



<pre rel="CSS" class="wp-block-csstricks-code-block language-css" data-line=""><code markup="tt">/* Added to cards */
.no-filter {
  isolation: isolate;
}</code></pre>



<p>A comparison:</p>



<figure class="wp-block-image size-large"><img data-recalc-dims="1" loading="lazy" decoding="async" width="1024" height="355" src="https://i0.wp.com/css-tricks.com/wp-content/uploads/2026/02/s_4A82C1A16CF8BD3E78A39757A7700A0B223569FCA13B21C3FBDA9426FB071038_1771785916661_2.png?resize=1024%2C355" alt="Side-by-side comparison showing blend mode applied on the left and excluded from cards placed in the layout on the right, preventing the card backgrounds from changing." class="wp-image-392508" srcset="https://i0.wp.com/css-tricks.com/wp-content/uploads/2026/02/s_4A82C1A16CF8BD3E78A39757A7700A0B223569FCA13B21C3FBDA9426FB071038_1771785916661_2-scaled.png?resize=1024%2C355&amp;ssl=1 1024w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2026/02/s_4A82C1A16CF8BD3E78A39757A7700A0B223569FCA13B21C3FBDA9426FB071038_1771785916661_2-scaled.png?resize=300%2C104&amp;ssl=1 300w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2026/02/s_4A82C1A16CF8BD3E78A39757A7700A0B223569FCA13B21C3FBDA9426FB071038_1771785916661_2-scaled.png?resize=768%2C266&amp;ssl=1 768w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2026/02/s_4A82C1A16CF8BD3E78A39757A7700A0B223569FCA13B21C3FBDA9426FB071038_1771785916661_2-scaled.png?resize=1536%2C532&amp;ssl=1 1536w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2026/02/s_4A82C1A16CF8BD3E78A39757A7700A0B223569FCA13B21C3FBDA9426FB071038_1771785916661_2-scaled.png?resize=2048%2C709&amp;ssl=1 2048w" sizes="auto, (min-width: 735px) 864px, 96vw" /><figcaption class="wp-element-caption"><strong>Left:</strong> Full application of blend mode. <strong>Right:</strong> Blend mode excluded from cards.</figcaption></figure>



<p>You’ll need to combine <code>corner-shape</code> with <code>border-radius</code>, which uses <code>corner-shape: round</code> under the hood by default. Yes, that’s right, <code>border-radius</code> doesn’t actually round corners — <code>corner-shape: round</code> does that under the hood. Rather, <code>border-radius</code> handles the x-axis and y-axis coordinates to draw from:</p>



<pre rel="CSS" class="wp-block-csstricks-code-block language-css" data-line=""><code markup="tt">/* Syntax */
border-bottom-left-radius: &lt;x-axis-coord> &lt;y-axis-coord>;

/* Usage */
border-bottom-left-radius: 50% 50%;
/* Or */
border-bottom-left-radius: 50%;</code></pre>



<figure class="wp-block-image size-full"><img data-recalc-dims="1" loading="lazy" decoding="async" width="2560" height="1665" src="https://i0.wp.com/css-tricks.com/wp-content/uploads/2026/02/s_4A82C1A16CF8BD3E78A39757A7700A0B223569FCA13B21C3FBDA9426FB071038_1771786781751_3-scaled.png?resize=2560%2C1665" alt="Diagramming the shape showing border-radius applied to the bottom-left corner. The rounded corner is 50% on the y-axis and 50% on the x-axis." class="wp-image-392507" srcset="https://i0.wp.com/css-tricks.com/wp-content/uploads/2026/02/s_4A82C1A16CF8BD3E78A39757A7700A0B223569FCA13B21C3FBDA9426FB071038_1771786781751_3-scaled.png?w=2560&amp;ssl=1 2560w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2026/02/s_4A82C1A16CF8BD3E78A39757A7700A0B223569FCA13B21C3FBDA9426FB071038_1771786781751_3-scaled.png?resize=300%2C195&amp;ssl=1 300w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2026/02/s_4A82C1A16CF8BD3E78A39757A7700A0B223569FCA13B21C3FBDA9426FB071038_1771786781751_3-scaled.png?resize=1024%2C666&amp;ssl=1 1024w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2026/02/s_4A82C1A16CF8BD3E78A39757A7700A0B223569FCA13B21C3FBDA9426FB071038_1771786781751_3-scaled.png?resize=768%2C499&amp;ssl=1 768w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2026/02/s_4A82C1A16CF8BD3E78A39757A7700A0B223569FCA13B21C3FBDA9426FB071038_1771786781751_3-scaled.png?resize=1536%2C999&amp;ssl=1 1536w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2026/02/s_4A82C1A16CF8BD3E78A39757A7700A0B223569FCA13B21C3FBDA9426FB071038_1771786781751_3-scaled.png?resize=2048%2C1332&amp;ssl=1 2048w" sizes="auto, (min-width: 735px) 864px, 96vw" /></figure>



<p>In our case, we’re using <code>border-bottom-left-radius: 100%</code> to slide those coordinates to the opposite end of their respective axes. However, we’ll be overwriting the implied <code>corner-shape: round</code> in our <code>@keyframe</code> animation, so we refer to that with <code>animation: bend-it-like-beckham</code>. There’s no need to specify a duration because it’s a scroll-driven animation, as defined by <code>animation-timeline: scroll()</code>.</p>



<p>In the <code>@keyframe</code> animation, we’re animating from <code>corner-shape: superellipse(notch)</code>, which is like an inset square. This is equivalent to <code>corner-shape: superellipse(-infinity)</code>, so it’s not actually squared but it’s so aggressively sharp that it looks squared. This animates to <code>corner-shape: superellipse(square)</code> (an <em>outset</em> square), or <code>corner-shape: superellipse(infinity)</code>.</p>


<h3 class="wp-block-heading" id="animating-corner-shape-revisited-">Animating <code>corner-shape</code>&#8230; <em>revisited</em></h3>


<p>The demo above is actually a bit different to the one that I originally shared in the intro. It has one minor flaw, and I’ll show you how to fix it, but more importantly, you’ll learn more about an intricate detail of <code>corner-shape</code>.</p>



<p>The flaw: at the beginning and end of the animation, the curvature looks quite harsh because we’re animating from <code>notch</code> and <code>square</code>, right? It also looks like the shape is being sucked into the corners. Finally, the shape being stuck to the sides of the viewport makes the whole thing feel too contained.</p>



<p>The solution is simple:</p>



<pre rel="CSS" class="wp-block-csstricks-code-block language-css" data-line=""><code markup="tt">/* Change this... */
inset: 0;

/* ...to this */
inset: -1rem;</code></pre>



<p>This stretches the shape <em>beyond</em> the viewport, and even though this makes the animation appear to start late and finish early, we can fix that by not animating from/to <code>-infinity</code>/<code>infinity</code>:</p>



<pre rel="CSS" class="wp-block-csstricks-code-block language-css" data-line=""><code markup="tt">@keyframes bend-it-like-beckham {
  from {
    corner-shape: superellipse(-6);
  }

  to {
    corner-shape: superellipse(6);
  }
}</code></pre>



<p>Sure, this means that part of the shape is always visible, but we can fiddle with the <code>superellipse()</code> value to ensure that it stays outside of the viewport. Here’s a side-by-side comparison:</p>



<figure class="wp-block-image size-full"><img data-recalc-dims="1" loading="lazy" decoding="async" width="2560" height="862" src="https://i0.wp.com/css-tricks.com/wp-content/uploads/2026/02/s_4A82C1A16CF8BD3E78A39757A7700A0B223569FCA13B21C3FBDA9426FB071038_1771786883121_4-scaled.png?resize=2560%2C862" alt="Two versions of the same magenta colored rectangle side-by-side. The left shows the top-right corner more rounded than the right which is equally rounded." class="wp-image-392505" srcset="https://i0.wp.com/css-tricks.com/wp-content/uploads/2026/02/s_4A82C1A16CF8BD3E78A39757A7700A0B223569FCA13B21C3FBDA9426FB071038_1771786883121_4-scaled.png?w=2560&amp;ssl=1 2560w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2026/02/s_4A82C1A16CF8BD3E78A39757A7700A0B223569FCA13B21C3FBDA9426FB071038_1771786883121_4-scaled.png?resize=300%2C101&amp;ssl=1 300w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2026/02/s_4A82C1A16CF8BD3E78A39757A7700A0B223569FCA13B21C3FBDA9426FB071038_1771786883121_4-scaled.png?resize=1024%2C345&amp;ssl=1 1024w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2026/02/s_4A82C1A16CF8BD3E78A39757A7700A0B223569FCA13B21C3FBDA9426FB071038_1771786883121_4-scaled.png?resize=768%2C259&amp;ssl=1 768w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2026/02/s_4A82C1A16CF8BD3E78A39757A7700A0B223569FCA13B21C3FBDA9426FB071038_1771786883121_4-scaled.png?resize=1536%2C517&amp;ssl=1 1536w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2026/02/s_4A82C1A16CF8BD3E78A39757A7700A0B223569FCA13B21C3FBDA9426FB071038_1771786883121_4-scaled.png?resize=2048%2C689&amp;ssl=1 2048w" sizes="auto, (min-width: 735px) 864px, 96vw" /></figure>



<p>And the original demo (which is where we’re at now):</p>



<div class="wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper"><iframe id="cp_embed_wBWXdKq" src="//codepen.io/anon/embed/preview/wBWXdKq?height=450&amp;theme-id=1&amp;slug-hash=wBWXdKq&amp;default-tab=result" height="450" scrolling="no" frameborder="0" allowfullscreen allowpaymentrequest name="CodePen Embed wBWXdKq" title="CodePen Embed wBWXdKq" class="cp_embed_iframe" style="width:100%;overflow:hidden">CodePen Embed Fallback</iframe></div>


<h3 class="wp-block-heading" id="adding-more-scroll-features">Adding more scroll features</h3>


<p>Scroll-driven animations work very well with other scroll features, including <a href="https://css-tricks.com/practical-css-scroll-snapping/">scroll snapping</a>, <a href="https://css-tricks.com/almanac/pseudo-selectors/s/scroll-button/">scroll buttons</a>, <a href="https://css-tricks.com/almanac/pseudo-selectors/s/scroll-marker/">scroll markers</a>, simple <a href="https://developer.mozilla.org/en-US/docs/Web/URI/Reference/Fragment/Text_fragments" rel="noopener">text fragments</a>, and simple JavaScript methods such as <code>scrollTo()</code>/<code>scroll()</code>, <code>scrollBy()</code>, and <code>scrollIntoView()</code>.</p>



<p>For example, we only have to add the following CSS snippet to introduce scroll snapping that works right alongside the scroll-driven <code>corner-shape</code> animation that we’ve already set up:</p>



<pre rel="CSS" class="wp-block-csstricks-code-block language-css" data-line=""><code markup="tt">:root {
  /* Snap vertically */
  scroll-snap-type: y;

  section {
    /* Snap to section start */
    scroll-snap-align: start;
  }
}</code></pre>



<div class="wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper"><iframe id="cp_embed_pvbqvqz" src="//codepen.io/anon/embed/preview/pvbqvqz?height=450&amp;theme-id=1&amp;slug-hash=pvbqvqz&amp;default-tab=result" height="450" scrolling="no" frameborder="0" allowfullscreen allowpaymentrequest name="CodePen Embed pvbqvqz" title="CodePen Embed pvbqvqz" class="cp_embed_iframe" style="width:100%;overflow:hidden">CodePen Embed Fallback</iframe></div>



<figure class="wp-block-video"><video height="1844" style="aspect-ratio: 2940 / 1844;" width="2940" controls src="https://css-tricks.com/wp-content/uploads/2026/02/corner-shape-animation-5.mp4" playsinline></video></figure>


<h3 class="wp-block-heading" id="-masking-with-corner-shape-">“Masking” with <code>corner-shape</code></h3>


<p>In the example below, I’ve essentially created a border around the viewport and then a notched shape (<code>corner-shape: notch</code>) on top of it that’s the same color as the background (<code>background: inherit</code>). This shape completely covers the border at first, but then animates to reveal it (or in this case, the four corners of it):</p>



<div class="wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper"><iframe id="cp_embed_WbxqEMx" src="//codepen.io/anon/embed/preview/WbxqEMx?height=450&amp;theme-id=1&amp;slug-hash=WbxqEMx&amp;default-tab=result" height="450" scrolling="no" frameborder="0" allowfullscreen allowpaymentrequest name="CodePen Embed WbxqEMx" title="CodePen Embed WbxqEMx" class="cp_embed_iframe" style="width:100%;overflow:hidden">CodePen Embed Fallback</iframe></div>



<figure class="wp-block-video"><video height="1844" style="aspect-ratio: 2940 / 1844;" width="2940" controls src="https://css-tricks.com/wp-content/uploads/2026/02/corner-shape-animation-6.mp4" playsinline></video></figure>



<p>If I make the shape a bit more visible, it’s easier to see what’s happening here, which is that I’m rotating this shape as well (<code>rotate: 5deg</code>), making the shape even more interesting.</p>



<figure class="wp-block-image size-full"><img data-recalc-dims="1" loading="lazy" decoding="async" width="2560" height="1607" src="https://i0.wp.com/css-tricks.com/wp-content/uploads/2026/02/s_4A82C1A16CF8BD3E78A39757A7700A0B223569FCA13B21C3FBDA9426FB071038_1771787649530_7-scaled.png?resize=2560%2C1607" alt="A large gray cross shape overlaid on top of a pinkish background. The shape is rotated slightly to the right and extends beyond the boundaries of the background.," class="wp-image-392504" srcset="https://i0.wp.com/css-tricks.com/wp-content/uploads/2026/02/s_4A82C1A16CF8BD3E78A39757A7700A0B223569FCA13B21C3FBDA9426FB071038_1771787649530_7-scaled.png?w=2560&amp;ssl=1 2560w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2026/02/s_4A82C1A16CF8BD3E78A39757A7700A0B223569FCA13B21C3FBDA9426FB071038_1771787649530_7-scaled.png?resize=300%2C188&amp;ssl=1 300w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2026/02/s_4A82C1A16CF8BD3E78A39757A7700A0B223569FCA13B21C3FBDA9426FB071038_1771787649530_7-scaled.png?resize=1024%2C643&amp;ssl=1 1024w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2026/02/s_4A82C1A16CF8BD3E78A39757A7700A0B223569FCA13B21C3FBDA9426FB071038_1771787649530_7-scaled.png?resize=768%2C482&amp;ssl=1 768w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2026/02/s_4A82C1A16CF8BD3E78A39757A7700A0B223569FCA13B21C3FBDA9426FB071038_1771787649530_7-scaled.png?resize=1536%2C964&amp;ssl=1 1536w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2026/02/s_4A82C1A16CF8BD3E78A39757A7700A0B223569FCA13B21C3FBDA9426FB071038_1771787649530_7-scaled.png?resize=2048%2C1286&amp;ssl=1 2048w" sizes="auto, (min-width: 735px) 864px, 96vw" /></figure>



<p>This time around we’re animating <code>border-radius</code>, not <code>corner-shape</code>. When we animate to <code>border-radius: 20vw / 20vh</code>, <code>20vw</code> and <code>20vh</code> refers to the x-axis and y-axis of each corner, respectively, meaning that 20% of the border is revealed as we scroll.</p>



<p>The only other thing worth mentioning here is that we need to mess around with <code>z-index</code> to ensure that the content is higher up in the stacking context than the border and shape. Other than that, this example simply demonstrates another fun way to use <code>corner-shape</code>:</p>



<pre rel="CSS" class="wp-block-csstricks-code-block language-css" data-line=""><code markup="tt">@keyframes tech-corners {
  from {
    border-radius: 0;
  }

  to {
    border-radius: 20vw / 20vh;
  }
}

/* Border */
body::before {
  /* Fill (- 1rem) */
  content: "";
  position: fixed;
  inset: 1rem;
  border: 1rem solid black;
}

/* Notch */
body::after {
  /* Fill (+ 3rem) */
  content: "";
  position: fixed;
  inset: -3rem;

  /* Rotated shape */
  background: inherit;
  rotate: 5deg;
  corner-shape: notch;

  /* Animation settings */
  animation: tech-corners;
  animation-timeline: scroll();
}

main {
  /* Stacking fix */
  position: relative;
  z-index: 1;
}</code></pre>


<h3 class="wp-block-heading" id="animating-multiple-corner-shape-elements">Animating multiple <code>corner-shape</code> elements</h3>


<p>In this example, we have multiple nested diamond shapes thanks to <code>corner-shape: bevel</code>, all leveraging the same scroll-driven animation where the diamonds increase in size, using <code>padding</code>:</p>



<div class="wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper"><iframe id="cp_embed_gbMVapx" src="//codepen.io/anon/embed/preview/gbMVapx?height=450&amp;theme-id=1&amp;slug-hash=gbMVapx&amp;default-tab=result" height="450" scrolling="no" frameborder="0" allowfullscreen allowpaymentrequest name="CodePen Embed gbMVapx" title="CodePen Embed gbMVapx" class="cp_embed_iframe" style="width:100%;overflow:hidden">CodePen Embed Fallback</iframe></div>



<figure class="wp-block-video"><video height="1844" style="aspect-ratio: 2940 / 1844;" width="2940" controls src="https://css-tricks.com/wp-content/uploads/2026/02/corner-shape-animation-8.mp4" playsinline></video></figure>



<pre rel="HTML" class="wp-block-csstricks-code-block language-markup" data-line=""><code markup="tt">&lt;div id="diamonds">
  &lt;div>
    &lt;div>
      &lt;div>
        &lt;div>
          &lt;div>
            &lt;div>
              &lt;div>
                &lt;div>
                  &lt;div>
                    &lt;div>&lt;/div>
                  &lt;/div>
                &lt;/div>
              &lt;/div>
            &lt;/div>
          &lt;/div>
        &lt;/div>
      &lt;/div>
    &lt;/div>
  &lt;/div>
&lt;/div>

&lt;main>
  &lt;!-- Content -->
&lt;/main></code></pre>



<pre rel="CSS" class="wp-block-csstricks-code-block language-css" data-line=""><code markup="tt">@keyframes diamonds-are-forever {
  from {
    padding: 7rem;
  }

  to {
    padding: 14rem;
  }
}

#diamonds {
  /* Center them */
  position: fixed;
  inset: 50% auto auto 50%;
  translate: -50% -50%;

  /* #diamonds, the &lt;div>s within */
  &amp;, div {
    corner-shape: bevel;
    border-radius: 100%;
    animation: diamonds-are-forever;
    animation-timeline: scroll();
    border: 0.0625rem solid #00000030;
  }
}

main {
  /* Stacking fix */
  position: relative;
  z-index: 1;
}</code></pre>


<h3 class="wp-block-heading" id="that-s-a-wrap">That’s a wrap</h3>


<p>We just explored animating from one custom <code>superellipse()</code> value to another, using <code>corner-shape</code> as a mask to create new shapes (again, while animating it), and animating multiple <code>corner-shape</code> elements at once. There are so many ways to animate <code>corner-shape</code> other than from one keyword to another, and if we make them scroll-driven animations, we can create some really interesting effects (although, they’d also look awesome if they were static).</p>
<hr />
<p><small><a rel="nofollow" href="https://css-tricks.com/experimenting-with-scroll-driven-corner-shape-animations/">Experimenting With Scroll-Driven corner-shape Animations</a> originally published on <a rel="nofollow" href="https://css-tricks.com">CSS-Tricks</a>, which is part of the <a href="https://try.digitalocean.com/css-tricks/?utm_medium=rss&amp;utm_source=css-tricks.com&amp;utm_campaign=family_&amp;utm_content=">DigitalOcean</a> family. You should <a href="https://css-tricks.com/newsletters/">get the newsletter</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://css-tricks.com/experimenting-with-scroll-driven-corner-shape-animations/feed/</wfw:commentRss>
			<slash:comments>1</slash:comments>
		
		<enclosure url="https://css-tricks.com/wp-content/uploads/2026/02/corner-shape-amimation-1.mp4" length="812988" type="video/mp4" />
<enclosure url="https://css-tricks.com/wp-content/uploads/2026/02/corner-shape-animation-5.mp4" length="1511047" type="video/mp4" />
<enclosure url="https://css-tricks.com/wp-content/uploads/2026/02/corner-shape-animation-6.mp4" length="1700266" type="video/mp4" />
<enclosure url="https://css-tricks.com/wp-content/uploads/2026/02/corner-shape-animation-8.mp4" length="1478908" type="video/mp4" />

		<post-id xmlns="com-wordpress:feed-additions:1">392503</post-id>	</item>
		<item>
		<title>JavaScript for Everyone: Destructuring</title>
		<link>https://css-tricks.com/javascript-for-everyone-destructuring/</link>
					<comments>https://css-tricks.com/javascript-for-everyone-destructuring/#comments</comments>
		
		<dc:creator><![CDATA[Mat Marquis]]></dc:creator>
		<pubDate>Thu, 19 Mar 2026 13:06:30 +0000</pubDate>
				<category><![CDATA[Articles]]></category>
		<category><![CDATA[destructuring]]></category>
		<category><![CDATA[JavaScript]]></category>
		<guid isPermaLink="false">https://css-tricks.com/?p=392714</guid>

					<description><![CDATA[<p>Mat Marquis and Andy Bell have released <i>JavaScript for Everyone</i>, an online course offered exclusively at Piccalilli. This post is an excerpt from the course taken specifically from a chapter all about JavaScript destructuring. </p>
<hr />
<p><small><a rel="nofollow" href="https://css-tricks.com/javascript-for-everyone-destructuring/">JavaScript for Everyone: Destructuring</a> originally published on <a rel="nofollow" href="https://css-tricks.com">CSS-Tricks</a>, which is part of the <a href="https://try.digitalocean.com/css-tricks/?utm_medium=rss&amp;utm_source=css-tricks.com&amp;utm_campaign=family_&amp;utm_content=">DigitalOcean</a> family. You should <a href="https://css-tricks.com/newsletters/">get the newsletter</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p class="is-style-explanation"><strong>Editor’s note:</strong> Mat Marquis and Andy Bell have released <a href="https://piccalil.li/javascript-for-everyone/lessons" rel="noopener"><em>JavaScript for Everyone</em></a>, an online course offered exclusively at Piccalilli. This post is an excerpt from the course taken specifically from a chapter all about JavaScript destructuring. We’re publishing it here because we believe in this material and want to encourage folks like yourself to sign up for the course. So, please enjoy this break from our regular broadcasting to get a small taste of what you can expect from enrolling in the full <em>JavaScript for Everyone</em> course.</p>



<p>I&#8217;ve been writing about JavaScript for long enough that I wouldn&#8217;t rule out a hubris-related curse of some kind. I wrote <a href="https://javascriptforwebdesigners.com/" rel="noopener"><em>JavaScript for Web Designers</em></a> more than a decade ago now, back in the era when packs of feral <code>var</code> still roamed the Earth. The fundamentals are sound, but the advice is a little dated now, for sure. Still, despite being a web development antique, one part of the book has aged particularly well, to my constant frustration.</p>



<span id="more-392714"></span>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p>An entire programming language seemed like too much to ever fully understand, and I was certain that I wasn&#8217;t tuned for it. I was a developer, sure, but I wasn&#8217;t a <em>developer</em>-developer. I didn&#8217;t have the requisite robot brain; I just put borders on things for a living.</p>
<cite>JavaScript for Web Designers</cite></blockquote>



<p>I <em>still</em> hear this sentiment from incredibly talented designers and highly technical CSS experts that somehow can&#8217;t fathom calling themselves &#8220;JavaScript developers,&#8221; as though they were tragically born without whatever gland produces the chemicals that make a person innately understand the concept of variable hoisting and could never possibly qualify — this despite the fact that many of them <em>write JavaScript as part of their day-to-day work</em>. While I may not stand by the use of <code>alert()</code> in some of my examples (again, long time ago), the spirit of <em>JavaScript for Web Designers</em> holds every bit as true today as it did back then: type a semicolon and you&#8217;re writing JavaScript. Write JavaScript and you&#8217;re a JavaScript developer, full stop.</p>



<p>Now, sooner or later, you do run into the catch: nobody is born thinking like JavaScript, but to get <em>really</em> good at JavaScript, you will need to learn <em>how</em>. In order to know why JavaScript works the way it does, why sometimes things that feel like they should work don&#8217;t, and why things that feel like they <em>shouldn&#8217;t</em> work sometimes do, you need to go one step beyond the code you&#8217;re writing or even the result of running it — you need to get inside JavaScript&#8217;s head. You need to learn to interact with the language on its own terms.</p>



<p>That deep-magic knowledge is the goal of <a href="https://piccalil.li/javascript-for-everyone" rel="noopener"><em>JavaScript for Everyone</em></a>, a course designed to help you get from junior- to senior developer. In <em>JavaScript for Everyone</em>, my aim is to help you make sense of the more arcane rules of JavaScript as-it-is-played — not just teach you the <em>how</em> but the <em>why</em>, using the syntaxes you’re most likely to encounter in your day-to-day work. If you’re brand new to the language, you’ll walk away from this course with a foundational understanding of JavaScript worth hundreds of hours of trial-and-error; if you’re a junior developer, you’ll finish this course with a depth of knowledge to rival any senior.</p>



<p>Thanks to our friends here at CSS-Tricks, I&#8217;m able to share the entire lesson on <strong>destructuring assignment</strong>. These are some of my favorite JavaScript syntaxes, which I&#8217;m sure we can <em>all</em> agree are normal and in fact very <em>cool</em> things to have —syntaxes are as powerful as they are terse, all of them doing a lot of work with only a few characters. The downside of that terseness is that it makes these syntaxes a little more opaque than most, especially when you&#8217;re armed only with a browser tab open to <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring" rel="noopener">MDN</a> and a gleam in your eye. We got this, though — by the time you&#8217;ve reached the end of this lesson, you&#8217;ll be unpacking complex nested data structures with the best of them.</p>



<p>And if you missed it before, <a href="https://css-tricks.com/an-introduction-to-javascript-expressions/">there&#8217;s another excerpt from the <em>JavaScript for Everyone</em> course covering JavaScript Expressions available here on CSS-Tricks</a>.</p>


<h3 class="wp-block-heading" id="destructuring-assignment">Destructuring Assignment</h3>


<p>When you&#8217;re working with a data structure like an array or object literal, you&#8217;ll frequently find yourself in a situation where you want to grab some or all of the values that structure contains and use them to initialize discrete variables. That makes those values easier to work with, but <em>historically</em> speaking, it can lead to pretty wordy code:</p>



<pre rel="JavaScript" class="wp-block-csstricks-code-block language-javascript" data-line=""><code markup="tt">const theArray = [ false, true, false ];
const firstElement = theArray[0];
const secondElement = theArray[1];
const thirdElement = theArray[2];</code></pre>



<p>This is fine! I mean, it <em>works</em>; it has for thirty years now. But as of 2015&#8217;s ES6, we&#8217;ve had a much more elegant option: <strong>destructuring</strong>.</p>



<p>Destructuring allows you to extract individual values from an array or object and assign them to a set of identifiers without needing to access the keys and/or values one at a time. In its most simple form — called <strong>binding pattern destructuring</strong> — each value is unpacked from the array or object literal and assigned to a corresponding identifier, all of which are declared with a single <code>let</code> or <code>const</code> (or <code>var</code>, technically, yes, fine). Brace yourself, because this is a strange one:</p>



<pre rel="JavaScript" class="wp-block-csstricks-code-block language-javascript" data-line=""><code markup="tt">const theArray = [ false, true, false ];
const [ firstElement, secondElement, thirdElement ] = theArray;

console.log( firstElement );
// Result: false

console.log( secondElement );
// Result: true

console.log( thirdElement );
// Result: false</code></pre>



<p><em>That&#8217;s</em> the good stuff, even if it is a little weird to see brackets on that side of an assignment operator. That one binding covers all the same territory as the much more verbose snippet above it.</p>



<p>When working with an array, the individual identifiers are wrapped in a pair of array-style brackets, and each comma separated identifier you specify within those brackets will be initialized with the value in the corresponding element in the source Array. You’ll sometimes see destructuring referred to as <strong>unpacking</strong> a data structure, but despite how that and &#8220;destructuring&#8221; both sound, the original array or object isn&#8217;t modified by the process.</p>



<p>Elements can be skipped over by omitting an identifier between commas, the way you’d leave out a value when creating a sparse array:</p>



<pre rel="JavaScript" class="wp-block-csstricks-code-block language-javascript" data-line=""><code markup="tt">const theArray = [ true, false, true ];
const [ firstElement, , thirdElement ] = theArray;

console.log( firstElement );
// Result: true

console.log( thirdElement );
// Result: true</code></pre>



<p>There are a couple of differences in how you destructure an object using binding pattern destructuring. The identifiers are wrapped in a pair of curly braces rather than brackets; sensible enough, considering we&#8217;re dealing with objects. In the simplest version of this syntax, the identifiers you use have to correspond to the property keys:</p>



<pre rel="JavaScript" class="wp-block-csstricks-code-block language-javascript" data-line=""><code markup="tt">const theObject = {
  "theProperty" : true,
  "theOtherProperty" : false
};
const { theProperty, theOtherProperty } = theObject;

console.log( theProperty );
// result: true

console.log( theOtherProperty );
// result: false</code></pre>



<p>An array is an indexed collection, and indexed collections are intended to be used in ways where the specific iteration order matters — for example, with destructuring here, where we can assume that the identifiers we specify will correspond to the elements in the array, in sequential order.</p>



<p>That&#8217;s not the case with an object, which is a keyed collection — in strict technical terms, just a big ol&#8217; pile of properties that are intended to be defined and accessed in <em>whatever</em> order, based on their keys. No big deal in practice, though; odds are, you&#8217;d want to use the property keys’ identifier names (or something <em>very</em> similar) as your identifiers anyway. Simple and effective, but the drawback is that it assumes a given&#8230; well, <em>structure</em> to the object being destructured.</p>



<p>This brings us to the alternate syntax, which looks <em>absolutely wild</em>, at least to me. The syntax is object literal <em>shaped</em>, but very, very different — so before you look at this, briefly forget everything you know about object literals:</p>



<pre rel="JavaScript" class="wp-block-csstricks-code-block language-javascript" data-line=""><code markup="tt">const theObject = {
  "theProperty" : true,
  "theOtherProperty" : false
};
const { theProperty : theIdentifier, theOtherProperty : theOtherIdentifier } = theObject;

console.log( theIdentifier );
// result: true

console.log( theOtherIdentifier );
// result: false</code></pre>



<p>You&#8217;re still not thinking about object literal notation, right? Because if you were, <em>wow</em> would that syntax look strange. I mean, a reference to the property to be destructured where a key would be and identifiers where the values would be?</p>



<p>Fortunately, we&#8217;re not thinking about object literal notation even a little bit right now, so I don&#8217;t have to write that previous paragraph in the first place. Instead, we can frame it like this: within the parentheses-wrapped curly braces, zero or more comma-separated instances of the property key with the value we want, followed by a colon, followed by the identifier we want that property&#8217;s value assigned to. After the curly braces, an assignment operator (<code>=</code>) and the object to be destructured. That&#8217;s all a lot in print, I know, but you&#8217;ll get a feel for it after using it a few times.</p>



<p>The second approach to destructuring is <strong>assignment pattern destructuring</strong>. With assignment patterns, the value of each destructured property is assigned to a specific target — like a variable we declared with <code>let</code> (or, <em>technically</em>, <code>var</code>), a property of another object, or an element in an array.</p>



<p>When working with arrays and variables declared with <code>let</code>, assignment pattern destructuring really just adds a step where you declare the variables that will end up containing the destructured values:</p>



<pre rel="JavaScript" class="wp-block-csstricks-code-block language-javascript" data-line=""><code markup="tt">const theArray = [ true, false ];
let theFirstIdentifier;
let theSecondIdentifier

[ theFirstIdentifier, theSecondIdentifier ] = theArray;

console.log( theFirstIdentifier );
// true

console.log( theSecondIdentifier );
// false</code></pre>



<p>This gives you the same end result as you&#8217;d get using binding pattern destructuring, like so:</p>



<pre rel="JavaScript" class="wp-block-csstricks-code-block language-javascript" data-line=""><code markup="tt">const theArray = [ true, false ];

let [ theFirstIdentifier, theSecondIdentifier ] = theArray;

console.log( theFirstIdentifier );
// true

console.log( theSecondIdentifier );
// false</code></pre>



<p>Binding pattern destructuring will allow you to use <code>const</code> from the jump, though:</p>



<pre rel="JavaScript" class="wp-block-csstricks-code-block language-javascript" data-line=""><code markup="tt">const theArray = [ true, false ];

const [ theFirstIdentifier, theSecondIdentifier ] = theArray;

console.log( theFirstIdentifier );
// true

console.log( theSecondIdentifier );
// false</code></pre>



<p>Now, if you wanted to use those destructured values to populate another array or the properties of an object, you would hit a predictable double-declaration wall when using binding pattern destructuring:</p>



<pre rel="JavaScript" class="wp-block-csstricks-code-block language-javascript" data-line=""><code markup="tt">// Error
const theArray = [ true, false ];
let theResultArray = [];

let [ theResultArray[1], theResultArray[0] ] = theArray;
// Uncaught SyntaxError: redeclaration of let theResultArray</code></pre>



<p>We can&#8217;t make <code>let</code>/<code>const</code>/<code>var</code> do anything but create variables; that&#8217;s their entire deal. In the example above, the first part of the line is interpreted as <code>let theResultArray</code>, and we get an error: <code>theResultArray</code> was already declared.</p>



<p>No such issue when we&#8217;re using assignment pattern destructuring:</p>



<pre rel="JavaScript" class="wp-block-csstricks-code-block language-javascript" data-line=""><code markup="tt">const theArray = [ true, false ];
let theResultArray = [];

[ theResultArray[1], theResultArray[0] ] = theArray;

console.log( theResultArray );
// result: Array [ false, true ]</code></pre>



<p>Once again, this syntax applies to objects as well, with a few little catches:</p>



<pre rel="JavaScript" class="wp-block-csstricks-code-block language-javascript" data-line=""><code markup="tt">const theObject = {
  "theProperty" : true,
  "theOtherProperty" : false
};
let theProperty;
let theOtherProperty;

({ theProperty, theOtherProperty } = theObject );

console.log( theProperty );
// true

console.log( theOtherProperty );
// false</code></pre>



<p>You&#8217;ll notice a pair of disambiguating parentheses around the line where we&#8217;re doing the destructuring. You&#8217;ve seen this before: without the grouping operator, a pair of curly braces in a context where a statement is expected is assumed to be a block statement, and you get a syntax error:</p>



<pre rel="JavaScript" class="wp-block-csstricks-code-block language-javascript" data-line=""><code markup="tt">// Error
const theObject = {
  "theProperty" : true,
  "theOtherProperty" : false
};
let theProperty;
let theOtherProperty;

{ theProperty, theOtherProperty } = theObject;
// Uncaught SyntaxError: expected expression, got '='</code></pre>



<p>So far this isn&#8217;t doing anything that binding pattern destructuring couldn&#8217;t. We&#8217;re using identifiers that match the property keys, but any identifier will do, if we use the alternate object destructuring syntax:</p>



<pre rel="JavaScript" class="wp-block-csstricks-code-block language-javascript" data-line=""><code markup="tt">const theObject = {
  "theProperty" : true,
  "theOtherProperty" : false
};
let theFirstIdentifier;
let theSecondIdentifier;

({ theProperty: theFirstIdentifier, theOtherProperty: theSecondIdentifier } = theObject );

console.log( theFirstIdentifier );
// true

console.log( theSecondIdentifier );
// false</code></pre>



<p>Once again, nothing binding pattern destructuring couldn&#8217;t do. But <em>unlike</em> binding pattern destructuring, any kind of assignment target will work with assignment pattern destructuring:</p>



<pre rel="JavaScript" class="wp-block-csstricks-code-block language-javascript" data-line=""><code markup="tt">const theObject = {
  "theProperty" : true,
  "theOtherProperty" : false
};
let resultObject = {};

({ theProperty : resultObject.resultProp, theOtherProperty : resultObject.otherResultProp } = theObject );

console.log( resultObject );
// result: Object { resultProp: true, otherResultProp: false }</code></pre>



<p>With either syntax, you can set &#8220;default&#8221; values that will be used if an element or property isn’t present at all, or it contains an explicit <code>undefined</code> value:</p>



<pre rel="JavaScript" class="wp-block-csstricks-code-block language-javascript" data-line=""><code markup="tt">const theArray = [ true, undefined ];
const [ firstElement, secondElement = "A string.", thirdElement = 100 ] = theArray;

console.log( firstElement );
// Result: true

console.log( secondElement );
// Result: A string.

console.log( thirdElement );
// Result: 100</code></pre>



<pre rel="JavaScript" class="wp-block-csstricks-code-block language-javascript" data-line=""><code markup="tt">const theObject = {
  "theProperty" : true,
  "theOtherProperty" : undefined
};
const { theProperty, theOtherProperty = "A string.", aThirdProperty = 100 } = theObject;

console.log( theProperty );
// Result: true

console.log( theOtherProperty );
// Result: A string.

console.log( aThirdProperty );
// Result: 100</code></pre>



<p>Snazzy stuff for sure, but where this syntax really shines is when you&#8217;re unpacking <em>nested</em> arrays and objects. Naturally, there&#8217;s nothing stopping you from unpacking an object that contains an object as a property value, then unpacking that inner object separately:</p>



<pre rel="JavaScript" class="wp-block-csstricks-code-block language-javascript" data-line=""><code markup="tt">const theObject = {
  "theProperty" : true,
  "theNestedObject" : {
    "anotherProperty" : true,
    "stillOneMoreProp" : "A string."
  }
};

const { theProperty, theNestedObject } = theObject;
const { anotherProperty, stillOneMoreProp = "Default string." } = theNestedObject;

console.log( stillOneMoreProp );
// Result: A string.</code></pre>



<p>But we can make this way more concise. We don’t have to unpack the nested object separately — we can unpack it as part of the same binding:</p>



<pre rel="JavaScript" class="wp-block-csstricks-code-block language-javascript" data-line=""><code markup="tt">const theObject = {
  "theProperty" : true,
  "theNestedObject" : {
    "anotherProperty" : true,
    "stillOneMoreProp" : "A string."
  }
};
const { theProperty, theNestedObject : { anotherProperty, stillOneMoreProp } } = theObject;

console.log( stillOneMoreProp );
// Result: A string.</code></pre>



<p>From an object within an object to three easy-to-use constants in a single line of code.</p>



<p>We can unpack mixed data structures just as succinctly:</p>



<pre rel="JavaScript" class="wp-block-csstricks-code-block language-javascript" data-line=""><code markup="tt">const theObject = [{
  "aProperty" : true,
},{
  "anotherProperty" : "A string."
}];
const [{ aProperty }, { anotherProperty }] = theObject;

console.log( anotherProperty );
// Result: A string.</code></pre>



<p>A <em>dense</em> syntax, there&#8217;s no question of that — bordering on &#8220;opaque,&#8221; even. It might take a little experimentation to get the hang of this one, but once it clicks, destructuring assignment gives you an incredibly quick and convenient way to break down complex data structures without spinning up a bunch of intermediate data structures and values.</p>


<h3 class="wp-block-heading" id="rest-properties">Rest Properties</h3>


<p>In all the examples above we&#8217;ve been working with known quantities: &#8220;turn these X properties or elements into Y variables.&#8221; That doesn&#8217;t match the reality of breaking down a huge, tangled object, jam-packed array, or both.</p>



<p>In the context of a destructuring assignment, an ellipsis (that’s <code>...</code>, not <code>…</code>, for my fellow Unicode enthusiasts) followed by an identifier (to the tune of <code>...theIdentifier</code>) represents a <strong>rest property</strong> — an identifier that will represent <em>the rest</em> of the array or object being unpacked. This rest property will contain all the remaining elements or properties beyond the ones we’ve explicitly unpacked to their own identifiers, all bundled up in the same kind of data structure as the one we’re unpacking:</p>



<pre rel="JavaScript" class="wp-block-csstricks-code-block language-javascript" data-line=""><code markup="tt">const theArray = [ false, true, false, true, true, false ];
const [ firstElement, secondElement, ...remainingElements ] = theArray;

console.log( remainingElements );
// Result: Array(4) [ false, true, true, false ]</code></pre>



<p>Generally I try to avoid using examples that veer too close to real-world use on purpose where they can get a little convoluted and I don&#8217;t want to distract from the core ideas — but in this case, &#8220;convoluted&#8221; is exactly what we&#8217;re looking to work around. So let&#8217;s use an object near and dear to my heart: (part of) the data representing the very first newsletter I sent out back when I started writing this course.</p>



<pre rel="JavaScript" class="wp-block-csstricks-code-block language-javascript" data-line=""><code markup="tt">const firstPost = {
  "id": "mat-update-1.md",
  "slug": "mat-update-1",
  "body": "Hey, great to meet you, everybody. I'm Mat — \\"Wilto\\" is good too — and I'm here to teach you JavaScript. Not just what JavaScript is or what JavaScript does, but the *how* and the *why* of JavaScript. The weird stuff. The *deep magic_.\\n\\nWell, okay, I'm not *currently* here to teach you JavaScript, but I will be soon. Right now I'm just getting things in order for the course — planning, outlining, polishing the fancy semicolons that I only take out when I'm having company over, writing like 5,000 words about `this` as a warm-up that completely got away from me, that kind of thing.",
  "collection": "emails",
  "data": {
    "title": "Meet your Instructor",
    "pubDate": "2025-05-08T09:55:00.630Z",
    "headingSize": "large",
    "showUnsubscribeLink": true,
    "stream": "javascript-for-everyone"
  }
};</code></pre>



<p>Quite a bit going on in there. For purposes of this exercise, assume this is coming in from an external API the way it is over on my website — this isn’t an object we control. Sure, we can work with that object directly, but that’s a little unwieldy when all we need is, for example, the newsletter title and body:</p>



<pre rel="JavaScript" class="wp-block-csstricks-code-block language-javascript" data-line=""><code markup="tt">const firstPost = {
  "id": "mat-update-1.md",
  "slug": "mat-update-1",
  "body": "Hey, great to meet you, everybody. I'm Mat — \\"Wilto\\" is good too — and I'm here to teach you JavaScript. Not just what JavaScript is or what JavaScript does, but the *how* and the *why* of JavaScript. The weird stuff. The *deep magic_.\\n\\nWell, okay, I'm not *currently* here to teach you JavaScript, but I will be soon. Right now I'm just getting things in order for the course — planning, outlining, polishing the fancy semicolons that I only take out when I'm having company over, writing like 5,000 words about `this` as a warm-up that completely got away from me, that kind of thing.",
  "data": {
    "title": "Meet your Instructor",
    "pubDate": "2025-05-08T09:55:00.630Z",
    "headingSize": "large",
    "showUnsubscribeLink": true,
    "stream": "javascript-for-everyone"
  }
};

const { data : { title }, body } = firstPost;

console.log( title );
// Result: Meet your Instructor

console.log( body );
/* Result:
Hey, great to meet you, everybody. I'm Mat — \\"Wilto\\" is good too — and I'm here to teach you JavaScript. Not just what JavaScript is or what JavaScript does, but the *how* and the *why* of JavaScript. The weird stuff. The *deep magic_.

Well, okay, I'm not *currently* here to teach you JavaScript, but I will be soon. Right now I'm just getting things in order for the course — planning, outlining, polishing the fancy semicolons that I only take out when I'm having company over, writing like 5,000 words about `this` as a warm-up that completely got away from me, that kind of thing.
*/</code></pre>



<p>That&#8217;s <em>tidy</em>; a couple dozen characters and we have exactly what we need from that tangle. I know I&#8217;m not going to need those <code>id</code> or <code>slug</code> properties to publish it on my own website, so I omit those altogether — but that inner <code>data</code> object has a conspicuous ring to it, like maybe one could expect it to contain other properties associated with future posts. I don&#8217;t know what those properties will be, but I know I&#8217;ll want them all packaged up in a way where I can easily make use of them. I want the <code>firstPost.data.title</code> property in isolation, but I also want an object containing all the <em>rest</em> of the <code>firstPost.data</code> properties, whatever they end up being:</p>



<pre rel="JavaScript" class="wp-block-csstricks-code-block language-javascript" data-line=""><code markup="tt">const firstPost = {
  "id": "mat-update-1.md",
  "slug": "mat-update-1",
  "body": "Hey, great to meet you, everybody. I'm Mat — \\"Wilto\\" is good too — and I'm here to teach you JavaScript. Not just what JavaScript is or what JavaScript does, but the *how* and the *why* of JavaScript. The weird stuff. The *deep magic_.\\n\\nWell, okay, I'm not *currently* here to teach you JavaScript, but I will be soon. Right now I'm just getting things in order for the course — planning, outlining, polishing the fancy semicolons that I only take out when I'm having company over, writing like 5,000 words about `this` as a warm-up that completely got away from me, that kind of thing.",
  "data": {
    "title": "Meet your Instructor",
    "pubDate": "2025-05-08T09:55:00.630Z",
    "headingSize": "large",
    "showUnsubscribeLink": true,
    "stream": "javascript-for-everyone"
  }
};

const { data : { title, ...metaData }, body } = firstPost;

console.log( title );
// Result: Meet your Instructor

console.log( metaData );
// Result: Object { pubDate: "2025-05-08T09:55:00.630Z", headingSize: "large", showUnsubscribeLink: true, stream: "javascript-for-everyone" }</code></pre>



<p><em>Now</em> we&#8217;re talking. Now we have a <code>metaData</code> object containing anything and everything else in the <code>data</code> property of the object we&#8217;ve been handed.</p>



<p>Listen. If you&#8217;re anything like me, even if you haven&#8217;t quite gotten your head around the syntax itself, you&#8217;ll find that there&#8217;s something viscerally satisfying about the binding in the snippet above. All that work done in a single line of code. It&#8217;s terse, it&#8217;s elegant — it takes the complex and makes it simple. That&#8217;s the good stuff.</p>



<p>And yet: maybe you can hear it too, ever-so-faintly? A quiet voice, way down in the back of your mind, that asks &#8220;I wonder if there&#8217;s an even <em>better</em> way.&#8221; For what we&#8217;re doing here, in isolation, this solution is about as good as it gets — but as far as the wide world of JavaScript goes: there&#8217;s <em>always</em> a better way. If you can&#8217;t hear it just yet, I bet you will by the end of the course.</p>



<p>Anyone who writes JavaScript is a JavaScript developer; there are no two ways about that. But the satisfaction of creating order from chaos in just a few keystrokes, and the drive to find even better ways to do it? Those are the makings of a JavaScript developer to be <em>reckoned</em> with.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<p>You can do more than just &#8220;get by&#8221; with JavaScript; I know you can. You can <em>understand</em> JavaScript, all the way down to the mechanisms that power the language — the gears and springs that move the entire &#8220;interactive&#8221; layer of the web. To really understand JavaScript is to understand the boundaries of how users interact with the things we&#8217;re building, and broadening our understanding of the medium we work with every day sharpens all of our skills, from layout to accessibility to front-end performance to typography. <em>Understanding</em> JavaScript means less &#8220;I wonder if it&#8217;s possible to&#8230;&#8221; and &#8220;I guess we have to&#8230;&#8221; in your day-to-day decision making, even if you&#8217;re not the one tasked with <em>writing</em> it. Expanding our skillsets will always make us better — and more valued, professionally — no matter our roles.</p>



<p>JavaScript is a tricky thing to learn; I know that all too well — that&#8217;s why I wrote <a href="https://piccalil.li/javascript-for-everyone" rel="noopener"><em>JavaScript for Everyone</em></a>. You can do this, and I&#8217;m here to help.</p>



<p>I hope to see you there.</p>



<div class="wp-block-buttons is-layout-flex wp-block-buttons-is-layout-flex">
<div class="wp-block-button"><a class="wp-block-button__link wp-element-button" href="https://piccalil.li/javascript-for-everyone" rel="noopener">Check out the course</a></div>
</div>
<hr />
<p><small><a rel="nofollow" href="https://css-tricks.com/javascript-for-everyone-destructuring/">JavaScript for Everyone: Destructuring</a> originally published on <a rel="nofollow" href="https://css-tricks.com">CSS-Tricks</a>, which is part of the <a href="https://try.digitalocean.com/css-tricks/?utm_medium=rss&amp;utm_source=css-tricks.com&amp;utm_campaign=family_&amp;utm_content=">DigitalOcean</a> family. You should <a href="https://css-tricks.com/newsletters/">get the newsletter</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://css-tricks.com/javascript-for-everyone-destructuring/feed/</wfw:commentRss>
			<slash:comments>2</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">392714</post-id>	</item>
		<item>
		<title>What’s !important #7: random(), Folded Corners, Anchored Container Queries, and More</title>
		<link>https://css-tricks.com/whats-important-7/</link>
					<comments>https://css-tricks.com/whats-important-7/#comments</comments>
		
		<dc:creator><![CDATA[Daniel Schwarz]]></dc:creator>
		<pubDate>Mon, 16 Mar 2026 15:06:01 +0000</pubDate>
				<category><![CDATA[Articles]]></category>
		<category><![CDATA[news]]></category>
		<guid isPermaLink="false">https://css-tricks.com/?p=393021</guid>

					<description><![CDATA[<p>For this issue we have random(), folded clip-path corners, anchored container queries, customizable select, scroll-triggered animations, and more.</p>
<hr />
<p><small><a rel="nofollow" href="https://css-tricks.com/whats-important-7/">What’s !important #7: random(), Folded Corners, Anchored Container Queries, and More</a> originally published on <a rel="nofollow" href="https://css-tricks.com">CSS-Tricks</a>, which is part of the <a href="https://try.digitalocean.com/css-tricks/?utm_medium=rss&amp;utm_source=css-tricks.com&amp;utm_campaign=family_&amp;utm_content=">DigitalOcean</a> family. You should <a href="https://css-tricks.com/newsletters/">get the newsletter</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p>For this issue of <strong>What’s !important</strong>, we have a healthy balance of old CSS that you might’ve missed and <em>new</em> CSS that you don’t want to miss. This includes <code>random()</code>, <code>random-item()</code>, folded corners using <code>clip-path</code>, <code>backdrop-filter</code>, <code>font-variant-numeric: tabular-nums</code>, the Popover API, anchored container queries, anchor positioning in general, DOOM in CSS, customizable <code>&lt;select&gt;</code>, <code>:open</code>, scroll-triggered animations, <code>&lt;toolbar&gt;</code>, and somehow, more.</p>



<p>Let’s dig in.</p>



<span id="more-393021"></span>


<h3 class="wp-block-heading" id="understanding-random-and-randomitem">Understanding <code>random()</code> and <code>random-item()</code></h3>


<p>Alvaro Montoro explains <a href="https://alvaromontoro.com/blog/68092/native-random-values-in-css" rel="noopener">how the <code>random()</code> and <code>random-item()</code> CSS functions work</a>. As it turns out, they’re actually quite complex:</p>



<pre rel="CSS" class="wp-block-csstricks-code-block language-css" data-line=""><code markup="tt">width: random(--w element-shared, 1rem, 2rem);
color: random-item(--c, red, orange, yellow, darkkhaki);</code></pre>


<h3 class="wp-block-heading" id="creating-folded-corners-using-clippath">Creating folded corners using <code>clip-path</code></h3>


<p>My first solution to folded corners involved actual images. Not a <em>great</em> solution, but that was the way to do it in the noughties. Since then we’ve been able to <a href="https://codepen.io/abouzia/pen/eBErgV" rel="noopener">do it with <code>box-shadow</code></a>, but Kitty Giraudel has come up with a <a href="https://kittygiraudel.com/2026/03/05/folded-corner-with-css/" rel="noopener">CSS <code>clip-path</code> solution</a> that clips a custom shape (hover the kitty to see it in action):</p>



<div class="wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper"><iframe id="cp_embed_raNoZLr" src="//codepen.io/anon/embed/raNoZLr?height=450&amp;theme-id=1&amp;slug-hash=raNoZLr&amp;default-tab=css,result" height="450" scrolling="no" frameborder="0" allowfullscreen allowpaymentrequest name="CodePen Embed raNoZLr" title="CodePen Embed raNoZLr" class="cp_embed_iframe" style="width:100%;overflow:hidden">CodePen Embed Fallback</iframe></div>


<h3 class="wp-block-heading" id="revisiting-backdropfilter-and-fontvariantnumeric-tabularnums">Revisiting <code>backdrop-filter</code> and <code>font-variant-numeric: tabular-nums</code></h3>


<p><a href="https://www.alwaystwisted.com/articles/beyond-the-blur-css-backdrop-filter" rel="noopener">Stuart Robson talks about <code>backdrop-filter</code></a>. It’s not a new CSS property, but it’s very useful and hardly ever talked about. In fact, up until now, I thought that it was for the <code>::backdrop</code> pseudo-element, but we can actually use it to create all kinds of background effects for all kinds of elements, like this:</p>



<div class="wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper"><iframe id="cp_embed_bNwEKqM" src="//codepen.io/anon/embed/bNwEKqM?height=450&amp;theme-id=1&amp;slug-hash=bNwEKqM&amp;default-tab=result" height="450" scrolling="no" frameborder="0" allowfullscreen allowpaymentrequest name="CodePen Embed bNwEKqM" title="CodePen Embed bNwEKqM" class="cp_embed_iframe" style="width:100%;overflow:hidden">CodePen Embed Fallback</iframe></div>



<p><code>font-variant-numeric: tabular-nums</code> is another one. This property and value prevents layout shift when numbers change dynamically, as they do with live clocks, counters, timers, financial tables, and so on. <a href="https://www.amitmerchant.com/one-css-property-that-makes-numbers-look-instantly-better/" rel="noopener">Amit Merchant walks you through it</a> with this demo:</p>



<div class="wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper"><iframe id="cp_embed_dPpMNVd" src="//codepen.io/anon/embed/dPpMNVd?height=450&amp;theme-id=1&amp;slug-hash=dPpMNVd&amp;default-tab=css,result" height="450" scrolling="no" frameborder="0" allowfullscreen allowpaymentrequest name="CodePen Embed dPpMNVd" title="CodePen Embed dPpMNVd" class="cp_embed_iframe" style="width:100%;overflow:hidden">CodePen Embed Fallback</iframe></div>


<h3 class="wp-block-heading" id="getting-started-with-the-popover-api">Getting started with the Popover API</h3>


<p>Godstime Aburu does <a href="https://www.smashingmagazine.com/2026/03/getting-started-popover-api/" rel="noopener">a deep dive on the Popover API</a>, a new(ish) but everyday web platform feature that simplifies tooltip and tooltip-like UI patterns, but isn’t without its nuances.</p>


<h3 class="wp-block-heading" id="unraveling-yet-another-anchor-positioning-quirk">Unraveling yet another anchor positioning quirk</h3>


<p>Just <a href="https://frontendmasters.com/blog/the-big-gotcha-of-anchor-positioning/" rel="noopener">another anchor positioning quirk</a>, this time from Chris Coyier. These quirks have been piling up for a while now. <em>We’ve</em> talked about them <a href="https://css-tricks.com/anchor-positioning-quirks/">time</a> and <a href="https://css-tricks.com/yet-another-anchor-positioning-quirk/">time again</a>, but the thing is, they’re <em>not</em> bugs. Anchor positioning works in a way that isn’t commonly understood, so Chris’ article is definitely worth a read, as are the articles that he references.</p>


<h3 class="wp-block-heading" id="building-dynamic-toggletips-using-anchored-container-queries">Building dynamic toggletips using anchored container queries</h3>


<p>In this walkthrough, I demonstrate <a href="https://piccalil.li/blog/building-dynamic-toggletips-using-anchored-container-queries/" rel="noopener">how to build dynamic toggletips using anchored container queries</a>. Also, I ran into an anchor positioning quirk, so if you’re looking to solidify your understanding of all that, I think the walkthrough will help with that too.</p>



<p>Demo (full effect requires Chrome 143+):</p>



<div class="wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper"><iframe id="cp_embed_gbrMGYx" src="//codepen.io/anon/embed/gbrMGYx?height=450&amp;theme-id=1&amp;slug-hash=gbrMGYx&amp;default-tab=css,result" height="450" scrolling="no" frameborder="0" allowfullscreen allowpaymentrequest name="CodePen Embed gbrMGYx" title="CodePen Embed gbrMGYx" class="cp_embed_iframe" style="width:100%;overflow:hidden">CodePen Embed Fallback</iframe></div>


<h3 class="wp-block-heading" id="doom-in-css">DOOM in CSS</h3>


<p><a href="https://bsky.app/profile/html5test.com/post/3mgxr3pcjhk2k" rel="noopener">DOOM in CSS</a>. <strong>DOOM.</strong> <strong><em>In CSS.</em></strong></p>



<blockquote class="bluesky-embed" data-bluesky-uri="at://did:plc:fdu5alepkigblthylzkahr4f/app.bsky.feed.post/3mgxr3pcjhk2k" data-bluesky-cid="bafyreiemze2rlinsf6fkzrhhiaxb47nks2rywyttxtevbicw4etuoiamku" data-bluesky-embed-color-mode="system"><p lang="en">DOOM fully rendered in CSS. Every surface is a &lt;div&gt; that has a background image, with a clipping path with 3D transforms applied. Of course CSS does not have a movable camera, so we rotate and translate the scene around the user.<br><br><a href="https://bsky.app/profile/did:plc:fdu5alepkigblthylzkahr4f/post/3mgxr3pcjhk2k?ref_src=embed" rel="noopener">[image or embed]</a></p>&mdash; Niels Leenheer (<a href="https://bsky.app/profile/did:plc:fdu5alepkigblthylzkahr4f?ref_src=embed" rel="noopener">@html5test.com</a>) <a href="https://bsky.app/profile/did:plc:fdu5alepkigblthylzkahr4f/post/3mgxr3pcjhk2k?ref_src=embed" rel="noopener">Mar 13, 2026 at 20:32</a></blockquote><script async src="https://embed.bsky.app/static/embed.js" charset="utf-8"></script>


<h3 class="wp-block-heading" id="safari-updates-chrome-updates-and-quick-hits-you-missed">Safari updates, Chrome updates, and Quick Hits you missed</h3>


<ul class="wp-block-list">
<li><a href="https://developer.apple.com/documentation/safari-technology-preview-release-notes/stp-release-238" rel="noopener">Safari Technology Preview 238</a>
<ul class="wp-block-list">
<li><a href="https://css-tricks.com/abusing-customizable-selects/">Customizable <code>&lt;select></code></a></li>



<li><a href="https://css-tricks.com/almanac/pseudo-selectors/o/open/"><code>:open</code></a> (to my surprise, as I thought it was Baseline already)</li>
</ul>
</li>



<li><a href="https://developer.chrome.com/blog/new-in-chrome-146" rel="noopener">Chrome 146</a>
<ul class="wp-block-list">
<li><a href="https://developer.chrome.com/blog/scroll-triggered-animations" rel="noopener">Scroll-triggered animations</a></li>
</ul>
</li>
</ul>



<p>In addition, <a href="https://developer.chrome.com/blog/chrome-two-week-release" rel="noopener">Chrome will ship every two weeks starting September</a>.</p>



<p>From the <a href="https://css-tricks.com/category/quick-hits/">Quick Hits</a> reel, you might’ve missed that Font Awesome launched a Kickstarter campaign to transform Eleventy into Build Awesome, cancelled it because their emails failed to send (<em>despite meeting their goal!</em>), and vowed to try again. You can <a href="https://www.kickstarter.com/projects/fontawesome/build-awesome-pro/" rel="noopener">subscribe to the relaunch notification</a>.</p>



<p>Also, <a href="https://open-ui.org/components/toolbar.explainer/" rel="noopener"><code>&lt;toolbar&gt;</code></a> is coming along <a href="https://bsky.app/profile/lukewarlow.dev/post/3mgfu6abcv22w" rel="noopener">according to Luke Warlow</a>. This is akin to <a href="https://developer.chrome.com/blog/focusgroup-rfc" rel="noopener"><code>&lt;focusgroup&gt;</code></a>, which we can actually test in Chrome 146 with the “Experimental Web Platform features” flag enabled.</p>



<p>Right, I’m off to slay some demons in DOOM. Until next time!</p>



<p><em>P.S. Congratulations to <a href="https://css-tricks.com/author/kevinpowell/">Kevin Powell</a> for making it to <a href="https://www.youtube.com/live/e5JY5KzrDwg" rel="noopener">1 million YouTube subs</a>!</em></p>
<hr />
<p><small><a rel="nofollow" href="https://css-tricks.com/whats-important-7/">What’s !important #7: random(), Folded Corners, Anchored Container Queries, and More</a> originally published on <a rel="nofollow" href="https://css-tricks.com">CSS-Tricks</a>, which is part of the <a href="https://try.digitalocean.com/css-tricks/?utm_medium=rss&amp;utm_source=css-tricks.com&amp;utm_campaign=family_&amp;utm_content=">DigitalOcean</a> family. You should <a href="https://css-tricks.com/newsletters/">get the newsletter</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://css-tricks.com/whats-important-7/feed/</wfw:commentRss>
			<slash:comments>2</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">393021</post-id>	</item>
		<item>
		<title>4 Reasons That Make Tailwind Great for Building Layouts</title>
		<link>https://css-tricks.com/4-reasons-that-make-tailwind-great-for-building-layouts/</link>
					<comments>https://css-tricks.com/4-reasons-that-make-tailwind-great-for-building-layouts/#comments</comments>
		
		<dc:creator><![CDATA[Zell Liew]]></dc:creator>
		<pubDate>Mon, 16 Mar 2026 14:01:02 +0000</pubDate>
				<category><![CDATA[Articles]]></category>
		<category><![CDATA[framework]]></category>
		<category><![CDATA[layout]]></category>
		<category><![CDATA[tailwind]]></category>
		<guid isPermaLink="false">https://css-tricks.com/?p=392180</guid>

					<description><![CDATA[<p>Tailwind is really great for making layouts and there are many reasons why. Zell Liew looks at four specific examples of common use cases.</p>
<hr />
<p><small><a rel="nofollow" href="https://css-tricks.com/4-reasons-that-make-tailwind-great-for-building-layouts/">4 Reasons That Make Tailwind Great for Building Layouts</a> originally published on <a rel="nofollow" href="https://css-tricks.com">CSS-Tricks</a>, which is part of the <a href="https://try.digitalocean.com/css-tricks/?utm_medium=rss&amp;utm_source=css-tricks.com&amp;utm_campaign=family_&amp;utm_content=">DigitalOcean</a> family. You should <a href="https://css-tricks.com/newsletters/">get the newsletter</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p>When I talk about layouts, I&#8217;m referring to how you place items on a page. The CSS properties that are widely used here include:</p>



<ul class="wp-block-list">
<li><code>display</code> — often <code>grid</code> or <code>flex</code> nowadays</li>



<li><code>margin</code></li>



<li><code>padding</code></li>



<li><code>width</code></li>



<li><code>height</code></li>



<li><code>position</code></li>



<li><code>top</code>, <code>left</code>, <code>bottom</code>, <code>right</code></li>
</ul>



<p>I often include <code>border-width</code> as a minor item in this list as well.</p>



<p>At this point, there&#8217;s only one thing I&#8217;d like to say.</p>



<p><strong>Tailwind is really great for making layouts.</strong></p>



<p>There are many reasons why.</p>



<span id="more-392180"></span>


<h3 class="wp-block-heading" id="first-layout-styles-are-highly-dependent-on-the-html-structure">First: Layout styles are highly dependent on the HTML structure</h3>


<p>When we shift layouts into CSS, we lose the mental structure and it takes effort to re-establish them. Imagine the following three-column grid in HTML and CSS:</p>



<pre rel="HTML" class="wp-block-csstricks-code-block language-markup" data-line=""><code markup="tt">&lt;div class="grid">
  &lt;div class="grid-item">&lt;/div>
  &lt;div class="grid-item">&lt;/div>
&lt;/div></code></pre>



<pre rel="CSS" class="wp-block-csstricks-code-block language-css" data-line=""><code markup="tt">.grid {
  display: grid;
  grid-template-columns: 2fr 1fr;

  .grid-item:first-child {
    grid-column: span 2
  }

  .grid-item:last-child {
    grid-column: span 1
  }
}</code></pre>



<figure class="wp-block-image size-full"><img data-recalc-dims="1" loading="lazy" decoding="async" width="739" height="90" src="https://i0.wp.com/css-tricks.com/wp-content/uploads/2026/02/content-sidebar.png?resize=739%2C90" alt="Two blue rectangles side-by-side illustrating a two-column layout where the left column is twice the width of the right column." class="wp-image-392186" srcset="https://i0.wp.com/css-tricks.com/wp-content/uploads/2026/02/content-sidebar.png?w=739&amp;ssl=1 739w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2026/02/content-sidebar.png?resize=300%2C37&amp;ssl=1 300w" sizes="auto, (min-width: 735px) 864px, 96vw" /></figure>



<p>Now cover the HTML structure and just read the CSS. As you do that, notice you need to exert effort to imagine the HTML structure that this applies to.</p>



<p>Now imagine the same, but built with <a href="https://tailwindcss.com/docs/styling-with-utility-classes" rel="noopener">Tailwind utilities</a>:</p>



<pre rel="HTML" class="wp-block-csstricks-code-block language-markup" data-line=""><code markup="tt">&lt;div class="grid grid-cols-3">
  &lt;div class="col-span-2">&lt;/div>
  &lt;div class="col-span-1">&lt;/div>
&lt;/div></code></pre>



<p>You might almost begin to see the layout manifest in your eyes without seeing the actual output. It&#8217;s pretty clear: A three-column grid, first item spans two columns while the second one spans one column.</p>



<p>But <code>grid-cols-3</code> and <code>col-span-2</code> are kinda weird and foreign-looking because we&#8217;re trying to parse Tailwind&#8217;s method of writing CSS.</p>



<p>Now, watch what happens when we shift the syntax out of the way and use CSS variables to define the layout instead. The layout becomes crystal clear immediately:</p>



<pre rel="HTML" class="wp-block-csstricks-code-block language-markup" data-line=""><code markup="tt">&lt;div class="grid-simple [--cols:3]">
  &lt;div class="[--span:2]"> ... &lt;/div>
  &lt;div class="[--span:1]"> ... &lt;/div>
&lt;/div></code></pre>



<figure class="wp-block-image size-full"><img data-recalc-dims="1" loading="lazy" decoding="async" width="739" height="90" src="https://i0.wp.com/css-tricks.com/wp-content/uploads/2026/02/content-sidebar.png?resize=739%2C90" alt="Two blue rectangles side-by-side illustrating a two-column layout where the left column is twice the width of the right column." class="wp-image-392186" srcset="https://i0.wp.com/css-tricks.com/wp-content/uploads/2026/02/content-sidebar.png?w=739&amp;ssl=1 739w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2026/02/content-sidebar.png?resize=300%2C37&amp;ssl=1 300w" sizes="auto, (min-width: 735px) 864px, 96vw" /></figure>



<p>Same three-column layout.</p>



<p>But it makes the layout much easier to write, read, and visualize. It also has other benefits, but <a href="https://splendidlabz.com/docs/layouts/macro-layouts/grid-simple/#features" rel="noopener">I&#8217;ll let you explore its documentation</a> instead of explaining it here.</p>



<p>For now, let&#8217;s move on.</p>


<h4 class="wp-block-heading" id="why-not-use-2fr-1fr-">Why not use <code>2fr 1fr</code>?</h4>


<p>It makes sense to write <code>2fr 1fr</code> for a three-column grid, doesn&#8217;t it?</p>



<pre rel="CSS" class="wp-block-csstricks-code-block language-css" data-line=""><code markup="tt">.grid {
  display: grid;
  grid-template-columns: 2fr 1fr;
}</code></pre>



<p>Unfortunately, it won&#8217;t work. This is because <code>fr</code> is calculated based on the available space after subtracting away the grid&#8217;s gutters (or gap).</p>



<p>Since <code>2fr 1fr</code> only contains two columns, the output from <code>2fr 1fr</code> will be different from a standard three-column grid.</p>



<figure class="wp-block-image size-full"><img data-recalc-dims="1" loading="lazy" decoding="async" width="739" height="397" src="https://i0.wp.com/css-tricks.com/wp-content/uploads/2026/02/2fr-1fr-comparison.png?resize=739%2C397" alt="Three examples of multi-column layouts stacked. The first is an equal three-column layout, the second and third are two columns where the left column is double the width of the right column." class="wp-image-392185" srcset="https://i0.wp.com/css-tricks.com/wp-content/uploads/2026/02/2fr-1fr-comparison.png?w=739&amp;ssl=1 739w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2026/02/2fr-1fr-comparison.png?resize=300%2C161&amp;ssl=1 300w" sizes="auto, (min-width: 735px) 864px, 96vw" /></figure>



<p>Alright. Let&#8217;s continue with the reasons that make Tailwind great for building layouts.</p>


<h3 class="wp-block-heading" id="second-no-need-to-name-layouts">Second: No need to name layouts</h3>


<p>I think layouts are the hardest things to name. I rarely come up with better names than:</p>



<ul class="wp-block-list">
<li>Number + Columns, e.g. <code>.two-columns</code></li>



<li>Semantic names, e.g. <code>.content-sidebar</code></li>
</ul>



<p>But these names don&#8217;t do the layout justice. You can&#8217;t really tell what&#8217;s going on, even if you see <code>.two-columns</code>, because <code>.two-columns</code> can mean a variety of things:</p>



<ul class="wp-block-list">
<li>Two equal columns</li>



<li>Two columns with <code>1fr auto</code></li>



<li>Two columns with <code>auto 1fr</code></li>



<li>Two columns that spans total of 7 &#8220;columns&#8221; and the first object takes up 4 columns while the second takes up 3&#8230;</li>
</ul>



<p>You can already see me tripping up when I try to explain that last one there&#8230;</p>



<p>Instead of forcing ourselves to name the layout, we can let the numbers do the talking — then the whole structure becomes very clear.</p>



<pre rel="HTML" class="wp-block-csstricks-code-block language-markup" data-line=""><code markup="tt">&lt;div class="grid-simple [--cols:7]">
  &lt;div class="[--span:4]"> ... &lt;/div>
  &lt;div class="[--span:3]"> ... &lt;/div>
&lt;/div></code></pre>



<p>The variables paint a picture.</p>



<figure class="wp-block-image size-full"><img data-recalc-dims="1" loading="lazy" decoding="async" width="739" height="213" src="https://i0.wp.com/css-tricks.com/wp-content/uploads/2026/02/4-3.png?resize=739%2C213" alt="Example of a seven-column layout above a two-column layout with equally-sized columns." class="wp-image-392184" srcset="https://i0.wp.com/css-tricks.com/wp-content/uploads/2026/02/4-3.png?w=739&amp;ssl=1 739w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2026/02/4-3.png?resize=300%2C86&amp;ssl=1 300w" sizes="auto, (min-width: 735px) 864px, 96vw" /></figure>


<h3 class="wp-block-heading" id="third-layout-requirements-can-change-depending-on-context">Third: Layout requirements can change depending on context</h3>


<p>A “two-column” layout might have different properties when used in different contexts. Here’s an example.</p>



<figure class="wp-block-image size-full"><img data-recalc-dims="1" loading="lazy" decoding="async" width="735" height="156" src="https://i0.wp.com/css-tricks.com/wp-content/uploads/2026/02/proximity-gap.png?resize=735%2C156&#038;ssl=1" alt="Two two-by-two layouts next to each other. In both cases, the third item wraps to the second line, followed by the fourth item." class="wp-image-392243" srcset="https://i0.wp.com/css-tricks.com/wp-content/uploads/2026/02/proximity-gap.png?w=735&amp;ssl=1 735w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2026/02/proximity-gap.png?resize=300%2C64&amp;ssl=1 300w" sizes="auto, (min-width: 735px) 864px, 96vw" /></figure>



<p>In this example, you can see that:</p>



<ul class="wp-block-list">
<li>A <em>larger</em> <code>gap</code> is used <em>between</em> the I and J groups.</li>



<li>A <em>smaller</em> <code>gap</code> is used <em>within</em> the I and J groups.</li>
</ul>



<p>The difference in <code>gap</code> sizes is subtle, but used to show that the items are of separate groups.</p>



<p>Here&#8217;s an example where this concept is used in a real project. You can see the difference between the gap used within the newsletter container and the gap used between the newsletter and quote containers.</p>



<figure class="wp-block-image size-full"><img data-recalc-dims="1" loading="lazy" decoding="async" width="1301" height="703" src="https://i0.wp.com/css-tricks.com/wp-content/uploads/2026/02/different-gap.png?resize=1301%2C703" alt="A two-column layout for a newsletter signup component with the form as the left column that is wider than the width of the right column, containing content." class="wp-image-392183" srcset="https://i0.wp.com/css-tricks.com/wp-content/uploads/2026/02/different-gap.png?w=1301&amp;ssl=1 1301w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2026/02/different-gap.png?resize=300%2C162&amp;ssl=1 300w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2026/02/different-gap.png?resize=1024%2C553&amp;ssl=1 1024w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2026/02/different-gap.png?resize=768%2C415&amp;ssl=1 768w" sizes="auto, (min-width: 735px) 864px, 96vw" /></figure>



<p>If this sort of layout is only used in one place, we don&#8217;t have to create a modifier class just to change the <code>gap</code> value. We can change it directly.</p>



<pre rel="HTML" class="wp-block-csstricks-code-block language-markup" data-line=""><code markup="tt">&lt;div class="grid-simple [--cols:2] gap-8">
  &lt;div class="grid-simple gap-4 [--cols:2]"> ... &lt;/div>
  &lt;div class="grid-simple gap-4 [--cols:2]"> ... &lt;/div>
&lt;/div></code></pre>


<h4 class="wp-block-heading" id="another-common-example">Another common example</h4>


<p>Let&#8217;s say you have a heading for a marketing section. The heading would look nicer if you are able to vary its <code>max-width</code> so the text isn&#8217;t orphaned.</p>



<p><code>text-balance</code> might work here, but this is often nicer with manual positioning.</p>



<p>Without Tailwind, you might write an inline style for it.</p>



<pre rel="HTML" class="wp-block-csstricks-code-block language-markup" data-line=""><code markup="tt">&lt;h2 class="h2" style="max-width: 12em;">
  Your subscription has been confirmed
&lt;/h2></code></pre>



<p>With Tailwind, you can specify the <code>max-width</code> in a more terse way:</p>



<pre rel="HTML" class="wp-block-csstricks-code-block language-markup" data-line=""><code markup="tt">&lt;h2 class="h2 max-w-[12em]">
  Your subscription has been confirmed
&lt;/h2></code></pre>



<figure class="wp-block-image size-full"><img data-recalc-dims="1" loading="lazy" decoding="async" width="734" height="103" src="https://i0.wp.com/css-tricks.com/wp-content/uploads/2026/02/heading.png?resize=734%2C103&#038;ssl=1" alt="A centered heading in black that says Your subscription has been confirmed." class="wp-image-392182" srcset="https://i0.wp.com/css-tricks.com/wp-content/uploads/2026/02/heading.png?w=734&amp;ssl=1 734w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2026/02/heading.png?resize=300%2C42&amp;ssl=1 300w" sizes="auto, (min-width: 735px) 864px, 96vw" /></figure>


<h3 class="wp-block-heading" id="fourth-responsive-variants-can-be-created-on-the-fly">Fourth: Responsive variants can be created on the fly</h3>


<p><em>&#8220;At which breakpoint would you change your layouts?&#8221;</em> is another factor you&#8217;d want to consider when designing your layouts. I shall term this the <strong>responsive factor</strong> for this section.</p>



<p>Most likely, similar layouts should have the same responsive factor. In that case, it makes sense to group the layouts together into a named layout.</p>



<pre rel="CSS" class="wp-block-csstricks-code-block language-css" data-line=""><code markup="tt">.two-column {
  @apply grid-simple;
  /* --cols: 1 is the default */

  @media (width >= 800px) {
    --cols:2;
  }
}</code></pre>



<p>However, you may have layouts where you want two-column grids on a mobile and a much larger column count on tablets and desktops. This layout style is commonly used in a site footer component.</p>



<p>Since the footer grid is unique, we can add Tailwind&#8217;s responsive variants and change the layout on the fly.</p>



<pre rel="HTML" class="wp-block-csstricks-code-block language-markup" data-line=""><code markup="tt">&lt;div class="grid-simple [--cols:2] md:[--cols:5]">
  &lt;!-- span set to 1 by default so there's no need to specify them -->
  &lt;div> ... &lt;/div>
  &lt;div> ... &lt;/div>
  &lt;div> ... &lt;/div>
  &lt;div> ... &lt;/div>
  &lt;div> ... &lt;/div>
  &lt;div> ... &lt;/div>
&lt;/div></code></pre>



<figure class="wp-block-image size-full"><img data-recalc-dims="1" loading="lazy" decoding="async" width="769" height="339" src="https://i0.wp.com/css-tricks.com/wp-content/uploads/2026/02/footer.png?resize=769%2C339" alt="Example of a footer that adapts to the screen size. It goes from a two-column layout on small screens to a five-column layout on wider screens." class="wp-image-392181" srcset="https://i0.wp.com/css-tricks.com/wp-content/uploads/2026/02/footer.png?w=769&amp;ssl=1 769w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2026/02/footer.png?resize=300%2C132&amp;ssl=1 300w" sizes="auto, (min-width: 735px) 864px, 96vw" /></figure>



<p>Again, we get to create a new layout on the fly without creating an additional modifier class — this keeps our CSS clean and focused.</p>


<h3 class="wp-block-heading" id="unorthodox-tailwind">How to best use Tailwind</h3>


<p>This article is a sample lesson from my course, <a href="https://magicaldevschool.com/courses/unorthodox-tailwind/" rel="noopener">Unorthodox Tailwind</a>, where I show you how to use Tailwind and CSS synergistically.</p>



<p>Personally, I think the best way to use Tailwind is not to litter your HTML with Tailwind utilities, but to create utilities that let you create layouts and styles easily.</p>



<p>I cover much more of that in the course if you&#8217;re interested to find out more!</p>
<hr />
<p><small><a rel="nofollow" href="https://css-tricks.com/4-reasons-that-make-tailwind-great-for-building-layouts/">4 Reasons That Make Tailwind Great for Building Layouts</a> originally published on <a rel="nofollow" href="https://css-tricks.com">CSS-Tricks</a>, which is part of the <a href="https://try.digitalocean.com/css-tricks/?utm_medium=rss&amp;utm_source=css-tricks.com&amp;utm_campaign=family_&amp;utm_content=">DigitalOcean</a> family. You should <a href="https://css-tricks.com/newsletters/">get the newsletter</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://css-tricks.com/4-reasons-that-make-tailwind-great-for-building-layouts/feed/</wfw:commentRss>
			<slash:comments>5</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">392180</post-id>	</item>
		<item>
		<title>Abusing Customizable Selects</title>
		<link>https://css-tricks.com/abusing-customizable-selects/</link>
					<comments>https://css-tricks.com/abusing-customizable-selects/#comments</comments>
		
		<dc:creator><![CDATA[Patrick Brosset]]></dc:creator>
		<pubDate>Wed, 11 Mar 2026 13:59:15 +0000</pubDate>
				<category><![CDATA[Articles]]></category>
		<category><![CDATA[CSS functions]]></category>
		<category><![CDATA[html elements]]></category>
		<category><![CDATA[select]]></category>
		<guid isPermaLink="false">https://css-tricks.com/?p=392463</guid>

					<description><![CDATA[<p>Let’s go over a few demos using the new customizable <code>&#60;select&#62;</code> feature that may be wild, but also give us a great chance to learn new things in CSS.</p>
<hr />
<p><small><a rel="nofollow" href="https://css-tricks.com/abusing-customizable-selects/">Abusing Customizable Selects</a> originally published on <a rel="nofollow" href="https://css-tricks.com">CSS-Tricks</a>, which is part of the <a href="https://try.digitalocean.com/css-tricks/?utm_medium=rss&amp;utm_source=css-tricks.com&amp;utm_campaign=family_&amp;utm_content=">DigitalOcean</a> family. You should <a href="https://css-tricks.com/newsletters/">get the newsletter</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p>Web browsers ship new features all the time, but what fun is it if we can’t build silly and fun things with them?</p>



<p>In this article, let’s go over a few demos that I’ve made by using <a href="https://css-tricks.com/the-selectmenu-element-is-no-morelong-live-select/">the new customizable <code>&lt;select&gt;</code> feature</a>, and walk through the main steps and techniques that I’ve used to implement them.</p>



<span id="more-392463"></span>



<p>I hope they get you as excited as I am about custom selects, and give you just about enough knowledge to get started creating your own. Yours might be more, you know, useful than mine, and probably for good reasons, but I like going a little bit overboard on silly ideas because that gives me a better chance to learn.</p>



<p>Before we start, a word about browser support: the demos in this article only run on recent Chromium-based browsers because that’s where customizable selects are implemented right now. However, this feature is designed in a way that doesn’t break non-supporting browsers. After all, a customized <code>&lt;select&gt;</code> element is still a <code>&lt;select&gt;</code> element. So, if the browser you’re using doesn’t support customizable selects, you’ll just see normal selects and options in these demos, and that’s great. It’ll just be a lot less fun.</p>


<h3 class="wp-block-heading" id="curved-stack-of-folders">Curved stack of folders</h3>


<p>Let’s get started with the first demo: a stack of folders to pick from, with a twist:</p>



<figure class="wp-block-video ticss-32c95c39"><video height="964" style="aspect-ratio: 744 / 964;" width="744" controls src="https://css-tricks.com/wp-content/uploads/2026/02/css-tricks-custom-select-folder-stack.mp4" playsinline></video></figure>



<p>We&#8217;ll start with some HTML code first. We don’t need a lot of complicated markup here because each option is just the name of the folder. We can draw the folder icons later with CSS only.</p>



<pre rel="HTML" class="wp-block-csstricks-code-block language-markup" data-line=""><code markup="tt">&lt;select>
  &lt;option value="documents">&lt;span>Documents&lt;/span>&lt;/option>
  &lt;option value="photos">&lt;span>Photos&lt;/span>&lt;/option>
  &lt;option value="music">&lt;span>Music&lt;/span>&lt;/option>
  &lt;option value="videos">&lt;span>Videos&lt;/span>&lt;/option>
  &lt;option value="downloads">&lt;span>Downloads&lt;/span>&lt;/option>
  &lt;option value="desktop">&lt;span>Desktop&lt;/span>&lt;/option>
  &lt;option value="projects">&lt;span>Projects&lt;/span>&lt;/option>
  &lt;option value="backups">&lt;span>Backups&lt;/span>&lt;/option>
  &lt;option value="trash">&lt;span>Trash&lt;/span>&lt;/option>
&lt;/select></code></pre>



<p>You’ll notice that we’ve used <code>&lt;span&gt;</code> elements inside the <code>&lt;option&gt;</code> elements, to wrap each folder name. That’s going to be useful for styling the selected folder name later. Even though this is just a <code>&lt;span&gt;</code>, being able to do this is quite a big change from what was previously possible.</p>



<p>That&#8217;s because, up until very recently, <code>&lt;option&gt;</code>s could only contain text, because that’s the only thing that could appear inside options of a select. The HTML parser has now been relaxed to allow for a lot more HTML elements to be embedded in options. Browsers that don’t support customizable selects will just ignore these extra elements and display the text only.</p>



<p>So, here’s what our stack of folders looks like so far:</p>



<figure class="wp-block-image size-full ticss-6d55ca3f"><img data-recalc-dims="1" loading="lazy" decoding="async" width="246" height="563" src="https://i0.wp.com/css-tricks.com/wp-content/uploads/2026/02/image-7.png?resize=246%2C563&#038;ssl=1" alt="An unstyled select element with expanded options." class="wp-image-392465" srcset="https://i0.wp.com/css-tricks.com/wp-content/uploads/2026/02/image-7.png?w=246&amp;ssl=1 246w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2026/02/image-7.png?resize=131%2C300&amp;ssl=1 131w" sizes="auto, (min-width: 735px) 864px, 96vw" /></figure>



<p>Next up, and this is the most important thing you’ll want to do to opt into the customizable select feature: let’s reset the default appearance of the select and its dropdown part, by using the <a href="https://css-tricks.com/almanac/pseudo-selectors/p/picker/"><code>::picker()</code></a> pseudo-element:</p>



<pre rel="CSS" class="wp-block-csstricks-code-block language-css" data-line=""><code markup="tt">select,
::picker(select) {
  appearance: base-select;
}</code></pre>



<p>This CSS rule does a lot for us: it unlocks full styling capabilities for the entire select, including its button, dropdown, and options. Without this opt-in, you get a standard select.</p>



<p>Now let’s style the select, starting with its button part. First, we&#8217;ll get rid of the picker icon by using the new <a href="https://css-tricks.com/almanac/pseudo-selectors/p/picker-icon/"><code>::picker-icon</code></a> pseudo-element to hide it:</p>



<pre rel="CSS" class="wp-block-csstricks-code-block language-css" data-line=""><code markup="tt">select::picker-icon {
  display: none;
}</code></pre>



<p>Next, let’s add a bit more styles to create a nice-looking button:</p>



<pre rel="CSS" class="wp-block-csstricks-code-block language-css" data-line=""><code markup="tt">select {
  background: linear-gradient(
    135deg,
    rgba(40, 40, 50, 0.4) 0%,
    rgba(60, 60, 70, 0.25) 50%,
    rgba(50, 50, 60, 0.35) 100%
  );
  backdrop-filter: blur(12px) saturate(180%);
  box-shadow:
    0 8px 32px rgba(0, 0, 0, 0.2),
    inset 0 1px 1px rgba(255, 255, 255, 0.15),
    inset 0 -1px 1px rgba(0, 0, 0, 0.1);
  border: 1px solid rgba(255, 255, 255, 0.2);
  color: white;
  min-inline-size: 12rem;
}</code></pre>



<p>And here is our new select button:</p>



<figure class="wp-block-image size-full ticss-8950737a"><img data-recalc-dims="1" loading="lazy" decoding="async" width="444" height="97" src="https://i0.wp.com/css-tricks.com/wp-content/uploads/2026/02/image-6.png?resize=444%2C97&#038;ssl=1" alt="A custom select button with an opaque background, a folder icon, and a text label called Music." class="wp-image-392464" srcset="https://i0.wp.com/css-tricks.com/wp-content/uploads/2026/02/image-6.png?w=444&amp;ssl=1 444w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2026/02/image-6.png?resize=300%2C66&amp;ssl=1 300w" sizes="auto, (min-width: 735px) 864px, 96vw" /></figure>



<p>Now let’s turn our attention to the dropdown part since this is where the magic happens.</p>



<p>In a select, the dropdown contains all the options and appears when you click on the button. A lot of browser default styles apply to it already to set its <code>position</code>, <code>background-color</code>, <code>margin</code>, and more. So, we’ll have to disable and override a bunch of stuff.</p>



<p>In our demo, we don’t want the dropdown to be visible at all. Instead, we want each individual option (each folder in this case) to appear as if floating above the page, without a container element.</p>



<p>To do this, let’s use the <code>::picker(select)</code> pseudo-element to set our styles:</p>



<pre rel="CSS" class="wp-block-csstricks-code-block language-css" data-line=""><code markup="tt">::picker(select) {
  background: transparent;
  border: none;
  box-shadow: none;
  overflow: visible;
}</code></pre>



<p>And with this, the dropdown isn’t visible anymore and it no longer constrains the options or clips them if they overflow the dropdown area.</p>



<p>This gives us the following improvements:</p>



<figure class="wp-block-image size-full ticss-f85c3247"><img data-recalc-dims="1" loading="lazy" decoding="async" width="255" height="456" src="https://i0.wp.com/css-tricks.com/wp-content/uploads/2026/02/image-8.png?resize=255%2C456" alt="A select element with expanded options formatted as text in a single vertical list. An option called music is selected and represents the top picker button which is styled with a folder icon to the left of the text label." class="wp-image-392466" srcset="https://i0.wp.com/css-tricks.com/wp-content/uploads/2026/02/image-8.png?w=255&amp;ssl=1 255w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2026/02/image-8.png?resize=168%2C300&amp;ssl=1 168w" sizes="auto, (min-width: 735px) 864px, 96vw" /></figure>



<p>It’s now time to turn our attention to the option elements. First, let’s replace the checkmark icon with a little disc icon instead by using the <a href="https://css-tricks.com/almanac/pseudo-selectors/c/checkmark/"><code>::checkmark</code></a> pseudo-element:</p>



<pre rel="CSS" class="wp-block-csstricks-code-block language-css" data-line=""><code markup="tt">option::checkmark {
  content: "●";
  color: #222;
}</code></pre>



<p>This pseudo-element makes it easy to change the shape, the color, or even the size of the checkmark.</p>



<p>Let’s also add an additional pseudo-element to each option, by using <code>option::before</code>, to display a folder emoji next to each option. And, with a pinch more CSS fine tuning, we end up with this:</p>



<figure class="wp-block-image size-full ticss-24375411"><img data-recalc-dims="1" loading="lazy" decoding="async" width="385" height="818" src="https://i0.wp.com/css-tricks.com/wp-content/uploads/2026/02/image-10.png?resize=385%2C818" alt="A vertical column of folder icons expanded as options from a select element. Each folder includes a label on the right." class="wp-image-392468" srcset="https://i0.wp.com/css-tricks.com/wp-content/uploads/2026/02/image-10.png?w=385&amp;ssl=1 385w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2026/02/image-10.png?resize=141%2C300&amp;ssl=1 141w" sizes="auto, (min-width: 735px) 864px, 96vw" /></figure>



<p>We now have a list of folders which floats on top of the page when we click the select button. It works like any other select, too, either with the mouse, or with the keyboard, so we can just thank the browser for maintaining the accessibility of the input while we’re having fun with CSS.</p>



<p>Let’s now apply some CSS transformation to make the stack of folders a little curvy, so it looks cooler.</p>



<p>To achieve this, we’ll need one more piece of new CSS syntax which, unfortunately, isn’t yet widely available: the <a href="https://css-tricks.com/almanac/functions/s/sibling-index/"><code>sibling-index()</code></a> function. This function returns the index of the element within its siblings. The <a href="https://css-tricks.com/almanac/functions/s/sibling-count/"><code>sibling-count()</code></a> function also exists, and it returns the total number of siblings, but we won’t need it here.</p>



<p>Having access to the index of the current element within its siblings means that we can style each option depending on its position within the select dropdown. This is exactly what we need to make the options appear at a gradually larger angle.</p>



<p>Here is the code:</p>



<pre rel="CSS" class="wp-block-csstricks-code-block language-css" data-line=""><code markup="tt">option {
  --rotation-offset: -4deg;
  rotate: calc(sibling-index() * var(--rotation-offset));
}</code></pre>



<p>In this code snippet, we first create a custom property called <code>--rotation-offset</code>, which defines the angle by which each option should rotate, with respect to the previous option. We then use this with the <a href="https://css-tricks.com/almanac/properties/r/rotate/"><code>rotate</code></a> property, multiplying its value by <code>sibling-index()</code>. That way, the first option is rotated by -4 degrees, the second one by -8 degrees, the third by -12 degrees, and so on.</p>



<p>Now, that’s not enough on its own to create the illusion of a curved stack of folders because each folder rotates around its own point of origin, which is located in the top-left corner of each folder by default. Right now, we get this:</p>



<figure class="wp-block-image size-full ticss-4a92d36f"><img data-recalc-dims="1" loading="lazy" decoding="async" width="363" height="864" src="https://i0.wp.com/css-tricks.com/wp-content/uploads/2026/02/image-15.png?resize=363%2C864" alt="A single column of folder icons with labels on the right. Each folder is slightly rotated more as the list goes down." class="wp-image-392473" srcset="https://i0.wp.com/css-tricks.com/wp-content/uploads/2026/02/image-15.png?w=363&amp;ssl=1 363w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2026/02/image-15.png?resize=126%2C300&amp;ssl=1 126w" sizes="auto, (min-width: 735px) 864px, 96vw" /></figure>



<p>Let’s use the <a href="https://css-tricks.com/almanac/properties/t/transform-origin/"><code>transform-origin</code></a> property to set a shared point of origin around which all options will rotate. Because <code>transform-origin</code> is relative to each individual element, we need to use the <code>sibling-index()</code> function again to move all origin points up and to the right so they’re all in the same spot:</p>



<pre rel="CSS" class="wp-block-csstricks-code-block language-css" data-line=""><code markup="tt">option {
  --rotation-offset: -4deg;
  rotate: calc(sibling-index() * var(--rotation-offset));
  transform-origin: right calc(sibling-index() * -1.5rem);
}</code></pre>



<p>And with this, we get the following result:</p>



<figure class="wp-block-image size-full ticss-c7e6d4e9"><img data-recalc-dims="1" loading="lazy" decoding="async" width="508" height="886" src="https://i0.wp.com/css-tricks.com/wp-content/uploads/2026/02/image-14.png?resize=508%2C886" alt="A vertical column of folders with labels on the right fanned out and curving towards the right." class="wp-image-392472" srcset="https://i0.wp.com/css-tricks.com/wp-content/uploads/2026/02/image-14.png?w=508&amp;ssl=1 508w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2026/02/image-14.png?resize=172%2C300&amp;ssl=1 172w" sizes="auto, (min-width: 735px) 864px, 96vw" /></figure>



<p>The final step is to animate the options. It looks great as it is, but we want the stack of folders to get gradually curved until it reaches its final shape. That’ll make it a lore more lively and fun to interact with.</p>



<p>Let’s reset the option’s rotation by default, and apply a transition with a nice elastic <a href="https://css-tricks.com/almanac/functions/c/cubic-bezier/">easing function</a>:</p>



<pre rel="CSS" class="wp-block-csstricks-code-block language-css" data-line=""><code markup="tt">option {
  rotate: 0deg;
  transition: rotate 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);
}</code></pre>



<p>And now, let’s apply the right rotation angle only when the select is open:</p>



<pre rel="CSS" class="wp-block-csstricks-code-block language-css" data-line=""><code markup="tt">select:open option {
  rotate: calc(sibling-index() * -1 *  var(--rotation-offset));
}</code></pre>



<p>Unfortunately, the above is not enough. By default, CSS transitions are not triggered when an element appears, which is the case for our options. Thankfully, there’s a fix for this issue: the <a href="https://css-tricks.com/almanac/rules/s/starting-style/"><code>@starting-style</code></a> at-rule. This at-rule lets us define the initial state of the options, making it possible for the transition to play right when the options appear:</p>



<pre rel="CSS" class="wp-block-csstricks-code-block language-css" data-line=""><code markup="tt">@starting-style {
  select:open option {
    rotate: 0deg;
  }
}</code></pre>



<p>One more thing to make it even nicer. Let’s delay each transition relative to the previous one to make it look like each folder comes in slightly after the one before it. To achieve this, let’s use the <code>sibling-index()</code> function once more, as a multiplier to a short transition delay:</p>



<pre rel="CSS" class="wp-block-csstricks-code-block language-css" data-line=""><code markup="tt">option {
  transition-delay: calc((sibling-index() - 1) * 0.01s);
}</code></pre>



<p>We now have an animated, curved, stack of folders implemented with a <code>&lt;select&gt;</code> element! Check out the demo and code in the next CodePen:</p>



<div class="wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper"><iframe id="cp_embed_dPXdgae" src="//codepen.io/anon/embed/preview/dPXdgae?height=450&amp;theme-id=1&amp;slug-hash=dPXdgae&amp;default-tab=result" height="450" scrolling="no" frameborder="0" allowfullscreen allowpaymentrequest name="CodePen Embed dPXdgae" title="CodePen Embed dPXdgae" class="cp_embed_iframe" style="width:100%;overflow:hidden">CodePen Embed Fallback</iframe></div>



<p>CSS gains a lot of new capabilities each year. I hope this demo walk through helped you get a better understanding of some of these new capabilities. Building it helped me understand a lot of new, to me, concepts. It also got me very excited about the customizable select feature. So much, that I created other demos too. So, let’s look at two more of them. This time though, we’ll go quicker and only highlight the most important parts.</p>


<h3 class="wp-block-heading" id="fanned-deck-of-cards">Fanned deck of cards</h3>


<p>For our second demo, we’ll create a card picker, which opens up in a fanned deck fashion:</p>



<figure class="wp-block-video"><video height="672" style="aspect-ratio: 1060 / 672;" width="1060" controls src="https://css-tricks.com/wp-content/uploads/2026/02/css-tricks-custom-select-cards.mp4" playsinline></video></figure>



<p>The HTML markup for this demo is a little different than for the previous one. Each card has a bit of content to display, so let’s create a couple of <code>&lt;span&gt;</code> elements to each option:</p>



<pre rel="HTML" class="wp-block-csstricks-code-block language-markup" data-line=""><code markup="tt">&lt;option class="red" value="QH">
  &lt;span class="rank">Q&lt;/span>
  &lt;span class="suit">&#x2665;&lt;/span>
&lt;/option></code></pre>



<p>The other interesting thing about the HTML code we’ll use here, is the addition of an empty <code>&lt;button&gt;</code> element right below the <code>&lt;select&gt;</code> opening tag:</p>



<pre rel="HTML" class="wp-block-csstricks-code-block language-markup" data-line=""><code markup="tt">&lt;select>
  &lt;button>&lt;/button>
  &lt;option>…&lt;/option>
  &lt;!-- ... -->
&lt;/select></code></pre>



<p>This empty <code>&lt;button></code> serves a very specific purpose: it prevents the default <a href="https://developer.mozilla.org/docs/Web/HTML/Reference/Elements/selectedcontent" rel="noopener"><code>&lt;selectedcontent></code></a> behavior from happening.</p>



<p>In a customized select, the browser automatically displays the currently selected option&#8217;s content (in this case, the card face) in the button area of the select. And it does this by creating an element named <code>&lt;selectedcontent&gt;</code> which mirrors the selected option. But, in our demo, we want the button to always show the back of the deck of cards, not the selected card. To achieve this, we override the default behavior by introducing our own <code>&lt;button&gt;</code>. This tells the browser not to insert its own <code>&lt;selectedcontent&gt;</code> element and lets us style the <code>&lt;select&gt;</code> element:</p>



<pre rel="CSS" class="wp-block-csstricks-code-block language-css" data-line=""><code markup="tt">select {
  background:
    /* Diamond pattern overlay */
    repeating-linear-gradient(45deg,
      transparent,
      transparent 1vmin,
      rgba(255, 255, 255, 0.05) 1vmin,
      rgba(255, 255, 255, 0.05) 2vmin),
    repeating-linear-gradient(-45deg,
      transparent,
      transparent 1vmin,
      rgba(255, 255, 255, 0.05) 1vmin,
      rgba(255, 255, 255, 0.05) 2vmin),
    /* Base gradient */
    linear-gradient(135deg, #8b0000 0%, #dc143c 50%, #8b0000 100%);
}</code></pre>



<figure class="wp-block-image size-full"><img data-recalc-dims="1" loading="lazy" decoding="async" width="703" height="518" src="https://i0.wp.com/css-tricks.com/wp-content/uploads/2026/02/image-13.png?resize=703%2C518" alt="A single card with its back showing in red." class="wp-image-392471" srcset="https://i0.wp.com/css-tricks.com/wp-content/uploads/2026/02/image-13.png?w=703&amp;ssl=1 703w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2026/02/image-13.png?resize=300%2C221&amp;ssl=1 300w" sizes="auto, (min-width: 735px) 864px, 96vw" /></figure>



<p>Now, for the dropdown part, just like in the previous demo, we don’t want the dropdown container element to be visible, so we’ll also override the default background, border, and overflow styles like we did before.</p>



<p>More importantly, the position of the deck of cards, when opened, is very important. We want it to fan out from the deck itself and remain centered above it.</p>



<p>In a customizable select, the dropdown part, i.e., the <code>::picker(select)</code> pseudo-element, is positioned relative to the button part thanks to <a href="https://css-tricks.com/css-anchor-positioning-guide/">anchor positioning</a>, which is great because we can override it!</p>



<p>In our case, let’s override the alignment relative to the anchor, which is the button, by using the <a href="https://css-tricks.com/almanac/properties/p/position-area/"><code>position-area</code></a> property:</p>



<pre rel="CSS" class="wp-block-csstricks-code-block language-css" data-line=""><code markup="tt">::picker(select) {
  position-area: center center;
  inset: 0;
}</code></pre>



<p>We’re also setting the <code>inset</code> property to <code>0</code> here. This sets all <code>top</code>, <code>right</code>, <code>bottom</code>, and <code>left</code> properties to <code>0</code> in a single declaration, which makes the dropdown part able to use the entire available space, rather than being constrained by the browser to appear on the side of the select button.</p>



<p>Finally, let’s make the cards appear side by side, rather than above each other:</p>



<pre rel="CSS" class="wp-block-csstricks-code-block language-css" data-line=""><code markup="tt">select:open::picker(select) {
  display: flex;
}</code></pre>



<p>When the select element is open and the options are visible, we now see this:</p>



<figure class="wp-block-image size-full"><img data-recalc-dims="1" loading="lazy" decoding="async" width="937" height="290" src="https://i0.wp.com/css-tricks.com/wp-content/uploads/2026/02/image-9.png?resize=937%2C290" alt="Nice cards lined up in a single row. Each card slightly overlaps." class="wp-image-392467" srcset="https://i0.wp.com/css-tricks.com/wp-content/uploads/2026/02/image-9.png?w=937&amp;ssl=1 937w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2026/02/image-9.png?resize=300%2C93&amp;ssl=1 300w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2026/02/image-9.png?resize=768%2C238&amp;ssl=1 768w" sizes="auto, (min-width: 735px) 864px, 96vw" /></figure>



<p>The next step is to rotate each card so the options appear in a fanned out way, with the center card straight, the cards to the left gradually more rotated towards the left, and the cards to the right rotated towards the right.</p>



<p>To do this, you’ve guessed it, we’ll use the <code>sibling-index()</code> property again. We’ll also use the <code>sibling-count()</code> property this time:</p>



<pre rel="CSS" class="wp-block-csstricks-code-block language-css" data-line=""><code markup="tt">option {
  --card-fan-rotation: 7deg;
  --card-fan-spread: -11vmin;
  --option-index: calc(sibling-index() - 1);
  --center: calc(sibling-count() / 2);
  --offset-from-center: calc(var(--option-index) - var(--center));

  rotate: calc(var(--offset-from-center) * var(--card-fan-rotation));
  translate: calc(var(--offset-from-center) * var(--card-fan-spread)) 0;
  transform-origin: center 75vmin;
}</code></pre>



<p>In the above code snippet, we’re calculating the offset of each card relative to the center card, and we’re using this to rotate each card by increments of 7 degrees. For example, in a deck with 9 cards, the left-most card (i.e., the first card) will get a -4 offset, and will be rotated by <code>-4 * 7 = -28</code> degrees, while the right-most card will be rotated by 28 degrees.</p>



<p>We also use the <code>translate</code> property to bring the cards close together into a fan, and the `transform-origin` property to make it all look perfect.</p>



<figure class="wp-block-image size-full"><img data-recalc-dims="1" loading="lazy" decoding="async" width="937" height="333" src="https://i0.wp.com/css-tricks.com/wp-content/uploads/2026/02/image-11.png?resize=937%2C333" alt="Nice cards fanned out in a subtle arc." class="wp-image-392469" srcset="https://i0.wp.com/css-tricks.com/wp-content/uploads/2026/02/image-11.png?w=937&amp;ssl=1 937w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2026/02/image-11.png?resize=300%2C107&amp;ssl=1 300w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2026/02/image-11.png?resize=768%2C273&amp;ssl=1 768w" sizes="auto, (min-width: 735px) 864px, 96vw" /></figure>



<p>&nbsp;Finally, let’s bring it all together by animating the opening of the deck. To do this, we can define a CSS transition on the custom <code>--card-fan-rotation</code> property. Animating it from 0 to 7 degrees is all we need to create the illusion we’re after. Animating a custom property takes a couple of steps.</p>



<p>First, let’s define the custom property’s type, so that the browser can animate it correctly:</p>



<pre rel="CSS" class="wp-block-csstricks-code-block language-css" data-line=""><code markup="tt">@property --card-fan-rotation {
  syntax: '&lt;angle>';
  inherits: false;
  initial-value: 7deg;
}</code></pre>



<p>Second, let’s use a <code>@starting-style</code> at-rule, like in the previous demo, to allow the CSS transition to play when the options appear:</p>



<pre rel="CSS" class="wp-block-csstricks-code-block language-css" data-line=""><code markup="tt">@starting-style {
  select:open option {
  --card-fan-rotation: 0deg;
  }
}</code></pre>



<p>Then, set the starting rotation angle when the select element is closed, and define the CSS transition:</p>



<pre rel="CSS" class="wp-block-csstricks-code-block language-css" data-line=""><code markup="tt">option {
  --card-fan-rotation: 0deg;
  transition: --card-fan-rotation 0.2s ease-out;
}</code></pre>



<p>And, finally, let’s set the final angle when the select is opened:</p>



<pre rel="CSS" class="wp-block-csstricks-code-block language-css" data-line=""><code markup="tt">select:open option {
  --card-fan-rotation: initial;
}</code></pre>



<p>We can use the `initial` value above instead of hard-coding the <code>7deg</code> value again, since it’s already defined as the initial value in the <a href="https://css-tricks.com/almanac/rules/p/property/"><code>@property</code></a> rule above.</p>



<p>That’s it, our deck of cards, with animated opening, is now ready! Check out the complete code and live demo in this CodePen:</p>



<div class="wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper"><iframe id="cp_embed_ZYONReV" src="//codepen.io/anon/embed/preview/ZYONReV?height=450&amp;theme-id=1&amp;slug-hash=ZYONReV&amp;default-tab=result" height="450" scrolling="no" frameborder="0" allowfullscreen allowpaymentrequest name="CodePen Embed ZYONReV" title="CodePen Embed ZYONReV" class="cp_embed_iframe" style="width:100%;overflow:hidden">CodePen Embed Fallback</iframe></div>



<p>It’s amazing to me how far customizable selects allow you to push things. You don’t only get to override the way the button and its options look, you get to change how everything is positioned, and even animated.</p>



<p>Let’s close with one final demo.</p>


<h3 class="wp-block-heading" id="radial-emoji-picker">Radial emoji picker</h3>


<figure class="wp-block-video ticss-00635ff7"><video height="600" style="aspect-ratio: 600 / 600;" width="600" controls src="https://css-tricks.com/wp-content/uploads/2026/02/css-tricks-custom-select-emojis.mp4" playsinline></video></figure>



<p>Just like in the previous demo, here we want the emojis to be centered around the select button. To achieve this, let’s override the default anchor positioning of the dropdown part.</p>



<p>This time, we’ll use the <a href="https://css-tricks.com/almanac/functions/a/anchor/"><code>anchor()</code></a> function to set the top and left coordinates of the dropdown container:</p>



<pre rel="CSS" class="wp-block-csstricks-code-block language-css" data-line=""><code markup="tt">::picker(select) {
  top: calc(anchor(top) - var(--radius));
  left: calc(anchor(left) - var(--radius));
  width: calc(var(--radius) * 2 + var(--option-size));
  height: calc(var(--radius) * 2 + var(--option-size));
}</code></pre>



<p>In this code snippet, the <code>--radius</code> property is the radius of the circle of emojis. And, since customizable selects already use anchor positioning, we can use the <code>anchor()</code> function to position the dropdown relative to the button.</p>



<p>Now we need to position the options in a circle, inside the dropdown. As it turns out, CSS knows trigonometry now, too, so we’ll use the <a href="https://css-tricks.com/almanac/functions/c/cos/"><code>cos()</code></a> and <a href="https://css-tricks.com/almanac/functions/s/sin/"><code>sin()</code></a> functions together with the <code>sibling-index()</code> and <code>sibling-count()</code> functions:</p>



<pre rel="CSS" class="wp-block-csstricks-code-block language-css" data-line=""><code markup="tt">option {
  position: absolute;
  --angle: calc((sibling-index() - 2) * (360deg / (sibling-count() - 1)) - 90deg);
  top: 50%;
  left: 50%;
  translate:
    calc(-50% + cos(var(--angle)) * var(--radius)) calc(-50% + sin(var(--angle)) * var(--radius));
}</code></pre>



<p>And there we are:</p>



<figure class="wp-block-image size-full ticss-5688fc58"><img data-recalc-dims="1" loading="lazy" decoding="async" width="496" height="522" src="https://i0.wp.com/css-tricks.com/wp-content/uploads/2026/02/image-12.png?resize=496%2C522" alt="Circular options with icons around another circular item in the center with a star icon." class="wp-image-392470" srcset="https://i0.wp.com/css-tricks.com/wp-content/uploads/2026/02/image-12.png?w=496&amp;ssl=1 496w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2026/02/image-12.png?resize=285%2C300&amp;ssl=1 285w" sizes="auto, (min-width: 735px) 864px, 96vw" /></figure>



<p>The final demo also contains a bit of code for animating the opening of the options, but we won’t dig into the details in this article.</p>



<p>To learn more and play with the live demo, check out this CodePen:</p>



<div class="wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper"><iframe id="cp_embed_PwzvBGB" src="//codepen.io/anon/embed/preview/PwzvBGB?height=450&amp;theme-id=1&amp;slug-hash=PwzvBGB&amp;default-tab=result" height="450" scrolling="no" frameborder="0" allowfullscreen allowpaymentrequest name="CodePen Embed PwzvBGB" title="CodePen Embed PwzvBGB" class="cp_embed_iframe" style="width:100%;overflow:hidden">CodePen Embed Fallback</iframe></div>


<h3 class="wp-block-heading" id="wrapping-up">Wrapping up</h3>


<p>That’s it for now. I hope these demos have given you a bit more of an understanding for how customizable selects are customized, and some excitement for actually using the feature in a real project.</p>



<p>Keep in mind, even when customized, the element is still a <code>&lt;select&gt;</code> and will work just fine in non-supporting browsers. So, even if the feature is still in its early days, you can use it as a great progressive enhancement.</p>
<hr />
<p><small><a rel="nofollow" href="https://css-tricks.com/abusing-customizable-selects/">Abusing Customizable Selects</a> originally published on <a rel="nofollow" href="https://css-tricks.com">CSS-Tricks</a>, which is part of the <a href="https://try.digitalocean.com/css-tricks/?utm_medium=rss&amp;utm_source=css-tricks.com&amp;utm_campaign=family_&amp;utm_content=">DigitalOcean</a> family. You should <a href="https://css-tricks.com/newsletters/">get the newsletter</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://css-tricks.com/abusing-customizable-selects/feed/</wfw:commentRss>
			<slash:comments>4</slash:comments>
		
		<enclosure url="https://css-tricks.com/wp-content/uploads/2026/02/css-tricks-custom-select-folder-stack.mp4" length="410481" type="video/mp4" />
<enclosure url="https://css-tricks.com/wp-content/uploads/2026/02/css-tricks-custom-select-cards.mp4" length="531678" type="video/mp4" />
<enclosure url="https://css-tricks.com/wp-content/uploads/2026/02/css-tricks-custom-select-emojis.mp4" length="251294" type="video/mp4" />

		<post-id xmlns="com-wordpress:feed-additions:1">392463</post-id>	</item>
		<item>
		<title>The Value of z-index</title>
		<link>https://css-tricks.com/the-value-of-z-index/</link>
					<comments>https://css-tricks.com/the-value-of-z-index/#comments</comments>
		
		<dc:creator><![CDATA[Amit Sheen]]></dc:creator>
		<pubDate>Mon, 09 Mar 2026 14:20:01 +0000</pubDate>
				<category><![CDATA[Articles]]></category>
		<category><![CDATA[css properties]]></category>
		<category><![CDATA[stacking contexts]]></category>
		<guid isPermaLink="false">https://css-tricks.com/?p=392843</guid>

					<description><![CDATA[<p>How we look at the stacking order of our projects, how we choose <code>z-index</code> values, and more importantly, the implications of those choices.</p>
<hr />
<p><small><a rel="nofollow" href="https://css-tricks.com/the-value-of-z-index/">The Value of z-index</a> originally published on <a rel="nofollow" href="https://css-tricks.com">CSS-Tricks</a>, which is part of the <a href="https://try.digitalocean.com/css-tricks/?utm_medium=rss&amp;utm_source=css-tricks.com&amp;utm_campaign=family_&amp;utm_content=">DigitalOcean</a> family. You should <a href="https://css-tricks.com/newsletters/">get the newsletter</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p>The <a href="https://css-tricks.com/almanac/properties/z/z-index/"><code>z-index</code></a> property is one of the most important tools any UI developer has at their disposal, as it allows you to control the stacking order of elements on a webpage. Modals, toasts, popups, dropdowns, tooltips, and many other common elements rely on it to ensure they appear above other content.</p>



<p>While most resources focus on the technical details or the common pitfalls of the <strong>Stacking Context</strong> (we&#8217;ll get to that in a moment&#8230;), I think they miss one of the most important and potentially chaotic aspects of <code>z-index</code>: <strong>the value</strong>.</p>



<span id="more-392843"></span>



<figure class="wp-block-image size-full"><img data-recalc-dims="1" loading="lazy" decoding="async" width="1920" height="1080" src="https://i0.wp.com/css-tricks.com/wp-content/uploads/2026/03/s_8926F490CDA74C2CA8D0B6E99405A055C6BF40F4CCE75686F0BAADE6ECBD39FB_1772115867944_image.png?resize=1920%2C1080" alt="Screenshot of a code editor with a large number of z-index values, many of which include the !important keyword." class="wp-image-392844" srcset="https://i0.wp.com/css-tricks.com/wp-content/uploads/2026/03/s_8926F490CDA74C2CA8D0B6E99405A055C6BF40F4CCE75686F0BAADE6ECBD39FB_1772115867944_image.png?w=1920&amp;ssl=1 1920w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2026/03/s_8926F490CDA74C2CA8D0B6E99405A055C6BF40F4CCE75686F0BAADE6ECBD39FB_1772115867944_image.png?resize=300%2C169&amp;ssl=1 300w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2026/03/s_8926F490CDA74C2CA8D0B6E99405A055C6BF40F4CCE75686F0BAADE6ECBD39FB_1772115867944_image.png?resize=1024%2C576&amp;ssl=1 1024w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2026/03/s_8926F490CDA74C2CA8D0B6E99405A055C6BF40F4CCE75686F0BAADE6ECBD39FB_1772115867944_image.png?resize=768%2C432&amp;ssl=1 768w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2026/03/s_8926F490CDA74C2CA8D0B6E99405A055C6BF40F4CCE75686F0BAADE6ECBD39FB_1772115867944_image.png?resize=1536%2C864&amp;ssl=1 1536w" sizes="auto, (min-width: 735px) 864px, 96vw" /></figure>



<p>In most projects, once you hit a certain size, the <code>z-index</code> values become a mess of <a href="https://css-tricks.com/magic-numbers-in-css/">&#8220;magic numbers&#8221;</a>, a chaotic battlefield of values, where every team tries to outdo the others with higher and higher numbers.</p>


<h3 class="wp-block-heading" id="how-this-idea-started">How This Idea Started</h3>


<p>I saw this line on a pull request a few years ago:</p>



<pre rel="CSS" class="wp-block-csstricks-code-block language-css" data-line=""><code markup="tt">z-index: 10001;</code></pre>



<p>I thought to myself, &#8220;Wow, that&#8217;s a big number! I wonder why they chose that specific value?&#8221; When I asked the author, they said: &#8220;Well, I just wanted to make sure it was above all the other elements on the page, so I chose a high number.&#8221;</p>



<p>This got me thinking about how we look at the stacking order of our projects, how we choose <code>z-index</code> values, and more importantly, the implications of those choices.</p>


<h3 class="wp-block-heading" id="the-fear-of-being-hidden">The Fear of Being Hidden</h3>


<p>The core issue isn&#8217;t a technical one, but a lack of visibility. In a large project with multiple teams, you don&#8217;t always know what else is floating on the screen. There might be a toast notification from Team A, a cookie banner from Team B, or a modal from the marketing SDK.</p>



<p>The developer&#8217;s logic was simple in this case: <strong>&#8220;If I use a really high number, surely it will be on top.&#8221;</strong></p>



<p>This is how we end up with magic numbers, these arbitrary values that aren&#8217;t connected to the rest of the application. They are guesses made in isolation, hoping to win the &#8220;arms race&#8221; of <code>z-index</code> values.</p>


<h3 class="wp-block-heading" id="we-re-not-talking-about-stacking-context-but-">We&#8217;re Not Talking About Stacking Context&#8230; But&#8230;</h3>


<p>As I mentioned at the beginning, there are many resources that cover <code>z-index</code> in the context of the Stacking Context. In this article, we won&#8217;t cover that topic. However, it&#8217;s impossible to talk about <code>z-index</code> values without at least mentioning it, as it&#8217;s a crucial concept to understand.</p>



<p>Essentially, elements with a higher <code>z-index</code> value will be displayed in front of those with a lower value <strong>as long as they are in the same Stacking Context</strong>.</p>



<p>If they aren&#8217;t, then even if you set a massive <code>z-index</code> value on an element in a &#8220;lower&#8221; stack, elements in a &#8220;higher&#8221; stack will stay on top of it, even if they have a very low <code>z-index</code> value. This means that sometimes, even if you give an element the maximum possible value, it can still end up being hidden behind something else.</p>



<div class="wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper"><iframe id="cp_embed_ogzbzag/acba5545674b8aa9e7090847517d4272" src="//codepen.io/anon/embed/ogzbzag/acba5545674b8aa9e7090847517d4272?height=450&amp;theme-id=1&amp;slug-hash=ogzbzag/acba5545674b8aa9e7090847517d4272&amp;default-tab=result" height="450" scrolling="no" frameborder="0" allowfullscreen allowpaymentrequest name="CodePen Embed ogzbzag/acba5545674b8aa9e7090847517d4272" title="CodePen Embed ogzbzag/acba5545674b8aa9e7090847517d4272" class="cp_embed_iframe" style="width:100%;overflow:hidden">CodePen Embed Fallback</iframe></div>



<div class="wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper"><iframe id="cp_embed_ogzbzag/acba5545674b8aa9e7090847517d4272" src="//codepen.io/anon/embed/ogzbzag/acba5545674b8aa9e7090847517d4272?height=450&amp;theme-id=1&amp;slug-hash=ogzbzag/acba5545674b8aa9e7090847517d4272&amp;default-tab=result" height="450" scrolling="no" frameborder="0" allowfullscreen allowpaymentrequest name="CodePen Embed ogzbzag/acba5545674b8aa9e7090847517d4272" title="CodePen Embed ogzbzag/acba5545674b8aa9e7090847517d4272" class="cp_embed_iframe" style="width:100%;overflow:hidden">CodePen Embed Fallback</iframe></div>



<p>Now let&#8217;s get back to the values.</p>



<p class="is-style-explanation"><strong>&#x1f4a1; Did you know?</strong> The maximum value for <code>z-index</code> is <strong>2147483647</strong>. Why this specific number? It&#8217;s the maximum value for a 32-bit signed integer. If you try to go any higher, most browsers will simply clamp it to this limit.</p>


<h4 class="wp-block-heading" id="the-problem-with-magic-numbers-">The Problem With &#8220;Magic Numbers&#8221;</h4>


<p>Using arbitrary high values for <code>z-index</code> can lead to several issues:</p>



<ol class="wp-block-list">
<li><strong>Lack of maintainability</strong>: When you see a <code>z-index</code> value like <code>10001</code>, it doesn&#8217;t tell you anything about its relationship to other elements. It&#8217;s just a number that was chosen without any context.</li>



<li><strong>Potential for conflicts:</strong> If multiple teams or developers are using high <code>z-index</code> values, they might end up conflicting with each other, leading to unexpected behavior where some elements are hidden behind others.</li>



<li><strong>Difficult to debug:</strong> When something goes wrong with the stacking order, it can be challenging to figure out why, especially if there are many elements with high <code>z-index</code> values.A Better Approach</li>
</ol>



<p>I&#8217;ve encountered this &#8220;arms race&#8221; in almost every large project I&#8217;ve been a part of. The moment you have multiple teams working in the same codebase without a standardized system, chaos eventually takes over.</p>



<p>The solution is actually quite simple: <strong>tokenization of <code>z-index</code> values.</strong></p>



<p>Now, wait, stay with me! I know that the moment someone mentions &#8220;tokens&#8221;, some developers might roll their eyes or shake their heads, but this approach actually works. Most of the major (and better-designed) design systems include <code>z-index</code> tokens for a reason. Teams that adopt them swear by them and never look back.</p>



<p>By using tokens, you gain:</p>



<ul class="wp-block-list">
<li><strong>Simple and easy maintenance:</strong> You manage values in one place.</li>



<li><strong>Conflict prevention:</strong> No more guessing if <code>100</code> is higher than whatever Team B is using.</li>



<li><strong>Easier debugging::</strong> You can see exactly which &#8220;layer&#8221; an element belongs to.</li>



<li><strong>Better Stacking Context management:</strong> It forces you to think about layers systematically rather than as random numbers.</li>
</ul>


<h3 class="wp-block-heading" id="a-practical-example">A Practical Example</h3>


<p>Let&#8217;s look at how this works in practice. I&#8217;ve prepared a simple demo where we manage our layers through a central set of tokens in the <code>:root</code>:</p>



<pre rel="CSS" class="wp-block-csstricks-code-block language-css" data-line=""><code markup="tt">:root {
  --z-base: 0;
  --z-toast: 100;
  --z-popup: 200;
  --z-overlay: 300;
}</code></pre>



<div class="wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper"><iframe id="cp_embed_GgjgQXa/09de94c17a5fb5756581868390797d10" src="//codepen.io/anon/embed/GgjgQXa/09de94c17a5fb5756581868390797d10?height=450&amp;theme-id=1&amp;slug-hash=GgjgQXa/09de94c17a5fb5756581868390797d10&amp;default-tab=result" height="450" scrolling="no" frameborder="0" allowfullscreen allowpaymentrequest name="CodePen Embed GgjgQXa/09de94c17a5fb5756581868390797d10" title="CodePen Embed GgjgQXa/09de94c17a5fb5756581868390797d10" class="cp_embed_iframe" style="width:100%;overflow:hidden">CodePen Embed Fallback</iframe></div>



<p>This setup is incredibly convenient. If you need to add a new popup or a toast, you know exactly which <code>z-index</code> to use. If you want to change the order — for example, to place toasts above the overlay — you don&#8217;t need to hunt through dozens of files. You just change the values in the <code>:root</code>, and everything updates accordingly in one place.</p>


<h4 class="wp-block-heading" id="handling-new-elements">Handling New Elements</h4>


<p>The real power of this system shines when your requirements change. Suppose you need to add a new <strong>sidebar</strong> and place it specifically between the base content and the toasts.</p>



<p>In a traditional setup, you&#8217;d be checking every existing element to see what numbers they use. With tokens, we simply insert a new token and adjust the scale:</p>



<pre rel="CSS" class="wp-block-csstricks-code-block language-css" data-line=""><code markup="tt">:root {
  --z-base: 0;
  --z-sidebar: 100;
  --z-toast: 200;
  --z-popup: 300;
  --z-overlay: 400;
}</code></pre>



<div class="wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper"><iframe id="cp_embed_gbwbvBN/ae6e7037318d3f526df93c803d34a7e6" src="//codepen.io/anon/embed/gbwbvBN/ae6e7037318d3f526df93c803d34a7e6?height=500&amp;theme-id=1&amp;slug-hash=gbwbvBN/ae6e7037318d3f526df93c803d34a7e6&amp;default-tab=result" height="500" scrolling="no" frameborder="0" allowfullscreen allowpaymentrequest name="CodePen Embed gbwbvBN/ae6e7037318d3f526df93c803d34a7e6" title="CodePen Embed gbwbvBN/ae6e7037318d3f526df93c803d34a7e6" class="cp_embed_iframe" style="width:100%;overflow:hidden">CodePen Embed Fallback</iframe></div>



<p>You don&#8217;t have to touch a single existing component with this setup. You update the tokens and you&#8217;re good to go. The logic of your application remains consistent, and you&#8217;re no longer guessing which number is &#8220;high enough&#8221;.</p>


<h3 class="wp-block-heading" id="the-power-of-relative-layering">The Power of Relative Layering</h3>


<p>We sometimes want to &#8220;lock&#8221; specific layers relative to each other. A great example of this is a background element for a modal or an overlay. Instead of creating a separate token for the background, we can calculate its position relative to the main layer.</p>



<p>Using <code>calc()</code> allows us to maintain a strict relationship between elements that always belong together:</p>



<pre rel="CSS" class="wp-block-csstricks-code-block language-css" data-line=""><code markup="tt">.overlay-background {
  z-index: calc(var(--z-overlay) - 1);
}</code></pre>



<p>This ensures that the background will always stay exactly one step behind the overlay, no matter what value we assign to the <code>--z-overlay</code> token.</p>



<div class="wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper"><iframe id="cp_embed_KwgwQbg/802823b9a4baf0f9ae583939538e5f69" src="//codepen.io/anon/embed/KwgwQbg/802823b9a4baf0f9ae583939538e5f69?height=450&amp;theme-id=1&amp;slug-hash=KwgwQbg/802823b9a4baf0f9ae583939538e5f69&amp;default-tab=result" height="450" scrolling="no" frameborder="0" allowfullscreen allowpaymentrequest name="CodePen Embed KwgwQbg/802823b9a4baf0f9ae583939538e5f69" title="CodePen Embed KwgwQbg/802823b9a4baf0f9ae583939538e5f69" class="cp_embed_iframe" style="width:100%;overflow:hidden">CodePen Embed Fallback</iframe></div>


<h3 class="wp-block-heading" id="managing-internal-layers">Managing Internal Layers</h3>


<p>Up until now, we&#8217;ve focused on the main, global layers of the application. But what happens inside those layers?</p>



<p>The tokens we created for the main layers (like <code>100</code>, <code>200</code>, etc.) are not suitable for managing internal elements. <strong>This is because most of these main components create their own Stacking Context.</strong> Inside a popup that has <code>z-index: 300</code>, a value of <code>301</code> is functionally identical to <code>1</code>. Using large global tokens for internal positioning is confusing and unnecessary.</p>



<p class="is-style-explanation"><strong>Note:</strong> For these local tokens to work as expected, you must ensure the container creates a Stacking Context. If you&#8217;re working on a component that doesn&#8217;t already have one (e.g., it doesn&#8217;t has a <code>z-index</code> set), you can create one explicitly using <code>isolation: isolate</code>.</p>



<p>To solve this, we can introduce a pair of &#8220;local&#8221; tokens specifically for internal use:</p>



<pre rel="CSS" class="wp-block-csstricks-code-block language-css" data-line=""><code markup="tt">:root {
  /* ... global tokens ... */

  --z-bottom: -10;
  --z-top: 10;
}</code></pre>



<p>This allows us to handle internal positioning with precision. If you need a floating action button inside a popup to stay on top, or a decorative icon on a toast to sit behind the main content, you can use these local anchors:</p>



<pre rel="CSS" class="wp-block-csstricks-code-block language-css" data-line=""><code markup="tt">.popup-close-button {
  z-index: var(--z-top);
}

.toast-decorative-icon {
  z-index: var(--z-bottom);
}</code></pre>



<p>For even more complex internal layouts, you can still use <code>calc()</code> with these local tokens. If you have multiple elements stacking within a component, <code>calc(var(--z-top) + 1)</code> (or <code>- 1</code>) gives you that extra bit of precision without ever needing to look at global values.</p>



<div class="wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper"><iframe id="cp_embed_WbGbMmw/5b263640f071f7adafff0c4603aafc50" src="//codepen.io/anon/embed/WbGbMmw/5b263640f071f7adafff0c4603aafc50?height=450&amp;theme-id=1&amp;slug-hash=WbGbMmw/5b263640f071f7adafff0c4603aafc50&amp;default-tab=result" height="450" scrolling="no" frameborder="0" allowfullscreen allowpaymentrequest name="CodePen Embed WbGbMmw/5b263640f071f7adafff0c4603aafc50" title="CodePen Embed WbGbMmw/5b263640f071f7adafff0c4603aafc50" class="cp_embed_iframe" style="width:100%;overflow:hidden">CodePen Embed Fallback</iframe></div>



<p>This keeps our logic consistent: we think about layers and positions systematically, rather than throwing random numbers at the problem and hoping for the best.</p>


<h3 class="wp-block-heading" id="versatile-components-the-tooltip-case">Versatile Components: The Tooltip Case</h3>


<p>One of the biggest headaches in CSS is managing components that can appear anywhere, <a href="https://css-tricks.com/tooltip-best-practices/">like a tooltip</a>.</p>



<p>Traditionally, developers give tooltips a massive <code>z-index</code> (like <code>9999</code>) because they might appear over a modal. But if the tooltip is physically inside the modal&#8217;s DOM structure, its <code>z-index</code> is only relative to that modal anyway.</p>



<p>A tooltip simply needs to be above the content it&#8217;s attached to. By using our local tokens, we can stop the guessing game:</p>



<pre rel="CSS" class="wp-block-csstricks-code-block language-css" data-line=""><code markup="tt">.tooltip {
  z-index: var(--z-top);
}</code></pre>



<p>Whether the tooltip is on a button in the main content, an icon inside a toast, or a link within a popup, it will always appear correctly above its immediate surroundings. It doesn&#8217;t need to know about the global &#8220;arms race&#8221; because it&#8217;s already standing on the &#8220;stable floor&#8221; provided by its parent layer&#8217;s token.</p>



<div class="wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper"><iframe id="cp_embed_emdmVoG/524d65a9d247414bf03e9efbd9adaf4b" src="//codepen.io/anon/embed/emdmVoG/524d65a9d247414bf03e9efbd9adaf4b?height=450&amp;theme-id=1&amp;slug-hash=emdmVoG/524d65a9d247414bf03e9efbd9adaf4b&amp;default-tab=result" height="450" scrolling="no" frameborder="0" allowfullscreen allowpaymentrequest name="CodePen Embed emdmVoG/524d65a9d247414bf03e9efbd9adaf4b" title="CodePen Embed emdmVoG/524d65a9d247414bf03e9efbd9adaf4b" class="cp_embed_iframe" style="width:100%;overflow:hidden">CodePen Embed Fallback</iframe></div>


<h4 class="wp-block-heading" id="negative-values-can-be-good">Negative Values Can Be Good</h4>


<p>Negative values often scare developers. We worry that an element with <code>z-index: -1</code> will disappear behind the page background or some distant parent.</p>



<p>However, within our systematic approach, negative values are a powerful tool for internal decorations. When a component creates its own Stacking Context, the <code>z-index</code> is confined to that component. And <code>z-index: var(--z-bottom)</code> simply means &#8220;place this behind the default content of this specific container&#8221;.</p>



<p>This is perfect for:</p>



<ul class="wp-block-list">
<li><strong>Component backgrounds:</strong> Subtle patterns or gradients that shouldn&#8217;t interfere with text.</li>



<li><strong>Shadow simulations:</strong> When you need more control than <code>box-shadow</code> provides.</li>



<li><strong>Inner glows or borders:</strong> Elements that should sit &#8220;under&#8221; the main UI.</li>
</ul>


<h3 class="wp-block-heading" id="conclusion-the-z-index-manifesto">Conclusion: The <code>z-index</code> Manifesto</h3>


<p>With just a few CSS variables, we’ve built a complete management system for <code>z-index</code>. It&#8217;s a simple yet powerful way to ensure that managing layers never feels like a guessing game again.</p>



<p>To maintain a clean and scalable codebase, here are the golden rules for working with <code>z-index</code>:</p>



<ol class="wp-block-list">
<li><strong>No magic numbers:</strong> Never use arbitrary values like <code>999</code> or <code>10001</code>. If a number isn&#8217;t tied to a system, it&#8217;s a bug waiting to happen.</li>



<li><strong>Tokens are mandatory:</strong> Every <code>z-index</code> in your CSS should come from a token, either a global layer token or a local positioning token.</li>



<li><strong>It&#8217;s rarely the value:</strong> If an element isn&#8217;t appearing on top despite a &#8220;high&#8221; value, the problem is almost certainly its <strong>Stacking Context</strong>, not the number itself.</li>



<li><strong>Think in layers:</strong> Stop asking &#8220;how high should this be?&#8221; and start asking &#8220;which layer does this belong to?&#8221;</li>



<li><strong>Calc for connection:</strong> Use <code>calc()</code> to bind related elements together (like an overlay and its background) rather than giving them separate, unrelated tokens.</li>



<li><strong>Local contexts for local problems:</strong> Use local tokens (<code>--z-top</code>, <code>--z-bottom</code>) and internal stacking contexts to manage complexity within components.</li>
</ol>



<p>By following these rules, you turn <code>z-index</code> from a chaotic source of bugs into a predictable, manageable part of your design system. The value of <code>z-index</code> isn&#8217;t in how high the number is, but in the system that defines it.</p>


<h3 class="wp-block-heading" id="bonus-enforcing-a-clean-system">Bonus: Enforcing a Clean System</h3>


<p>A system is only as good as its enforcement. In a deadline-driven environment, it&#8217;s easy for a developer to slip in a quick <code>z-index: 999</code> to &#8220;make it just work&#8221;. Without automation, your beautiful token system will eventually erode back into chaos.</p>



<p>To prevent this, I developed a library specifically designed to enforce this exact system: <a href="https://www.npmjs.com/package/z-index-token-enforcer" rel="noopener"><strong>z-index-token-enforcer</strong></a>.</p>



<pre rel="Terminal" class="wp-block-csstricks-code-block language-none" data-line=""><code markup="tt">npm install z-index-token-enforcer --save-dev</code></pre>



<p>It provides a unified set of tools to automatically flag any literal <code>z-index</code> values and require developers to use your predefined tokens:</p>



<ul class="wp-block-list">
<li><strong>Stylelint plugin:</strong> For standard CSS/SCSS enforcement</li>



<li><strong>ESLint plugin:</strong> To catch literal values in CSS-in-JS and React inline styles</li>



<li><strong>CLI scanner:</strong> A standalone script that can quickly scan files directly or be integrated into your CI/CD pipelines</li>
</ul>



<p>By using these tools, you turn the &#8220;Golden Rules&#8221; from a recommendation into a hard requirement, ensuring that your codebase stays clean, scalable, and, most importantly, predictable.</p>
<hr />
<p><small><a rel="nofollow" href="https://css-tricks.com/the-value-of-z-index/">The Value of z-index</a> originally published on <a rel="nofollow" href="https://css-tricks.com">CSS-Tricks</a>, which is part of the <a href="https://try.digitalocean.com/css-tricks/?utm_medium=rss&amp;utm_source=css-tricks.com&amp;utm_campaign=family_&amp;utm_content=">DigitalOcean</a> family. You should <a href="https://css-tricks.com/newsletters/">get the newsletter</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://css-tricks.com/the-value-of-z-index/feed/</wfw:commentRss>
			<slash:comments>6</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">392843</post-id>	</item>
		<item>
		<title>The Different Ways to Select &#060;html&gt; in CSS</title>
		<link>https://css-tricks.com/the-different-ways-to-select-html-in-css/</link>
					<comments>https://css-tricks.com/the-different-ways-to-select-html-in-css/#comments</comments>
		
		<dc:creator><![CDATA[Daniel Schwarz]]></dc:creator>
		<pubDate>Thu, 05 Mar 2026 14:01:04 +0000</pubDate>
				<category><![CDATA[Articles]]></category>
		<category><![CDATA[selectors]]></category>
		<guid isPermaLink="false">https://css-tricks.com/?p=392257</guid>

					<description><![CDATA[<p>Sure, we can select the <code>&#60;html&#62;</code> element in CSS with, you know, a simple element selector, <code>html</code>. But what other (trivial and perhaps useless) ways can we do it?</p>
<hr />
<p><small><a rel="nofollow" href="https://css-tricks.com/the-different-ways-to-select-html-in-css/">The Different Ways to Select &lt;html&gt; in CSS</a> originally published on <a rel="nofollow" href="https://css-tricks.com">CSS-Tricks</a>, which is part of the <a href="https://try.digitalocean.com/css-tricks/?utm_medium=rss&amp;utm_source=css-tricks.com&amp;utm_campaign=family_&amp;utm_content=">DigitalOcean</a> family. You should <a href="https://css-tricks.com/newsletters/">get the newsletter</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p><a href="https://css-tip.com/root-selectors/" rel="noopener">Temani Afif recently did this exercise</a> and I thought I’d build off of it. Some of these are useful. Many of them are not. There’s a bird at the end!</p>



<span id="more-392257"></span>


<h3 class="wp-block-heading" id="-html-"><code>html</code></h3>


<pre rel="CSS" class="wp-block-csstricks-code-block language-css" data-line=""><code markup="tt">html {
  /* I mean, duh */
}</code></pre>


<h3 class="wp-block-heading" id="-root-"><code>:root</code></h3>


<pre rel="CSS" class="wp-block-csstricks-code-block language-css" data-line=""><code markup="tt">:root {
  /* Sarsaparilla, anyone? */
}</code></pre>



<p><code>:root</code> is a CSS pseudo-class that matches the root element of the current (XML) document. If the current document is a HTML document, then it matches <code>&lt;html&gt;</code>. The XML documents that you’ll most likely encounter as a web developer (besides HTML) are:</p>



<ul class="wp-block-list">
<li>SVG documents: <code>:root</code> matches <code>&lt;svg&gt;</code></li>



<li>RSS documents: <code>:root</code> matches <code>&lt;rss&gt;</code></li>



<li>Atom documents: <code>:root</code> matches <code>&lt;feed&gt;</code></li>



<li>MathML documents: <code>:root</code> matches <code>&lt;math&gt;</code></li>



<li>Other XML documents: <code>:root</code> matches the outermost element (e.g., <code>&lt;note&gt;</code>)</li>
</ul>



<p>But what’s the practicality of <code>:root</code>? Well, the specificity of pseudo-classes (0-1-0) is higher than that of elements (0-0-1), so you’re less likely to run into conflicts with <code>:root</code>.</p>



<p>It’s conventional to declare global custom properties on <code>:root</code>, but I actually prefer <code>:scope</code> because it semantically matches the global scope. In practice though, it makes no difference.</p>



<pre rel="CSS" class="wp-block-csstricks-code-block language-css" data-line=""><code markup="tt">/* Global variables */
:root { --color: black; }
:scope { --color: black; }</code></pre>



<p>Let’s talk about <code>:scope</code> some more…</p>


<h3 class="wp-block-heading" id="-scope-or-"><code>:scope</code> or <code>&amp;</code></h3>


<pre rel="CSS" class="wp-block-csstricks-code-block language-css" data-line=""><code markup="tt">:scope {
  /* Insert scope creep here */
}</code></pre>



<p>Okay, that’s not <em>really</em> what <code>:scope</code> is for.</p>



<p>As I mentioned, <code>:scope</code> matches the <em>global</em> scope root (<code>&lt;html&gt;</code>). However, this is only true when not used within the newly baseline <a href="https://css-tricks.com/almanac/rules/s/scope/"><code>@scope</code></a> at-rule, which is used to define a <em>custom</em> scope root.</p>



<p>We can also do this:</p>



<pre rel="CSS" class="wp-block-csstricks-code-block language-css" data-line=""><code markup="tt">&amp; {
  /* And...? */
}</code></pre>



<p>Normally, the <code>&amp;</code> selector is used with CSS nesting to concatenate the current selector to the containing selector, enabling us to nest selectors even when we aren’t technically dealing with nested selectors. For example:</p>



<pre rel="CSS" class="wp-block-csstricks-code-block language-css" data-line=""><code markup="tt">element:hover {
  /* This */
}

element {
  &amp;:hover {
    /* Becomes this (notice the &amp;) */
  }
}

element {
  :hover {
    /* Because this (with no &amp;) */
  }
}

element :hover {
  /* Means this (notice the space before :hover) */
}

element {
  :hover &amp; {
    /* Means :hover element, but I digress */
  }
}</code></pre>



<p>When <code>&amp;</code> isn’t nested, it simply selects the scope root, which outside of an <code>@scope</code> block is <code>&lt;html&gt;</code>. Who knew?</p>


<h3 class="wp-block-heading" id="-has-head-or-has-body-"><code>‌:has(head)</code> or <code>:has(body)</code></h3>


<pre rel="CSS" class="wp-block-csstricks-code-block language-css" data-line=""><code markup="tt">:has(head) {
  /* Nice! */
}

:has(body) {
  /* Even better! */
}</code></pre>



<p><code>&lt;html&gt;</code> elements should only contain a <code>&lt;head&gt;</code> and <code>&lt;body&gt;</code> (à la Anakin Skywalker) as direct children. Any other markup inserted here is invalid, although parsers will typically move it into the <code>&lt;head&gt;</code> or <code>&lt;body&gt;</code> anyway. More importantly, no other element is allowed to contain <code>&lt;head&gt;</code> or <code>&lt;body&gt;</code>, so when we say <code>:has(head)</code> or <code>:has(body)</code>, this can only refer to the <code>&lt;html&gt;</code> element, unless you mistakenly insert <code>&lt;head&gt;</code> or <code>&lt;body&gt;</code> inside of <code>&lt;head&gt;</code> or <code>&lt;body&gt;</code>. But why would you? That’s just nasty.</p>



<p>Is <code>:has(head)</code> or <code>:has(body)</code> practical? No. But I <em>am</em> going to plug <a href="https://css-tricks.com/almanac/pseudo-selectors/h/has/"><code>:has()</code></a>, and you also learned about the illegal things that you shouldn’t do to HTML bodies.</p>


<h3 class="wp-block-heading" id="-not-"><code>:not(* *)</code></h3>


<pre rel="CSS" class="wp-block-csstricks-code-block language-css" data-line=""><code markup="tt">:not(* *) {
  /* (* *) are my starry eyes looking at CSS &lt;3 */
}</code></pre>



<p>Any element that’s contained by another element (<code>* *</code>)? Yeah, <code>:not()</code> that. The only element that’s not contained by another element is the <code>&lt;html&gt;</code> element. <code>*</code>, by the way, is called the <a href="https://css-tricks.com/almanac/selectors/u/universal/">universal selector</a>.</p>



<p>And if you throw a <a href="https://css-tricks.com/almanac/selectors/c/child/">child combinator</a> right in the middle of them, you get a cute bird:</p>



<pre rel="CSS" class="wp-block-csstricks-code-block language-css" data-line=""><code markup="tt">:not(* > *) {
  /* Chirp, chirp */
}</code></pre>



<p>“Siri, file this under <em>Completely Useless.</em>” (Ironically, Siri did no such thing).</p>
<hr />
<p><small><a rel="nofollow" href="https://css-tricks.com/the-different-ways-to-select-html-in-css/">The Different Ways to Select &lt;html&gt; in CSS</a> originally published on <a rel="nofollow" href="https://css-tricks.com">CSS-Tricks</a>, which is part of the <a href="https://try.digitalocean.com/css-tricks/?utm_medium=rss&amp;utm_source=css-tricks.com&amp;utm_campaign=family_&amp;utm_content=">DigitalOcean</a> family. You should <a href="https://css-tricks.com/newsletters/">get the newsletter</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://css-tricks.com/the-different-ways-to-select-html-in-css/feed/</wfw:commentRss>
			<slash:comments>4</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">392257</post-id>	</item>
		<item>
		<title>Popover API or Dialog API: Which to Choose?</title>
		<link>https://css-tricks.com/popover-api-or-dialog-api-which-to-choose/</link>
					<comments>https://css-tricks.com/popover-api-or-dialog-api-which-to-choose/#comments</comments>
		
		<dc:creator><![CDATA[Zell Liew]]></dc:creator>
		<pubDate>Mon, 02 Mar 2026 15:10:07 +0000</pubDate>
				<category><![CDATA[Articles]]></category>
		<category><![CDATA[accessibility]]></category>
		<category><![CDATA[dialog]]></category>
		<category><![CDATA[popover]]></category>
		<guid isPermaLink="false">https://css-tricks.com/?p=388435</guid>

					<description><![CDATA[<p>Choosing between Popover API and Dialog API is difficult because they seem to do the same job, but they don’t! After a bit lots of research, I discovered that the Popover API and Dialog API are wildly different in terms of accessibility and we'll go over that in this article.</p>
<hr />
<p><small><a rel="nofollow" href="https://css-tricks.com/popover-api-or-dialog-api-which-to-choose/">Popover API or Dialog API: Which to Choose?</a> originally published on <a rel="nofollow" href="https://css-tricks.com">CSS-Tricks</a>, which is part of the <a href="https://try.digitalocean.com/css-tricks/?utm_medium=rss&amp;utm_source=css-tricks.com&amp;utm_campaign=family_&amp;utm_content=">DigitalOcean</a> family. You should <a href="https://css-tricks.com/newsletters/">get the newsletter</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p>Choosing between Popover API and Dialog API is difficult because they seem to do the same job, but they don’t!</p>



<p>After <del>a bit</del> lots of research, I discovered that the Popover API and Dialog API are wildly different in terms of accessibility. So, if you’re trying to decide whether to use Popover API or Dialog’s API, I recommend you:</p>



<ul class="wp-block-list">
<li>Use Popover API for most popovers.</li>



<li>Use Dialog’s API only for modal dialogs.</li>
</ul>



<span id="more-388435"></span>


<h3 class="wp-block-heading" id="popovers-vs-dialogs">Popovers vs. Dialogs</h3>


<p>The relationship between Popovers and Dialogs are confusing to most developers, but it’s actually quite simple.</p>



<p><strong>Dialogs are simply subsets of popovers.</strong> And modal dialogs are subsets of dialogs. <a href="https://css-tricks.com/clarifying-the-relationship-between-popovers-and-dialogs/">Read this article</a> if you want to understand the rationale behind this relationship.</p>



<p>This is why you could use the Popover API even on a <code>&lt;dialog&gt;</code> element.</p>



<pre rel="HTML" class="wp-block-csstricks-code-block language-markup" data-line=""><code markup="tt">&lt;!-- Using popover on a dialog element --> 
&lt;dialog popover>...&lt;/div> </code></pre>



<p>Stylistically, the difference between popovers and modals are even clearer:</p>



<ul class="wp-block-list">
<li>Modals should show a backdrop.</li>



<li>Popovers should not.</li>
</ul>



<p><strong>Therefore, you should never style a popover’s <code>::backdrop</code> element.</strong> Doing so will simply indicate that the popover is a dialog — which creates <em>a whole can of problems</em>.</p>



<p><strong>You should only style a modal’s <code>::backdrop</code> element.</strong></p>


<h3 class="wp-block-heading" id="popover-api-and-its-accessibility">Popover API and its accessibility</h3>


<p>Building a popover with the Popover API is relatively easy. You specify three things:</p>



<ul class="wp-block-list">
<li>a <code>popovertarget</code> attribute on the popover trigger,</li>



<li>an <code>id</code> on the popover, and</li>



<li>a <code>popover</code> attribute on the popover.</li>
</ul>



<p>The <code>popovertarget</code> must match the <code>id</code>.</p>



<pre rel="HTML" class="wp-block-csstricks-code-block language-markup" data-line=""><code markup="tt">&lt;button popovertarget="the-popover"> ... &lt;/button>
&lt;dialog popover id="the-popover"> The Popover Content &lt;/dialog></code></pre>



<p>Notice that I&#8217;m using the <code>&lt;dialog&gt;</code> element to create a <code>dialog</code> role. This is optional, but recommended. I do this because <code>dialog</code> is a <a href="https://css-tricks.com/clarifying-the-relationship-between-popovers-and-dialogs/">great default role</a> since most popovers are simply just dialogs.</p>



<p>This two lines of code comes with a ton of accessibility features already built-in for you:</p>



<ul class="wp-block-list">
<li><strong>Automatic focus management</strong>
<ul class="wp-block-list">
<li>Focus goes to the popover when opening.</li>



<li>Focus goes back to the trigger when closing.</li>
</ul>
</li>



<li><strong>Automatic aria connection</strong>
<ul class="wp-block-list">
<li>No need to write <code>aria-expanded</code>, <code>aria-popup</code> and <code>aria-controls</code>. Browsers handle those natively. Woo!</li>
</ul>
</li>



<li><strong>Automatic light dismiss</strong>
<ul class="wp-block-list">
<li>Popover closes when user clicks outside.</li>



<li>Popover closes when they press the <kbd>Esc</kbd> key.</li>
</ul>
</li>
</ul>



<p>Now, without additional styling, the popover looks kinda meh. Styling is a whole ‘nother issue, so we’ll tackle that in a future article. <a href="https://css-tricks.com/poppin-in/">Geoff has a few notes</a> you can review in the meantime.</p>



<div class="wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper"><iframe id="cp_embed_dPYXEYV" src="//codepen.io/anon/embed/preview/dPYXEYV?height=350&amp;theme-id=1&amp;slug-hash=dPYXEYV&amp;default-tab=html,result" height="350" scrolling="no" frameborder="0" allowfullscreen allowpaymentrequest name="CodePen Embed dPYXEYV" title="CodePen Embed dPYXEYV" class="cp_embed_iframe" style="width:100%;overflow:hidden">CodePen Embed Fallback</iframe></div>


<h3 class="wp-block-heading" id="dialog-api-and-its-accessibility">Dialog API and its accessibility</h3>


<p>Unlike the Popover API, the Dialog API doesn&#8217;t have many built-in features by default:</p>



<ul class="wp-block-list">
<li>No automatic focus management</li>



<li>No automatic ARIA connection</li>



<li>No automatic light dismiss</li>
</ul>



<p>So, we have to build them ourselves with JavaScript. This is why <strong>the Popover API is superior to the Dialog API in almost every aspect — except for one</strong>: when modals are involved.</p>



<p>The Dialog API has a <code>showModal</code> method. When <code>showModal</code> is used, the Dialog API creates a modal. It:</p>



<ol class="wp-block-list">
<li>automatically <code>inert</code>s other elements,</li>



<li>prevents users from tabbing into other elements, and</li>



<li>prevents screen readers from reaching other elements.</li>
</ol>



<p>It does this so effectively, <a href="https://css-tricks.com/there-is-no-need-to-trap-focus-on-a-dialog-element/">we no longer need to trap focus within the modal</a>.</p>



<p>But we gotta take care of the focus and ARIA stuff when we use the Dialog API, so let&#8217;s tackle the bare minimum code you need for a functioning dialog.</p>



<p>We&#8217;ll begin by building the HTML scaffold:</p>



<pre rel="HTML" class="wp-block-csstricks-code-block language-markup" data-line=""><code markup="tt">&lt;button 
  class="modal-invoker" 
  data-target="the-modal" 
  aria-haspopup="dialog"
>...&lt;/button>

&lt;dialog id="the-modal">The Popover Content&lt;/dialog></code></pre>



<p>Notice I did not add any <code>aria-expanded</code> in the HTML. I do this for a variety of reasons:</p>



<ol class="wp-block-list">
<li>This reduces the complexity of the HTML.</li>



<li>We can write <code>aria-expanded</code>, <code>aria-controls</code>, and the focus stuff directly in JavaScript &#8211; since these won&#8217;t work without JavaScript.</li>



<li>Doing so makes this HTML very reusable.</li>
</ol>


<h4 class="wp-block-heading" id="setting-up">Setting up</h4>


<p>I&#8217;m going to write about a vanilla JavaScript implementation here. If you&#8217;re using a framework, like React or Svelte, you will have to make a couple of changes — but I hope that it&#8217;s gonna be straightforward for you.</p>



<p>First thing to do is to loop through all <code>dialog-invoker</code>s and set <code>aria-expanded</code> to <code>false</code>. This creates the initial state.</p>



<p>We will also set <code>aria-controls</code> to the <code>&lt;dialog&gt;</code> element. We&#8217;ll do this even though <a href="https://heydonworks.com/article/aria-controls-is-poop/" rel="noopener"><code>aria-controls</code> is poop</a>, &#8217;cause there&#8217;s no better way to connect these elements (and there&#8217;s no harm connecting them) as far as I know.</p>



<pre rel="JavaScript" class="wp-block-csstricks-code-block language-javascript" data-line=""><code markup="tt">const modalInvokers = Array.from(document.querySelectorAll('.modal-invoker'))

modalInvokers.forEach(invoker => {
  const dialogId = invoker.dataset.target
  const dialog = document.querySelector(`#${dialogId}`)
  invoker.setAttribute('aria-expanded', false)
  invoker.setAttribute('aria-controls', dialogId)
})</code></pre>


<h4 class="wp-block-heading" id="opening-the-modal">Opening the modal</h4>


<p>When the invoker/trigger is clicked, we gotta:</p>



<ol class="wp-block-list">
<li>change the <code>aria-expanded</code> from <code>false</code> to <code>true</code> to <code>show</code> the modal to assistive tech users, and</li>



<li>use the <code>showModal</code> function to open the modal.</li>
</ol>



<p>We don&#8217;t have to write any code to hide the modal in this <code>click</code> handler because users will never get to click on the invoker when the dialog is opened.</p>



<pre rel="JavaScript" class="wp-block-csstricks-code-block language-javascript" data-line=""><code markup="tt">modalInvokers.forEach(invoker => {
  // ... 

  // Opens the modal
  invoker.addEventListener('click', event => {
    invoker.setAttribute('aria-expanded', true)
    dialog.showModal()
  })
})</code></pre>



<div class="wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper"><iframe id="cp_embed_raOLgLe" src="//codepen.io/anon/embed/preview/raOLgLe?height=350&amp;theme-id=1&amp;slug-hash=raOLgLe&amp;default-tab=js,result" height="350" scrolling="no" frameborder="0" allowfullscreen allowpaymentrequest name="CodePen Embed raOLgLe" title="CodePen Embed raOLgLe" class="cp_embed_iframe" style="width:100%;overflow:hidden">CodePen Embed Fallback</iframe></div>



<p>Great. The modal is open. Now we gotta write code to close the modal.</p>


<h4 class="wp-block-heading" id="closing-the-modal">Closing the modal</h4>


<p>By default, <code>showModal</code> doesn&#8217;t have automatic light dismiss, so users can&#8217;t close the modal by clicking on the overlay, or by hitting the <kbd>Esc</kbd> key. This means we have to add another button that closes the modal. This must be placed within the modal content.</p>



<pre rel="HTML" class="wp-block-csstricks-code-block language-markup" data-line=""><code markup="tt">&lt;dialog id="the-modal"> 
  &lt;button class="modal-closer">X&lt;/button>
  &lt;!-- Other modal content -->
&lt;/dialog></code></pre>



<p>When users click the close button, we have to:</p>



<ol class="wp-block-list">
<li>set <code>aria-expanded</code> on the opening invoker to <code>false</code>,</li>



<li>close the modal with the <code>close</code> method, and</li>



<li>bring focus back to the opening invoker element.</li>
</ol>



<pre rel="JavaScript" class="wp-block-csstricks-code-block language-javascript" data-line=""><code markup="tt">modalInvokers.forEach(invoker => {
  // ... 

  // Opens the modal
  invoker.addEventListener('click', event => {
    invoker.setAttribute('aria-expanded', true)
    dialog.showModal()
  })
})

const modalClosers = Array.from(document.querySelectorAll('.modal-closer'))

modalClosers.forEach(closer => {
  const dialog = closer.closest('dialog')
  const dialogId = dialog.id
  const invoker = document.querySelector(`[data-target="${dialogId}"]`)
  
  closer.addEventListener('click', event => {
    dialog.close()
    invoker.setAttribute('aria-expanded', false)
    invoker.focus()
  })
})</code></pre>



<p>Phew, with this, we&#8217;re done with the <em>basic</em> implementation.</p>



<div class="wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper"><iframe id="cp_embed_QwjERxw" src="//codepen.io/anon/embed/preview/QwjERxw?height=350&amp;theme-id=1&amp;slug-hash=QwjERxw&amp;default-tab=js,result" height="350" scrolling="no" frameborder="0" allowfullscreen allowpaymentrequest name="CodePen Embed QwjERxw" title="CodePen Embed QwjERxw" class="cp_embed_iframe" style="width:100%;overflow:hidden">CodePen Embed Fallback</iframe></div>



<p>Of course, there&#8217;s advanced work like light dismiss and styling&#8230; which we can tackle in a future article.</p>


<h3 class="wp-block-heading" id="can-you-use-the-popover-api-to-create-modals-">Can you use the Popover API to create modals?</h3>


<p>Yeah, you can.</p>



<p>But you will have to handle these on your own:</p>



<ol class="wp-block-list">
<li>Inerting other elements</li>



<li><a href="https://css-tricks.com/there-is-no-need-to-trap-focus-on-a-dialog-element/">Trapping focus</a></li>
</ol>



<p>I think what we did earlier (setting <code>aria-expanded</code>, <code>aria-controls</code>, and <code>focus</code>) are easier compared to inerting elements and trapping focus.</p>


<h3 class="wp-block-heading" id="the-dialog-api-might-become-much-easier-to-use-in-the-future">The Dialog API might become much easier to use in the future</h3>


<p>A proposal about <a href="https://css-tricks.com/invoker-commands-additional-ways-to-work-with-dialog-popover-and-more/">invoker commands</a> has been <a href="https://github.com/whatwg/html/issues/9625" rel="noopener">created</a> so that the Dialog API can include <code>popovertarget</code> like the Popover API.</p>



<p>This is on the way, so we might be able to make modals even simpler with the Dialog API in the future. In the meantime, we gotta do the necessary work to patch accessibility stuff.</p>


<h3 class="wp-block-heading" id="deep-dive-into-building-workable-popovers-and-modals"><strong>Deep dive into building workable popovers and modals</strong></h3>


<p>We’ve only began to scratch the surface of building working popovers and modals with the code above — they&#8217;re barebone versions that are accessible, but they definitely don&#8217;t look nice and can&#8217;t be used for professional purposes yet.</p>



<p>To make the process of building popovers and modals easier, we will dive deeper into the implementation details for a professional-grade popover and a professional-grade modal in future articles.</p>



<p>In the meantime, I hope these give you some ideas on when to choose the Popover API and the Dialog API!</p>



<p>Remember, there&#8217;s no need to use both. One will do.</p>
<hr />
<p><small><a rel="nofollow" href="https://css-tricks.com/popover-api-or-dialog-api-which-to-choose/">Popover API or Dialog API: Which to Choose?</a> originally published on <a rel="nofollow" href="https://css-tricks.com">CSS-Tricks</a>, which is part of the <a href="https://try.digitalocean.com/css-tricks/?utm_medium=rss&amp;utm_source=css-tricks.com&amp;utm_campaign=family_&amp;utm_content=">DigitalOcean</a> family. You should <a href="https://css-tricks.com/newsletters/">get the newsletter</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://css-tricks.com/popover-api-or-dialog-api-which-to-choose/feed/</wfw:commentRss>
			<slash:comments>4</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">388435</post-id>	</item>
		<item>
		<title>What’s !important #6: :heading, border-shape, Truncating Text From the Middle, and More</title>
		<link>https://css-tricks.com/whats-important-6/</link>
					<comments>https://css-tricks.com/whats-important-6/#comments</comments>
		
		<dc:creator><![CDATA[Daniel Schwarz]]></dc:creator>
		<pubDate>Fri, 27 Feb 2026 16:30:22 +0000</pubDate>
				<category><![CDATA[Articles]]></category>
		<category><![CDATA[news]]></category>
		<guid isPermaLink="false">https://css-tricks.com/?p=392661</guid>

					<description><![CDATA[<p>Despite what’s been a sleepy couple of weeks for new Web Platform Features, we have an issue of What’s !important that’s prrrretty jam-packed. The web community had a lot to say, it seems, so fasten your seatbelts!</p>
<hr />
<p><small><a rel="nofollow" href="https://css-tricks.com/whats-important-6/">What’s !important #6: :heading, border-shape, Truncating Text From the Middle, and More</a> originally published on <a rel="nofollow" href="https://css-tricks.com">CSS-Tricks</a>, which is part of the <a href="https://try.digitalocean.com/css-tricks/?utm_medium=rss&amp;utm_source=css-tricks.com&amp;utm_campaign=family_&amp;utm_content=">DigitalOcean</a> family. You should <a href="https://css-tricks.com/newsletters/">get the newsletter</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p>Despite what’s been a sleepy couple of weeks for new Web Platform Features, we have an issue of What’s !important that’s <strong><em>prrrretty jam-packed</em></strong>. The web community had a lot to say, it seems, so fasten your seatbelts!</p>



<span id="more-392661"></span>


<h3 class="wp-block-heading" id="keyframes-animations-can-be-strings"><code>@keyframes</code> animations can be strings</h3>


<p>Peter Kröner shared <a href="https://bsky.app/profile/sirpepe.bsky.social/post/3mf4uwrbnkk2j" rel="noopener">an interesting fact about <code>@keyframes</code> animations</a> — that they can be strings:</p>



<pre rel="CSS" class="wp-block-csstricks-code-block language-css" data-line=""><code markup="tt">@keyframes "@animation" {
  /* ... */
}

#animate-this {
  animation: "@animation";
}</code></pre>



<blockquote class="bluesky-embed" data-bluesky-uri="at://did:plc:efcyuflqnbzhmaob2pelkpgo/app.bsky.feed.post/3mf4uwrbnkk2j" data-bluesky-cid="bafyreibdq6i3at2w2wvrtmzqnk5ollzrpnnukzsvq7jv3jjo565by3amwm" data-bluesky-embed-color-mode="system"><p lang="en">Yo dawg, time for a #CSS fun fact: keyframe names can be strings. Why? Well, in case you want your keyframes to be named “@keyframes,” obviously!

#webdev<br><br><a href="https://bsky.app/profile/did:plc:efcyuflqnbzhmaob2pelkpgo/post/3mf4uwrbnkk2j?ref_src=embed" rel="noopener">[image or embed]</a></p>&mdash; Peter Kröner (<a href="https://bsky.app/profile/did:plc:efcyuflqnbzhmaob2pelkpgo?ref_src=embed" rel="noopener">@sirpepe.bsky.social</a>) <a href="https://bsky.app/profile/did:plc:efcyuflqnbzhmaob2pelkpgo/post/3mf4uwrbnkk2j?ref_src=embed" rel="noopener">Feb 18, 2026 at 10:33</a></blockquote><script async src="https://embed.bsky.app/static/embed.js" charset="utf-8"></script>



<p>I don’t know why you’d want to do that, but it’s certainly an interesting thing to learn about <code>@keyframes</code> after 11 years of cross-browser support!</p>


<h3 class="wp-block-heading" id="vs-in-style-queries"><code>:</code> vs. <code>=</code> in style queries</h3>


<p>Another hidden trick, this one from <a href="https://css-tricks.com/author/afiftemani/">Temani Afif</a>, has revealed that we can <a href="https://css-tip.com/if-trick/" rel="noopener">replace the colon in a style query with an equals symbol</a>. Temani does a great job at explaining the difference, but here’s a quick code snippet to sum it up:</p>



<pre rel="CSS" class="wp-block-csstricks-code-block language-css" data-line=""><code markup="tt">.Jay-Z {
  --Problems: calc(98 + 1);

  /* Evaluates as calc(98 + 1), color is blueivy */
  color: if(style(--Problems: 99): red; else: blueivy);

  /* Evaluates as 99, color is red */
  color: if(style(--Problems = 99): red; else: blueivy);
}</code></pre>



<p>In short, <code>=</code> evaluates <code>--Problems</code> differently to <code>:</code>, even though Jay-Z undoubtably has 99 of them (he said so himself).</p>


<h3 class="wp-block-heading" id="declarative-ltdialoggts-and-an-updated-visuallyhidden">Declarative <code>&lt;dialog&gt;</code>s (and an updated <code>.visually-hidden</code>)</h3>


<p>David Bushell demonstrated <a href="https://dbushell.com/2026/02/12/declarative-dialog-menu-invoker-commands/" rel="noopener">how to create <code>&lt;dialog&gt;</code>s declaratively using invoker commands</a>, a useful feature that allows us to skip some J’Script in favor of HTML, and works in all web browsers as of recently.</p>



<p>Also, thanks to an inquisitive question from Ana Tudor, the article spawned a spin-off about the <a href="https://dbushell.com/2026/02/20/visually-hidden/" rel="noopener">minimum number of styles needed for a visually-hidden utility class</a>. Is it still seven?</p>



<p><em>Maybe not…</em></p>


<h3 class="wp-block-heading" id="how-to-truncate-text-from-the-middle">How to truncate text from the middle</h3>


<p>Wes Bos shared a clever trick for <a href="https://bsky.app/profile/wesbos.com/post/3megy3ywotc2h" rel="noopener">truncating text from the middle</a> using only CSS:</p>



<blockquote class="bluesky-embed" data-bluesky-uri="at://did:plc:etdjdgnly5tz5l5xdd4jq76d/app.bsky.feed.post/3megy3ywotc2h" data-bluesky-cid="bafyreiendylscy3u4rkd5tqcm57lj4ynm45pc3hbh5mndidh765affamy4" data-bluesky-embed-color-mode="system"><p lang="en">Someone on reddit posted a demo where CSS truncates text from the middle.

They didn&#x27;t post the code, so here is my shot at it with Flexbox<br><br><a href="https://bsky.app/profile/did:plc:etdjdgnly5tz5l5xdd4jq76d/post/3megy3ywotc2h?ref_src=embed" rel="noopener">[image or embed]</a></p>&mdash; Wes Bos (<a href="https://bsky.app/profile/did:plc:etdjdgnly5tz5l5xdd4jq76d?ref_src=embed" rel="noopener">@wesbos.com</a>) <a href="https://bsky.app/profile/did:plc:etdjdgnly5tz5l5xdd4jq76d/post/3megy3ywotc2h?ref_src=embed" rel="noopener">Feb 9, 2026 at 17:31</a></blockquote><script async src="https://embed.bsky.app/static/embed.js" charset="utf-8"></script>



<p><a href="https://bsky.app/profile/donnie.damato.design/post/3meoz3lzdjc2q" rel="noopener">Donnie D’Amato attempted a more-native solution using <code>::highlight()</code></a>, but <code>::highlight()</code> has some limitations, unfortunately. <a href="https://bsky.app/profile/wilkinson.graphics/post/3meh4neyktk2y" rel="noopener">As Henry Wilkinson mentioned</a>, <a href="https://github.com/w3c/csswg-drafts/issues/3937" rel="noopener">Hazel Bachrach’s 2019 call for a native solution</a> is still an open ticket, so fingers crossed!</p>


<h3 class="wp-block-heading" id="how-to-manage-color-variables-with-relative-color-syntax">How to manage color variables with relative color syntax</h3>


<p>Theo Soti demonstrated <a href="https://theosoti.com/blog/css-relative-colors/" rel="noopener">how to manage color variables with relative color syntax</a>. While not a new feature or concept, it’s frankly the best and most comprehensive walkthrough I’ve ever read that addresses these complexities.</p>


<h3 class="wp-block-heading" id="how-to-customize-lists-the-modern-way">How to customize lists (the modern way)</h3>


<p>In a similar article for Piccalilli, Richard Rutter comprehensively showed us <a href="https://piccalil.li/blog/an-in-depth-guide-to-customising-lists-with-css/" rel="noopener">how to customize lists</a>, although this one has some nuggets of what I can only assume is modern CSS. What’s <code>symbols()</code>? What’s <code>@counter-style</code> and <code>extends</code>? Richard walks you through <em>everything</em>.</p>



<figure class="wp-block-image size-full"><img data-recalc-dims="1" loading="lazy" decoding="async" width="2358" height="1715" src="https://i0.wp.com/css-tricks.com/wp-content/uploads/2026/02/customizing-lists.png?resize=2358%2C1715&#038;ssl=1" alt="A table with headings titled CSS and USE CASE detailing HTML list customizations. It lists the property list-style for basic bullet styles; the pseudo-element li::marker for coloring numbering; the function symbols() for Firefox-specific styles; the at-rule @counter-style for custom numbering systems; the descriptor extends for modifying existing systems; and the pseudo-element li::before for advanced marker positioning." class="wp-image-392662" srcset="https://i0.wp.com/css-tricks.com/wp-content/uploads/2026/02/customizing-lists.png?w=2358&amp;ssl=1 2358w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2026/02/customizing-lists.png?resize=300%2C218&amp;ssl=1 300w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2026/02/customizing-lists.png?resize=1024%2C745&amp;ssl=1 1024w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2026/02/customizing-lists.png?resize=768%2C559&amp;ssl=1 768w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2026/02/customizing-lists.png?resize=1536%2C1117&amp;ssl=1 1536w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2026/02/customizing-lists.png?resize=2048%2C1490&amp;ssl=1 2048w" sizes="auto, (min-width: 735px) 864px, 96vw" /><figcaption class="wp-element-caption">Source: <a href="https://piccalil.li/blog/an-in-depth-guide-to-customising-lists-with-css/" rel="noopener">Piccalilli</a>.</figcaption></figure>



<p>Can&#8217;t get enough on counters? Juan Diego put together <a href="https://css-tricks.com/styling-counters-in-css/">a comprehensive guide right here on CSS-Tricks</a>.</p>


<h3 class="wp-block-heading" id="how-to-create-typescales-using-heading">How to create typescales using <code>:heading</code></h3>


<p>Safari Technology Preview 237 recently began trialing <code>:heading</code>/<code>:heading()</code>, <a href="https://www.alwaystwisted.com/articles/styling-with-the-heading-pseudo-class" rel="noopener">as Stuart Robson explains</a>. <a href="https://www.alwaystwisted.com/articles/building-typographic-scales-with-headings-sibling-index-and-pow/" rel="noopener">The follow-up</a> is even better though, as it shows us how <code>pow()</code> can be used to write cleaner typescale logic, although I ultimately settled on the old-school <code>&lt;h1&gt;</code>&#8211;<code>&lt;h6&gt;</code> elements with a simpler implementation of <code>:heading</code> and no <code>sibling-index()</code>:</p>



<pre rel="CSS" class="wp-block-csstricks-code-block language-css" data-line=""><code markup="tt">:root {
  --font-size-base: 16px;
  --font-size-scale: 1.5;
}

:heading {
  /* Other heading styles */
}

/* Assuming only base/h3/h2/h1 */

body {
  font-size: var(--font-size-base);
}

h3 {
  font-size: calc(var(--font-size-base) * var(--font-size-scale));
}

h2 {
  font-size: calc(var(--font-size-base) * pow(var(--font-size-scale), 2));
}

h1 {
  font-size: calc(var(--font-size-base) * pow(var(--font-size-scale), 3));
}</code></pre>


<h3 class="wp-block-heading" id="una-kravets-introduced-bordershape">Una Kravets introduced <code>border-shape</code></h3>


<p>Speaking of new features, <code>border-shape</code> came as a surprise to me considering that we already have — or <em>will</em> have — <a href="https://css-tricks.com/almanac/properties/c/corner-shape/"><code>corner-shape</code></a>. However, <code>border-shape</code> is different, <a href="https://una.im/border-shape/" rel="noopener">as Una explains</a>. It addresses the issues with borders (because it <em>is</em> the border), allows for more shapes and even the <a href="https://css-tricks.com/almanac/functions/s/shape/"><code>shape()</code> function</a>, and overall it works differently behind the scenes.</p>



<figure class="wp-block-video"><video height="1080" style="aspect-ratio: 1760 / 1080;" width="1760" controls muted src="https://css-tricks.com/wp-content/uploads/2026/02/border-shape.mp4"></video><figcaption class="wp-element-caption">Source: <a href="https://una.im/border-shape/" rel="noopener">Una Kravets</a>.</figcaption></figure>


<h3 class="wp-block-heading" id="moderncss-wants-you-to-stop-writing-css-like-its-2015">modern.css wants you to stop writing CSS like it’s 2015</h3>


<p>It’s time to start using all of that modern CSS, and that’s exactly what <a href="https://modern-css.com/" rel="noopener">modern.css</a> wants to help you do. All of those awesome features that weren’t supported when you first read about them, that you forgot about? Or the ones that you missed or skipped completely? Well, modern.css has 75 code snippets and counting, and all you have to do is copy ‘em.</p>



<figure class="wp-block-image size-full"><img data-recalc-dims="1" loading="lazy" decoding="async" width="2560" height="1607" src="https://i0.wp.com/css-tricks.com/wp-content/uploads/2026/02/modern-css-scaled.png?resize=2560%2C1607&#038;ssl=1" alt="Screenshot of a website titled modern.css showing browser compatibility filters and six code snippets, labeled with their category (e.g., SELECTORS or LAYOUT), difficulty level, topic, an example of outdated code to avoid, a browser support percentage, and a link to view the modern solution." class="wp-image-392664" srcset="https://i0.wp.com/css-tricks.com/wp-content/uploads/2026/02/modern-css-scaled.png?w=2560&amp;ssl=1 2560w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2026/02/modern-css-scaled.png?resize=300%2C188&amp;ssl=1 300w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2026/02/modern-css-scaled.png?resize=1024%2C643&amp;ssl=1 1024w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2026/02/modern-css-scaled.png?resize=768%2C482&amp;ssl=1 768w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2026/02/modern-css-scaled.png?resize=1536%2C964&amp;ssl=1 1536w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2026/02/modern-css-scaled.png?resize=2048%2C1286&amp;ssl=1 2048w" sizes="auto, (min-width: 735px) 864px, 96vw" /></figure>


<h3 class="wp-block-heading" id="kevin-powell-also-has-some-css-snippets-for-you">Kevin Powell also has some CSS snippets for you</h3>


<p>And the commenters? They have some too!</p>



<figure class="wp-block-embed is-type-video is-provider-youtube wp-block-embed-youtube wp-embed-aspect-16-9 wp-has-aspect-ratio"><div class="wp-block-embed__wrapper">
<iframe loading="lazy" title="CSS properties that solve annoying problems" width="500" height="281" src="https://www.youtube.com/embed/dQ8_F4LPCs8?feature=oembed" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>
</div></figure>



<p>Honestly, <a href="https://www.youtube.com/@KevinPowell" rel="noopener">Kevin</a> is the only web dev talker that I actually follow on YouTube, and <a href="https://bsky.app/profile/kevinpowell.co/post/3meo3mlzyfc2y" rel="noopener">he’s <em>so</em> close to a million followers</a> right now, so make sure to hit ‘ol K-Po’s “Subscribe” button.</p>


<h3 class="wp-block-heading" id="in-case-you-missed-it">In case you missed it</h3>


<p>Actually, you didn’t miss that much! <a href="https://developer.mozilla.org/en-US/docs/Mozilla/Firefox/Releases/148" rel="noopener">Firefox 148</a> released the <a href="https://css-tricks.com/almanac/functions/s/shape/"><code>shape()</code> function</a>, which was being held captive by a flag, but is now a baseline feature. <a href="https://developer.apple.com/documentation/safari-technology-preview-release-notes/stp-release-237" rel="noopener">Safari Technology Preview 237</a> became the first to trial <a href="https://css-tricks.com/headings-semantics-fluidity-and-styling-oh-my/#aa-style-headings"><code>:heading</code></a>. Those are all we’ve seen from our beloved browsers in the last couple of weeks (not counting the usual flurry of smaller updates, of course).</p>



<p>That being said, <a href="https://web.dev/blog/interop-2026" rel="noopener">Chrome</a>, <a href="https://webkit.org/blog/17818/announcing-interop-2026/" rel="noopener">Safari</a>, and <a href="https://hacks.mozilla.org/2026/02/launching-interop-2026/" rel="noopener">Firefox</a> announced their targets for <a href="https://css-tricks.com/interop-2026/">Interop 2026</a>, revealing which Web Platform Features they intend to make consistent across all web browsers this year, which <em>more than</em> makes up for the lack of shiny features this week.</p>



<p>Also coming up (but testable in Chrome Canary now, just like <code>border-shape</code>) is the <code>scrolled</code> keyword for scroll-state container queries. <a href="https://www.bram.us/2025/10/22/solved-by-css-scroll-state-queries-hide-a-header-when-scrolling-down-show-it-again-when-scrolling-up/" rel="noopener">Bramus talks about <code>scrolled</code> scroll-state queries here</a>.</p>



<p>Remember, if you don’t want to miss anything, you can catch these <a href="https://css-tricks.com/category/quick-hits/">Quick Hits</a> as the news breaks in the sidebar of <a href="https://css-tricks.com/">css-tricks.com</a>.</p>



<p>See you in a fortnight!</p>
<hr />
<p><small><a rel="nofollow" href="https://css-tricks.com/whats-important-6/">What’s !important #6: :heading, border-shape, Truncating Text From the Middle, and More</a> originally published on <a rel="nofollow" href="https://css-tricks.com">CSS-Tricks</a>, which is part of the <a href="https://try.digitalocean.com/css-tricks/?utm_medium=rss&amp;utm_source=css-tricks.com&amp;utm_campaign=family_&amp;utm_content=">DigitalOcean</a> family. You should <a href="https://css-tricks.com/newsletters/">get the newsletter</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://css-tricks.com/whats-important-6/feed/</wfw:commentRss>
			<slash:comments>1</slash:comments>
		
		<enclosure url="https://css-tricks.com/wp-content/uploads/2026/02/border-shape.mp4" length="2187724" type="video/mp4" />

		<post-id xmlns="com-wordpress:feed-additions:1">392661</post-id>	</item>
	</channel>
</rss>

<!-- plugin=object-cache-pro client=phpredis metric#hits=5009 metric#misses=9 metric#hit-ratio=99.8 metric#bytes=5868649 metric#prefetches=303 metric#store-reads=25 metric#store-writes=2 metric#store-hits=310 metric#store-misses=5 metric#sql-queries=5 metric#ms-total=242.88 metric#ms-cache=16.11 metric#ms-cache-avg=0.6194 metric#ms-cache-ratio=6.6 -->
