<?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://sample-feeds.rowanmanning.com/examples/86e2cc42cc5078615edb74f4b5744c8e/feed.xml" rel="self" type="application/rss+xml" />
	<link>https://css-tricks.com</link>
	<description>Tips, Tricks, and Techniques on using Cascading Style Sheets.</description>
	<lastBuildDate>Tue, 14 Oct 2025 17:52:06 +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>Masonry: Watching a CSS Feature Evolve</title>
		<link>https://css-tricks.com/masonry-watching-a-css-feature-evolve/</link>
					<comments>https://css-tricks.com/masonry-watching-a-css-feature-evolve/#comments</comments>
		
		<dc:creator><![CDATA[Saleh Mubashar]]></dc:creator>
		<pubDate>Mon, 13 Oct 2025 14:31:35 +0000</pubDate>
				<category><![CDATA[Articles]]></category>
		<category><![CDATA[browser]]></category>
		<category><![CDATA[csswg]]></category>
		<category><![CDATA[grid]]></category>
		<category><![CDATA[masonry]]></category>
		<category><![CDATA[specifications]]></category>
		<guid isPermaLink="false">https://css-tricks.com/?p=389636</guid>

					<description><![CDATA[<p>Looking at the CSS Masonry discussions and what they can teach us about the development of new CSS features. What is the CSSWG’s role? What influence do browsers have? What can learn from the way past features evolved?</p>
<hr />
<p><small><a rel="nofollow" href="https://css-tricks.com/masonry-watching-a-css-feature-evolve/">Masonry: Watching a CSS Feature Evolve</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>You’ve probably heard the buzz about <a href="https://css-tricks.com/css-masonry-css-grid/">CSS Masonry</a>. You might even be current on the ongoing debate about how it should be built, with two big proposals on the table, one from the Chrome team and one from the WebKit team.</p>



<p>The two competing proposals are interesting in their own right. <a href="https://developer.chrome.com/blog/masonry-update?hl=en" rel="noopener">Chrome posted about its implementation</a> a while back, and <a href="https://webkit.org/blog/15269/help-us-invent-masonry-layouts-for-css-grid-level-3/" rel="noopener">WebKit followed it up with a detailed post</a> stating their position (which evolved out of a <a href="https://webkit.org/blog/16587/item-flow-part-1-a-new-unified-concept-for-layout/" rel="noopener">third proposal</a> from the Technical Architecture Group).</p>



<p>We’ll rehash some of that in this post, but even more interesting to me is that this entire process is an excellent illustration of how the CSS Working Group (CSSWG), browsers, and developers coalesce around standards for CSS features. There are tons of considerations that go into a feature, like technical implementations and backwards compatibility. But it can be a bit political, too.</p>



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



<p>That’s really what I want to do here: look at the CSS Masonry discussions and what they can teach us about the development of new CSS features. What is the CSSWG’s role? What influence do browsers have? What can learn from the way past features evolved?</p>


<h3 class="wp-block-heading" id="masonry-recap">Masonry Recap</h3>


<p>A masonry layout is different than, say Flexbox and Grid, stacking unevenly-sized items along a single track that automatically wraps into multiple rows or columns, depending on the direction. It’s called the “Pinterest layout” for the obvious reason that it’s the hallmark of Pinterest&#8217;s feed.</p>



<figure class="wp-block-image size-full"><img data-recalc-dims="1" fetchpriority="high" decoding="async" width="2047" height="1513" src="https://i0.wp.com/css-tricks.com/wp-content/uploads/2025/10/Screenshot-2024-09-25-at-9.42.57-AM.png?resize=2047%2C1513&#038;ssl=1" alt="Screenshot of  a Pinterest collection of inspiring quotes, displaying the thumbnail of echoes quote in a masonry-style layout divided in three vertical columns." class="wp-image-389637" srcset="https://i0.wp.com/css-tricks.com/wp-content/uploads/2025/10/Screenshot-2024-09-25-at-9.42.57-AM.png?w=2047&amp;ssl=1 2047w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2025/10/Screenshot-2024-09-25-at-9.42.57-AM.png?resize=300%2C222&amp;ssl=1 300w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2025/10/Screenshot-2024-09-25-at-9.42.57-AM.png?resize=1024%2C757&amp;ssl=1 1024w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2025/10/Screenshot-2024-09-25-at-9.42.57-AM.png?resize=768%2C568&amp;ssl=1 768w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2025/10/Screenshot-2024-09-25-at-9.42.57-AM.png?resize=1536%2C1135&amp;ssl=1 1536w" sizes="(min-width: 735px) 864px, 96vw" /><figcaption class="wp-element-caption">Pinterest’s masonry layout</figcaption></figure>



<p>We could go deeper here, but talking specifically about CSS Masonry isn’t the point. When Masonry entered CSS Working Group discussions, the first prototype actually came from Firefox back in 2019, based on an early draft that integrated masonry behavior directly into Grid.</p>



<p>The Chrome team followed later with a new <code>display: masonry</code> value, treating it as a distinct layout model. They argued that masonry is a different enough layout from Flexbox and Grid to deserve its own <code>display</code> value. Grid’s defaults don’t line up with how masonry works, so why force developers to learn a bunch of extra Grid syntax? Chrome pushed ahead with this idea and <a href="https://developer.chrome.com/blog/masonry-update?hl=en" rel="noopener">prototyped it in Chrome 140</a>:</p>



<pre rel="CSS" class="wp-block-csstricks-code-block language-css" data-line=""><code markup="tt">.container {
  display: masonry;
  grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
  gap: 10px;
}</code></pre>



<p>Meanwhile, the WebKit team has proposed that masonry should be a subset of Grid, rather than its own <code>display</code> type. <a href="https://webkit.org/blog/16587/item-flow-part-1-a-new-unified-concept-for-layout/" rel="noopener">They endorsed a newer direction</a> based on a recommendation by the W3C Technical Architecture Group (TAG) built around a concept called <strong>Item Flow</strong> that unifies <code>flex-flow</code> and <code>grid-auto-flow</code> into a single set of properties. Instead of writing <code>display: masonry</code>, you’d stick with <code>display: grid</code> and use a new <code>item-flow</code> shorthand to collapse rows or columns into a masonry-style layout:</p>



<pre rel="CSS" class="wp-block-csstricks-code-block language-css" data-line=""><code markup="tt">.container {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(14rem, 1fr));
  item-flow: row collapse;  
  gap: 1rem;
}</code></pre>



<p>The debate here really comes down to mental models and how you think about masonry. WebKit sees it as a natural extension of Grid, not a brand-new system. Their thinking is that developers shouldn’t need to learn an entirely new model when most of it already exists in Grid. With <code>item-flow</code>, you’re not telling the browser “this is a whole new layout system,” you’re more or less adjusting the way elements flow in a particular context.</p>


<h3 class="wp-block-heading" id="how-css-features-evolve">How CSS Features Evolve</h3>


<p>This sort of horse-trading isn’t new. Both Flexbox and Grid went through years of competing drafts before becoming the specs we use today. Flexbox, in particular, had a rocky rollout in the early 2010s. Those who were in the trenches at the time likely remember <a href="https://css-tricks.com/old-flexbox-and-new-flexbox/">multiple conflicting syntaxes floating around at once</a>. The initial release had missing gaps and browsers implemented the features differently, leading to all kinds of things, like proprietary properties, experimental releases, and different naming conventions that made the learning curve rather steep, and even <a href="https://css-tricks.com/using-flexbox/">Frankenstein-like usage in some cases</a> to get the most browser support.</p>



<p>In other words, Flexbox (nor Grid, for that matter) did not enjoyed a seamless release, but we’ve gotten to a place where the browsers implementations are interoperable with one another. That’s a big deal for developers like us who often juggle inconsistent support for various features. Heck, <a href="https://www.roboleary.net/blog/baseline-text-wrap-pretty/" rel="noopener">Rob O’Leary recently published the rabbit hole he traveled</a> trying to use <a href="https://css-tricks.com/pretty-is-in-the-eye-of-the-beholder/"><code>text-wrap: pretty</code></a> in his work, and that’s considered “Baseline” support that is “widely available.”</p>



<p>But I digress. It’s worth noting that Flexbox faced unique challenges early on, and masonry has benefitted from those lessons learned. I reached out to CSSWG member <a href="https://xanthir.com/contact/" rel="noopener">Tab Atkins</a><a href="https://xanthir.com/contact/" rel="noopener">-Bittner</a> for a little context since they were heavily involved in editing the <a href="https://www.w3.org/TR/css-flexbox-1/" rel="noopener">Flexbox specification</a>.</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p>“Flexbox was the first of the modern layout algorithms; we made a lot of mistakes and missteps while writing it, because we were trying to figure out how a modern layout model should work.”</p>
</blockquote>



<p>In other words, Flexbox was sort of a canary in the coal mine as the CSSWG considered what a modern CSS layout syntax should accomplish. This greatly benefited the work put into defining CSS Grid because a lot of the foundation for things like tracks, intrinsic sizing, and proportions were already tackled. Atkins-Bittner goes on further to explain that the Grid specification process also forced the CSSWG to rethink several of Flexbox’s design choices in the process.</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p>“We found a lot of decisions that made sense on their own in Flexbox needed to be changed if we wanted them to apply more generally.”</p>
</blockquote>



<p>This also explains why Flexbox underwent <a href="https://www.w3.org/standards/history/css-flexbox-1/" rel="noopener">several revisions</a> following its initial release.</p>



<p>It also highlights another key point: <strong>CSS features are</strong> <strong>always</strong> <strong>evolving</strong>. Early debate and iteration are essential because they reduce the need for big breaking changes. Still, some of the Flexbox mistakes (<a href="https://css-tricks.com/the-mistakes-of-css/">which do happen</a>) became widely adopted. Browsers had widely implemented their approaches and the specification caught up to it while trying to establish a consistent language that helps both user agents and developers implemented and use the features, respectively.</p>



<p>All this to say: Masonry is in a much better spot than Flexbox was at its inception. It benefits from the 15+ years that the CSSWG, browsers, and developers contributed to Flexbox and Grid over that time. The discussions are now less about fixing under-specified details and more about high-level design choices. Hence, novel ideas born from Masonry that combine the features of Flexbox and Grid into the new Item Flow proposal.</p>



<p>It’s messy. And weird. But it’s how things get done.</p>


<h3 class="wp-block-heading" id="the-csswg-s-role">The CSSWG’s Role</h3>


<p>Getting to this point requires process. And in CSS, that process runs through the Working Group. The CSS Working Group (CSSWG) runs on consensus: members debate in the open, weigh pros and cons, and push browsers towards common ground.</p>



<p><a href="https://www.oddbird.net/authors/miriam/" rel="noopener">Miriam Suzanne</a>, an invited expert with the CSSWG (and <a href="https://css-tricks.com/author/miriam/">CSS-Tricks alumni</a>), describes the process like this:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p>“The group runs on a consensus model, so everyone has to eventually come to an agreement — or at least agree not to block the most popular path forward.”</p>
</blockquote>



<p><strong>But consensus only applies to the specifications.</strong> Browsers still decide when and how to those features are shipped, as Suzanne continues:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p>“Browsers make their own decisions about how strictly they follow a spec, and sometimes release features that haven’t been fully specified. That can lead to situations where the group decides to change a spec years later to match what browsers actually implemented.”</p>
</blockquote>



<p>So, while the CSSWG facilitates discussions around features, it can’t actually stop browsers from shipping those features, let alone how they’re implemented. It’s a consensus-driven system, but consensus is only about publishing a specification. In practice, momentum can shift if one vendor is the first to ship or prototype a feature.</p>



<p>In most cases, though, the specification adoption process results in a stronger proposal overall. By the time features ship, the idea is that they’ve already been thoroughly debated, which in theory, reduces the need for significant revisions later that could lead to breaking changes. Backwards compatibility is always at the forefront of CSSWG discussions.</p>



<p>Developer feedback also plays an important role, though there isn’t a single standardized way that it is solicited, collected, or used. For the CSSWG, the <a href="https://github.com/w3c/csswg-drafts" rel="noopener">csswg-drafts GitHub repo</a> is the primary source of feedback and discussion, while browsers also run their own surveys and gather input through various other channels such as Chrome’s <a href="https://www.chromium.org/developers/technical-discussion-groups/" rel="noopener">technical discussion groups</a> and Webkit’s <a href="https://lists.webkit.org/mailman/listinfo" rel="noopener">mailing lists</a>.</p>


<h3 class="wp-block-heading" id="the-bigger-picture">The Bigger Picture</h3>


<p>Browsers are in the business of shaping new features. It’s also in their best interest for a number of reasons. Proposing new ideas gives them a seat at the table. Prototyping new features gets developers excited and helps further refine edge cases. Implementing new features (particularly first) gives them a competitive edge in the consumer market.</p>



<p>All that said, prototyping features ahead of consensus is a bit of a tightrope walk.</p>



<p>And that’s where Masonry comes back into the bigger picture. Chrome shipped a prototype of the feature that leans heavily into the first proposal for a new <code>display: masonry</code> value. Other browsers have yet to ship competing prototypes, but have openly discussed their positions, <a href="https://webkit.org/blog/16026/css-masonry-syntax/" rel="noopener">as WebKit did in subsequent blog posts</a>.</p>



<p>At first glance, that might suggest that Chrome is taking a heavy-handed approach to tip the scales in its favorable direction. But there’s a lot to like about prototyping features because it’s proof in the pudding for real-world uses by allowing developers early access to experiment.</p>



<p>Atkins-Bittner explains it nicely:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p>“Prototyping before consensus is an important part of building consensus. You get early implementation feedback, you get more eyes on the problem (the implementing engineers rather than just the spec authors).”</p>
</blockquote>



<p>This kind of “soft” commit moves conversations forward while leaving room to change course, if needed, based on real-world use.</p>



<p>But there’s obviously a tension here as well. Browsers may be custodians of web standards and features, but they’re still employed by massive companies that are selling a product at the end of the day. It’s easy to get cynical. And political.</p>



<p>In theory, though, allowing browsers to voluntarily adopt features gives everyone choice: browsers compete in the market based on what they implement, developers gain new features that push the web further, and everyone gets to choose the browser that best fits their browsing needs.</p>



<p>If one company controls access to a huge share of users, however, those choices feel less accessible. Standards often get shaped just as much by market power as by technical merit.</p>


<h3 class="wp-block-heading" id="where-we-re-at">Where We’re At</h3>


<p>At the end of the day, standards get shaped by a mix of politics, technical trade-offs, and developer feedback. Consensus is messy, and it’s rarely about one side “winning.” With masonry, it might look like Google got its way, but in reality the outcome reflects input from both proposals, plus ideas from the wider community.</p>



<p>As of this writing:</p>



<ul class="wp-block-list">
<li>Masonry will be a <strong>new display type</strong>, but must include the word “grid” in the name. The exact keyword is still being debated.</li>



<li>The CSSWG has resolved to proceed with the proposed <code>**item-flow**</code> approach.</li>



<li>Grid will be used for layout templates and explicitly placing items in them.</li>



<li>Some details, like a possible shorthand syntax and track listing defaults, are still being discussed.</li>
</ul>


<h3 class="wp-block-heading" id="further-reading">Further reading</h3>


<p>This is a big topic, one that goes much deeper and further than we’ve gone here. While working on this article, a few others popped up that are very much worth your time to see the spectrum of ideas and opinions about the CSS standards process:</p>



<ul class="wp-block-list">
<li>Alex Russell’s <a href="https://infrequently.org/series/effective-standards-work/" rel="noopener">post</a> about the standards adoption process in browsers.</li>



<li>Rob O’Leary’s <a href="https://www.roboleary.net/blog/baseline-text-wrap-pretty/" rel="noopener">article</a> about struggling with <code>text-wrap: pretty</code>, explaining that “Baseline” doesn’t always mean consistent support in practice.</li>



<li>David Bushell’s <a href="https://dbushell.com/2025/09/08/trillion-dollar-elephants/" rel="noopener">piece</a> about the WHATWG. It isn’t about the CSSWG specifically, but covers similar discussions on browser politics and standards consensus.</li>
</ul>
<hr />
<p><small><a rel="nofollow" href="https://css-tricks.com/masonry-watching-a-css-feature-evolve/">Masonry: Watching a CSS Feature Evolve</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/masonry-watching-a-css-feature-evolve/feed/</wfw:commentRss>
			<slash:comments>1</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">389636</post-id>	</item>
		<item>
		<title>We Completely Missed width/height: stretch</title>
		<link>https://css-tricks.com/we-completely-missed-width-height-stretch/</link>
					<comments>https://css-tricks.com/we-completely-missed-width-height-stretch/#comments</comments>
		
		<dc:creator><![CDATA[Daniel Schwarz]]></dc:creator>
		<pubDate>Fri, 10 Oct 2025 14:03:52 +0000</pubDate>
				<category><![CDATA[Articles]]></category>
		<category><![CDATA[box model]]></category>
		<category><![CDATA[css properties]]></category>
		<category><![CDATA[layout]]></category>
		<guid isPermaLink="false">https://css-tricks.com/?p=389611</guid>

					<description><![CDATA[<p>The <abbr title="too long, didn't read">TL;DR</abbr> is that <code>stretch</code> does the same thing as declaring <code>100%</code>, but ignores padding when looking at the available space. </p>
<hr />
<p><small><a rel="nofollow" href="https://css-tricks.com/we-completely-missed-width-height-stretch/">We Completely Missed width/height: stretch</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 <code>stretch</code> keyword, which you can use with <code>width</code> and <code>height</code> (as well as <code>min-width</code>, <code>max-width</code>, <code>min-height</code>, and <code>max-height</code>, of course), was shipped in Chromium web browsers back in June 2025. But the value is actually a unification of the non-standard <code>-webkit-fill-available</code> and <code>-moz-available</code> values, the latter of which has been available to use in Firefox since 2008.</p>



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



<p>The issue was that, before the <code>@supports</code> at-rule, there was no nice way to implement the right value for the right web browser, and I suppose we just forgot about it after that until, whoops, one day I see <a href="https://bsky.app/profile/davatron5000.bsky.social/post/3lvomxgrds22s" rel="noopener">Dave Rupert casually put it out there on Bluesky</a> a month ago:</p>



<figure class="wp-block-image size-full is-resized ticss-4a75a635"><img data-recalc-dims="1" decoding="async" width="1182" height="1082" src="https://i0.wp.com/css-tricks.com/wp-content/uploads/2025/10/s_AC0E97E63C42D4DCB598202C211DCD808E080150784BF489C692DBD48FB52DA8_1759247326546_Screenshot2025-09-30at9.48.26AM.png?resize=1182%2C1082&#038;ssl=1" alt="Dave Rupert post on Bluesky: Did you know you can do height: stretch now in CSS? Works for width too. via Patrick Brosset." class="wp-image-389619" style="width:500px;height:auto" srcset="https://i0.wp.com/css-tricks.com/wp-content/uploads/2025/10/s_AC0E97E63C42D4DCB598202C211DCD808E080150784BF489C692DBD48FB52DA8_1759247326546_Screenshot2025-09-30at9.48.26AM.png?w=1182&amp;ssl=1 1182w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2025/10/s_AC0E97E63C42D4DCB598202C211DCD808E080150784BF489C692DBD48FB52DA8_1759247326546_Screenshot2025-09-30at9.48.26AM.png?resize=300%2C275&amp;ssl=1 300w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2025/10/s_AC0E97E63C42D4DCB598202C211DCD808E080150784BF489C692DBD48FB52DA8_1759247326546_Screenshot2025-09-30at9.48.26AM.png?resize=1024%2C937&amp;ssl=1 1024w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2025/10/s_AC0E97E63C42D4DCB598202C211DCD808E080150784BF489C692DBD48FB52DA8_1759247326546_Screenshot2025-09-30at9.48.26AM.png?resize=768%2C703&amp;ssl=1 768w" sizes="(min-width: 735px) 864px, 96vw" /></figure>



<p>Layout pro <a href="https://youtu.be/iZZXOuLxagE" rel="noopener">Miriam Suzanne recorded an explainer</a> shortly thereafter. It’s worth giving this value a closer look.</p>


<h3 class="wp-block-heading" id="what-does-stretch-do-">What does <code>stretch</code> do?</h3>


<p>The quick answer is that <code>stretch</code> does the same thing as declaring <code>100%</code>, but ignores <code>padding</code> when looking at the available space. In short, if you’ve ever wanted <code>100%</code> to <em>actually mean <code>100%</code></em> (when using <code>padding</code>), <code>stretch</code> is what you’re looking for:</p>



<pre rel="CSS" class="wp-block-csstricks-code-block language-css" data-line=""><code markup="tt">div {
  padding: 3rem 50vw 3rem 1rem;
  width: 100%; /* 100% + 50vw + 1rem, causing overflow */
  width: stretch; /* 100% including padding, no overflow */
}</code></pre>



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



<p>The more <em>technical</em> answer is that the <code>stretch</code> value sets the width or height of the element’s margin box (rather than the box determined by <code>box-sizing</code>) to match the width/height of its <em>containing</em> block.</p>



<p><strong>Note:</strong> It’s never a bad idea to revisit the <a href="https://css-tricks.com/the-css-box-model/">CSS Box Model</a> for a refresher on different box sizings.</p>



<p>And on that note — yes — we can achieve the same result by declaring <code>box-sizing: border-box</code>, <a href="https://css-tricks.com/international-box-sizing-awareness-day/">something that many of us do</a>, as a CSS reset in fact.</p>



<pre rel="CSS" class="wp-block-csstricks-code-block language-css" data-line=""><code markup="tt">*,
::before,
::after {
  box-sizing: border-box;
}</code></pre>



<p>I suppose that it’s because of this solution that we forgot all about the non-standard values and didn’t pay any attention to <code>stretch</code> when it shipped, but I actually rather like <code>stretch</code> and don’t touch <code>box-sizing</code> at all now.</p>


<h3 class="wp-block-heading" id="yay-stretch-nay-box-sizing-">Yay <code>stretch</code>, nay <code>box-sizing</code></h3>


<p>There isn’t an especially compelling reason to switch to <code>stretch</code>, but there are several small ones. Firstly, the Universal selector (<code>*</code>) doesn’t apply to pseudo-elements, which is why the CSS reset typically includes <code>::before</code> and <code>::after</code>, and not only are there way more pseudo-elements than we might think, but the rise in declarative HTML components means that we’ll be seeing more of them. Do you really want to maintain something like the following?</p>



<pre rel="CSS" class="wp-block-csstricks-code-block language-css" data-line=""><code markup="tt">*, 
::after,
::backdrop,
::before,
::column,
::checkmark,
::cue (and ::cue()),
::details-content,
::file-selector-button,
::first-letter,
::first-line,
::grammar-error,
::highlight(),
::marker,
::part(),
::picker(),
::picker-icon,
::placeholder,
::scroll-button(),
::scroll-marker,
::scroll-marker-group,
::selection,
::slotted(),
::spelling-error,
::target-text,
::view-transition,
::view-transition-image-pair(),
::view-transition-group(),
::view-transition-new(),
::view-transition-old() {
  box-sizing: border-box;
}</code></pre>



<p>Okay, I’m being dramatic. Or maybe I’m not? I don’t know. I’ve actually used quite a few of these and having to maintain a list like this sounds dreadful, although I’ve certainly seen crazier CSS resets. Besides, you might <em>want</em> <code>100%</code> to exclude padding, and if you’re a fussy coder like me you won’t enjoy un-resetting CSS resets.</p>


<h3 class="wp-block-heading" id="animating-to-and-from-stretch-">Animating to and from <code>stretch</code></h3>


<p>Opinions aside, there’s one thing that <code>box-sizing</code> certainly isn’t and that’s <em>animatable</em>. If you didn’t catch it the first time, we do transition to and from <code>100%</code> and <code>stretch</code>:</p>



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



<p>Because <code>stretch</code> is a keyword though, you’ll need to <a href="https://css-tricks.com/almanac/properties/i/interpolate-size/">interpolate its size</a>, and you can only do that by declaring <code>interpolate-size: allow-keywords</code> (on the <code>:root</code> if you want to activate interpolation globally):</p>



<pre rel="CSS" class="wp-block-csstricks-code-block language-css" data-line=""><code markup="tt">:root {
  /* Activate interpolation */
  interpolate-size: allow-keywords;
}

div {
  width: 100%;
  transition: 300ms;

  &amp;:hover {
    width: stretch;
  }
}</code></pre>



<p>The <a href="https://css-tricks.com/almanac/functions/c/calc-size/"><code>calc-size()</code> function</a> wouldn’t be useful here due to the web browser support of <code>stretch</code> and the fact that <code>calc-size()</code> doesn’t support its non-standard alternatives. In the future though, you’ll be able to use <code>width: calc-size(stretch, size)</code> in the example above to interpolate <em>just that specific</em> width.</p>


<h3 class="wp-block-heading" id="web-browser-support">Web browser support</h3>


<p>Web browser support is limited to Chromium browsers for now:</p>



<ul class="wp-block-list">
<li>Opera 122+</li>



<li>Chrome and Edge 138+ (140+ on Android)</li>
</ul>



<p>Luckily though, because we have those non-standard values, we can use the <code>@supports</code> at-rule to implement the right value for the right browser. The best way to do that (and strip away the <code>@supports</code> logic later) is to save the right value as a custom property:</p>



<pre rel="CSS" class="wp-block-csstricks-code-block language-css" data-line=""><code markup="tt">:root {
  /* Firefox */
  @supports (width: -moz-available) {
    --stretch: -moz-available;
  }

  /* Safari */
  @supports (width: -webkit-fill-available) {
    --stretch: -webkit-fill-available;
  }

  /* Chromium */
  @supports (width: stretch) {
    --stretch: stretch;
  }
}

div {
  width: var(--stretch);
}</code></pre>



<p>Then later, once <code>stretch</code> is widely supported, switch to:</p>



<pre rel="CSS" class="wp-block-csstricks-code-block language-css" data-line=""><code markup="tt">div {
  width: stretch;
}</code></pre>


<h3 class="wp-block-heading" id="in-a-nutshell">In a nutshell</h3>


<p>While this might not exactly win Feature of the Year awards (I haven’t heard a <em>whisper</em> about it), quality-of-life improvements like this are some of my favorite features. If you’d rather use <code>box-sizing: border-box</code>, that’s totally fine — it works really well. Either way, more ways to write and organize code is never a bad thing, especially if certain ways don’t align with your mental model.</p>



<p>Plus, using a brand new feature in production is just too tempting to resist. Irrational, but tempting and satisfying!</p>
<hr />
<p><small><a rel="nofollow" href="https://css-tricks.com/we-completely-missed-width-height-stretch/">We Completely Missed width/height: stretch</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/we-completely-missed-width-height-stretch/feed/</wfw:commentRss>
			<slash:comments>1</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">389611</post-id>	</item>
		<item>
		<title>The thing about contrast-color</title>
		<link>https://css-tricks.com/the-thing-about-contrast-color/</link>
					<comments>https://css-tricks.com/the-thing-about-contrast-color/#comments</comments>
		
		<dc:creator><![CDATA[Geoff Graham]]></dc:creator>
		<pubDate>Wed, 08 Oct 2025 14:52:58 +0000</pubDate>
				<category><![CDATA[Links]]></category>
		<category><![CDATA[color]]></category>
		<category><![CDATA[CSS functions]]></category>
		<guid isPermaLink="false">https://css-tricks.com/?p=389698</guid>

					<description><![CDATA[<p>One of our favorites, <a href="https://stuffandnonsense.co.uk/blog/the-thing-about-contrast-color" rel="noopener">Andy Clarke</a>, on the one thing keeping the CSS <code><a href="https://css-tricks.com/exploring-the-css-contrast-color-function-a-second-time/">contrast-color()</a></code> function from true glory:</p>
<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p>For my website design, I chose a dark blue background colour (<code>#212E45</code>) and light text (<code>#d3d5da</code>). This </p>
</blockquote>
<p>&#8230;</p>
<hr />
<p><small><a rel="nofollow" href="https://css-tricks.com/the-thing-about-contrast-color/">The thing about contrast-color</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>One of our favorites, <a href="https://stuffandnonsense.co.uk/blog/the-thing-about-contrast-color" rel="noopener">Andy Clarke</a>, on the one thing keeping the CSS <code><a href="https://css-tricks.com/exploring-the-css-contrast-color-function-a-second-time/">contrast-color()</a></code> function from true glory:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p>For my website design, I chose a dark blue background colour (<code>#212E45</code>) and light text (<code>#d3d5da</code>). This colour is off-white to soften the contrast between background and foreground colours, while maintaining a decent level for accessibility considerations.</p>



<p>But here’s the thing. The <code>contrast-color()</code> function chooses either white for dark backgrounds or black for light ones. At least to my eyes, that contrast is too high and makes reading less comfortable, at least for me.</p>
</blockquote>



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



<p>Word. White and black are two very safe colors to create contrast with another color value. But the <em>amount</em> of contrast between a solid white/black and any other color, while offering the most contrast, may not be the best contrast ratio overall.</p>



<p>This was true when added a dark color scheme to my personal website. The contrast between the background color, a dark blue (<code>hsl(238.2 53.1% 12.5%</code>), and solid white (<code>#fff</code>) was too jarring for me.</p>



<figure class="wp-block-image size-full"><img data-recalc-dims="1" loading="lazy" decoding="async" width="1414" height="662" src="https://i0.wp.com/css-tricks.com/wp-content/uploads/2025/10/Screenshot-2025-10-07-at-9.24.53-AM.png?resize=1414%2C662&#038;ssl=1" alt="" class="wp-image-389699" srcset="https://i0.wp.com/css-tricks.com/wp-content/uploads/2025/10/Screenshot-2025-10-07-at-9.24.53-AM.png?w=1414&amp;ssl=1 1414w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2025/10/Screenshot-2025-10-07-at-9.24.53-AM.png?resize=300%2C140&amp;ssl=1 300w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2025/10/Screenshot-2025-10-07-at-9.24.53-AM.png?resize=1024%2C479&amp;ssl=1 1024w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2025/10/Screenshot-2025-10-07-at-9.24.53-AM.png?resize=768%2C360&amp;ssl=1 768w" sizes="auto, (min-width: 735px) 864px, 96vw" /></figure>



<p>To tone that down, I&#8217;d want something a little less opaque than what, say <code>hsl(100 100% 100% / .8)</code>, or 20% lighter than white. Can&#8217;t do that with <code>contrast-color()</code>, though. That&#8217;s why I reach for <code><a href="https://css-tricks.com/almanac/functions/l/light-dark/">light-dark()</a></code> instead:</p>



<pre rel="CSS" class="wp-block-csstricks-code-block language-css" data-line=""><code markup="tt">body {
  color: light-dark(hsl(238.2 53.1% 12.5%), hsl(100 100% 100% / .8));
}</code></pre>



<figure class="wp-block-image size-full"><img data-recalc-dims="1" loading="lazy" decoding="async" width="1328" height="674" src="https://i0.wp.com/css-tricks.com/wp-content/uploads/2025/10/Screenshot-2025-10-07-at-9.23.49-AM.png?resize=1328%2C674&#038;ssl=1" alt="" class="wp-image-389701" srcset="https://i0.wp.com/css-tricks.com/wp-content/uploads/2025/10/Screenshot-2025-10-07-at-9.23.49-AM.png?w=1328&amp;ssl=1 1328w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2025/10/Screenshot-2025-10-07-at-9.23.49-AM.png?resize=300%2C152&amp;ssl=1 300w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2025/10/Screenshot-2025-10-07-at-9.23.49-AM.png?resize=1024%2C520&amp;ssl=1 1024w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2025/10/Screenshot-2025-10-07-at-9.23.49-AM.png?resize=768%2C390&amp;ssl=1 768w" sizes="auto, (min-width: 735px) 864px, 96vw" /></figure>



<p>Will <code>contrast-color()</code> support more than a black/white duo in the future? The spec says yes:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p>Future versions of this specification are expected to introduce more control over both the contrast algorithm(s) used, the use cases, as well as the returned color.</p>
</blockquote>



<p>I&#8217;m sure it&#8217;s one of those things that &#8216;s easier said than done, as the &#8220;right&#8221; amount of contrast is more nuanced than simply saying it&#8217;s a ratio of 4.5:1. There are user preferences to take into account, too. And then it gets into weeds of <a href="https://www.smashingmagazine.com/2025/05/wcag-3-proposed-scoring-model-shift-accessibility-evaluation/" rel="noopener">work being done on WCAG 3.0</a>, which Danny does a nice job summarizing in a recent article detailing <a href="https://css-tricks.com/exploring-the-css-contrast-color-function-a-second-time/#aa-the-shortcomings-of-contrast-color">the shortcomings of <code>contrast-color()</code></a>.</p>



<p></p>
<hr />
<p><small><a rel="nofollow" href="https://css-tricks.com/the-thing-about-contrast-color/">The thing about contrast-color</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-thing-about-contrast-color/feed/</wfw:commentRss>
			<slash:comments>2</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">389698</post-id>	</item>
		<item>
		<title>Getting Creative With shape-outside</title>
		<link>https://css-tricks.com/getting-creative-with-shape-outside/</link>
					<comments>https://css-tricks.com/getting-creative-with-shape-outside/#comments</comments>
		
		<dc:creator><![CDATA[Andy Clarke]]></dc:creator>
		<pubDate>Mon, 06 Oct 2025 15:45:40 +0000</pubDate>
				<category><![CDATA[Articles]]></category>
		<category><![CDATA[css shapes]]></category>
		<category><![CDATA[images]]></category>
		<category><![CDATA[UI/IX Design]]></category>
		<guid isPermaLink="false">https://css-tricks.com/?p=389550</guid>

					<description><![CDATA[<p>There are so many creative opportunities for using <code>shape-outside</code> that I’m surprised I see it used so rarely. So, how can you use it to add personality to a design? Here’s how I do it.</p>
<hr />
<p><small><a rel="nofollow" href="https://css-tricks.com/getting-creative-with-shape-outside/">Getting Creative With shape-outside</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-tricks.com/getting-creative-with-images-in-long-form-content/">Last time</a>, I asked, “Why do so many long-form articles feel visually flat?” I explained that:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p>“Images in long-form content can (and often should) do more than illustrate. They can shape how people navigate, engage with, and interpret what they’re reading. They help set the pace, influence how readers feel, and add character that words alone can’t always convey.”</p>
</blockquote>



<p>Then, I touched on the expressive possibilities of CSS Shapes and how, by using <a href="https://css-tricks.com/almanac/properties/s/shape-outside/"><code>shape-outside</code></a>, you can wrap text around an image’s alpha channel to add energy to a design and keep it feeling lively.</p>



<p>There are so many creative opportunities for using <code>shape-outside</code> that I’m surprised I see it used so rarely. So, how can you use it to add personality to a design? Here’s how I do it.</p>



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



<figure class="wp-block-image size-full"><img data-recalc-dims="1" loading="lazy" decoding="async" width="1920" height="880" src="https://i0.wp.com/css-tricks.com/wp-content/uploads/2025/10/s_3EDD0D53C943B8C049C7F94CCE0401810B7E10ADC9A32E2DF8EBA64F2023E651_1759071926286_2025-09-28-1.webp?resize=1920%2C880&#038;ssl=1" alt="A collage of three screenshots of Patty Melt's website side-by-side demonstrating the use of text flowing around image shapes." class="wp-image-389552" srcset="https://i0.wp.com/css-tricks.com/wp-content/uploads/2025/10/s_3EDD0D53C943B8C049C7F94CCE0401810B7E10ADC9A32E2DF8EBA64F2023E651_1759071926286_2025-09-28-1.webp?w=1920&amp;ssl=1 1920w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2025/10/s_3EDD0D53C943B8C049C7F94CCE0401810B7E10ADC9A32E2DF8EBA64F2023E651_1759071926286_2025-09-28-1.webp?resize=300%2C138&amp;ssl=1 300w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2025/10/s_3EDD0D53C943B8C049C7F94CCE0401810B7E10ADC9A32E2DF8EBA64F2023E651_1759071926286_2025-09-28-1.webp?resize=1024%2C469&amp;ssl=1 1024w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2025/10/s_3EDD0D53C943B8C049C7F94CCE0401810B7E10ADC9A32E2DF8EBA64F2023E651_1759071926286_2025-09-28-1.webp?resize=768%2C352&amp;ssl=1 768w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2025/10/s_3EDD0D53C943B8C049C7F94CCE0401810B7E10ADC9A32E2DF8EBA64F2023E651_1759071926286_2025-09-28-1.webp?resize=1536%2C704&amp;ssl=1 1536w" sizes="auto, (min-width: 735px) 864px, 96vw" /><figcaption class="wp-element-caption">Patty Meltt is an up-and-coming country music sensation.</figcaption></figure>



<p><strong>My brief:</strong> Patty Meltt is an up-and-coming country music sensation, and she needed a website to launch her new album and tour. She wanted it to be distinctive-looking and memorable, so she called <a href="https://stuffandnonsense.co.uk" rel="noopener">Stuff &amp; Nonsense</a>. Patty’s not real, but the challenges of designing and developing sites like hers are.</p>



<p>Most <code>shape-outside</code> <a href="https://css-tricks.com/almanac/properties/s/shape-outside/">guides</a> start with circles and polygons. That’s useful, but it answers only the <em>how</em>. Designers need the <em>why</em> — otherwise it’s just another CSS property.</p>



<p>Whatever shape its subject takes, every image sits inside a box. By default, text flows above or below that box. If I float an image left or right, the text wraps around the rectangle, regardless of what’s inside. That’s the limitation <code>shape-outside</code> overcomes.</p>



<p><code>shape-outside</code> lets you break free from those boxes by enabling layouts that can respond to the contours of an image. That shift from images in boxes to letting the image content define the composition is what makes using <code>shape-outside</code> so interesting.</p>



<p>Solid blocks of text around straight-edged images can feel static. But text that bends around a guitar or curves around a portrait creates movement, which can make a story more compelling and engaging.</p>



<p>CSS <code>shape-outside</code> enables text to flow around any custom shape, including an image’s alpha channel (i.e., the transparent areas):</p>



<pre rel="CSS" class="wp-block-csstricks-code-block language-css" data-line=""><code markup="tt">img {
  float: left;
  width: 300px;
  shape-outside: url('patty.webp');
  shape-image-threshold: .5;
  shape-margin: 1rem;
}</code></pre>



<p>First, a quick recap.</p>



<p>For text to flow around elements, they need to <code>float</code> either left or right and have their <code>width</code> defined. The <a href="https://css-tricks.com/almanac/properties/s/shape-outside/"><code>shape-outside</code></a> URL selects an image with an alpha channel, such as a PNG or WebP. The <a href="https://css-tricks.com/almanac/properties/s/shape-image-threshold/"><code>shape-image-threshold</code></a> property sets the alpha channel threshold for creating a shape. Finally, there’s the <a href="https://css-tricks.com/almanac/properties/s/shape-margin/"><code>shape-margin</code></a> property which — believe it or not — creates a margin around the shape.</p>



<p class="is-style-explanation">Interactive examples from this article are <a href="https://stuffandnonsense.co.uk/lab/shape-outside.html" rel="noopener">available in my lab</a>.</p>


<h3 class="wp-block-heading" id="multiple-image-shapes">Multiple image shapes</h3>


<p>When I’m adding images to a long-form article, I ask myself, “How can they help shape someone’s experience?” Flowing text around images can slow people down a little, making their experience more immersive. Visually, it brings text and image into a closer relationship, making them feel part of a shared composition rather than isolated elements.</p>



<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/2025/10/s_3EDD0D53C943B8C049C7F94CCE0401810B7E10ADC9A32E2DF8EBA64F2023E651_1759071973283_2025-09-28-2.webp?resize=1920%2C1080&#038;ssl=1" alt="An image of Patty staring into the camera on the left and two columns of white text on a black background on the right." class="wp-image-389554" srcset="https://i0.wp.com/css-tricks.com/wp-content/uploads/2025/10/s_3EDD0D53C943B8C049C7F94CCE0401810B7E10ADC9A32E2DF8EBA64F2023E651_1759071973283_2025-09-28-2.webp?w=1920&amp;ssl=1 1920w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2025/10/s_3EDD0D53C943B8C049C7F94CCE0401810B7E10ADC9A32E2DF8EBA64F2023E651_1759071973283_2025-09-28-2.webp?resize=300%2C169&amp;ssl=1 300w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2025/10/s_3EDD0D53C943B8C049C7F94CCE0401810B7E10ADC9A32E2DF8EBA64F2023E651_1759071973283_2025-09-28-2.webp?resize=1024%2C576&amp;ssl=1 1024w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2025/10/s_3EDD0D53C943B8C049C7F94CCE0401810B7E10ADC9A32E2DF8EBA64F2023E651_1759071973283_2025-09-28-2.webp?resize=768%2C432&amp;ssl=1 768w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2025/10/s_3EDD0D53C943B8C049C7F94CCE0401810B7E10ADC9A32E2DF8EBA64F2023E651_1759071973283_2025-09-28-2.webp?resize=1536%2C864&amp;ssl=1 1536w" sizes="auto, (min-width: 735px) 864px, 96vw" /><figcaption class="wp-element-caption">Columns without <code>shape-outside</code> applied to them</figcaption></figure>



<p>Patty’s life story — from singing in honky-tonks to headlining stadiums — contains two sections: one about her, the other about her music. I added a tall vertical image of Patty to her biography and two smaller album covers to the music column:</p>



<pre rel="HTML" class="wp-block-csstricks-code-block language-markup" data-line=""><code markup="tt">&lt;section id="patty">
  &lt;div>
    &lt;img src="patty.webp" alt="">
    [...]
  &lt;/div>

  &lt;div>
    &lt;img src="album-1.webp" alt="">
    [...]
    &lt;img src="album-2.webp" alt="">
    [...]
  &lt;/div>
&lt;/section></code></pre>



<p>A simple grid then creates the two columns:</p>



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



<p>I like to make my designs as flexible as I can, so instead of specifying image widths and margins in static pixels, I opted for percentages on those column widths so their actual size is relative to whatever the size of the container happens to be:</p>



<pre rel="CSS" class="wp-block-csstricks-code-block language-css" data-line=""><code markup="tt">#patty > *:nth-child(1) img {
  float: left;
  width: 50%;
  shape-outside: url("patty.webp");
  shape-margin: 2%;
}

#patty > *:nth-child(2) img:nth-of-type(1) {
  float: left;
  width: 45%;
  shape-outside: url("album-1.webp");
  shape-margin: 2%;
}

#patty > *:nth-child(2) img:nth-of-type(2) {
  float: right;
  width: 45%;
  shape-outside: url("album-2.webp");
  shape-margin: 2%;
}</code></pre>



<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/2025/10/s_3EDD0D53C943B8C049C7F94CCE0401810B7E10ADC9A32E2DF8EBA64F2023E651_1759072011555_2025-09-28-3.webp?resize=1920%2C1080&#038;ssl=1" alt="Imager of Patty on the left and two columns of white text on a black background to the right. The second column of text flows around two images showing album covers." class="wp-image-389556" srcset="https://i0.wp.com/css-tricks.com/wp-content/uploads/2025/10/s_3EDD0D53C943B8C049C7F94CCE0401810B7E10ADC9A32E2DF8EBA64F2023E651_1759072011555_2025-09-28-3.webp?w=1920&amp;ssl=1 1920w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2025/10/s_3EDD0D53C943B8C049C7F94CCE0401810B7E10ADC9A32E2DF8EBA64F2023E651_1759072011555_2025-09-28-3.webp?resize=300%2C169&amp;ssl=1 300w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2025/10/s_3EDD0D53C943B8C049C7F94CCE0401810B7E10ADC9A32E2DF8EBA64F2023E651_1759072011555_2025-09-28-3.webp?resize=1024%2C576&amp;ssl=1 1024w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2025/10/s_3EDD0D53C943B8C049C7F94CCE0401810B7E10ADC9A32E2DF8EBA64F2023E651_1759072011555_2025-09-28-3.webp?resize=768%2C432&amp;ssl=1 768w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2025/10/s_3EDD0D53C943B8C049C7F94CCE0401810B7E10ADC9A32E2DF8EBA64F2023E651_1759072011555_2025-09-28-3.webp?resize=1536%2C864&amp;ssl=1 1536w" sizes="auto, (min-width: 735px) 864px, 96vw" /><figcaption class="wp-element-caption">Columns with <code>shape-outside</code> applied to them. <a href="https://stuffandnonsense.co.uk/lab/shape-outside.html#example-3" rel="noopener">See this example in my lab.</a></figcaption></figure>



<p>Text now flows around Patty’s tall image without clipping paths or polygons — just the natural silhouette of her image shaping the text.</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/2025/10/s_3EDD0D53C943B8C049C7F94CCE0401810B7E10ADC9A32E2DF8EBA64F2023E651_1759072033605_2025-09-28-4-1024x576.webp?resize=1024%2C576&#038;ssl=1" alt="Silhouette of Patty's image on the left and two slightly rotated squares on the right that the text will flow around." class="wp-image-389558" srcset="https://i0.wp.com/css-tricks.com/wp-content/uploads/2025/10/s_3EDD0D53C943B8C049C7F94CCE0401810B7E10ADC9A32E2DF8EBA64F2023E651_1759072033605_2025-09-28-4.webp?resize=1024%2C576&amp;ssl=1 1024w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2025/10/s_3EDD0D53C943B8C049C7F94CCE0401810B7E10ADC9A32E2DF8EBA64F2023E651_1759072033605_2025-09-28-4.webp?resize=300%2C169&amp;ssl=1 300w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2025/10/s_3EDD0D53C943B8C049C7F94CCE0401810B7E10ADC9A32E2DF8EBA64F2023E651_1759072033605_2025-09-28-4.webp?resize=768%2C432&amp;ssl=1 768w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2025/10/s_3EDD0D53C943B8C049C7F94CCE0401810B7E10ADC9A32E2DF8EBA64F2023E651_1759072033605_2025-09-28-4.webp?resize=1536%2C864&amp;ssl=1 1536w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2025/10/s_3EDD0D53C943B8C049C7F94CCE0401810B7E10ADC9A32E2DF8EBA64F2023E651_1759072033605_2025-09-28-4.webp?w=1920&amp;ssl=1 1920w" sizes="auto, (min-width: 735px) 864px, 96vw" /><figcaption class="wp-element-caption"> Building rotations into images.</figcaption></figure>



<p>When an image is rotated using a CSS <code><a href="https://css-tricks.com/almanac/properties/t/transform/">transform</a></code>, ideally, browsers would reflow text around its rotated alpha channel. Sadly, they don’t, so it’s often necessary to build that rotation into the image.</p>


<h3 class="wp-block-heading" id="-shape-outside-with-a-faux-centred-image"><code>shape-outside</code> with a faux-centred image</h3>


<p>For text to flow around elements, they need to be floated either to the left or right. Placing an image in the centre of the text would make Patty’s biography design more striking. But there’s no <code>center</code> value for floats, so how might this be possible?</p>



<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/2025/10/s_3EDD0D53C943B8C049C7F94CCE0401810B7E10ADC9A32E2DF8EBA64F2023E651_1759072103124_2025-09-28-5.webp?resize=1920%2C1080&#038;ssl=1" alt="" class="wp-image-389561" srcset="https://i0.wp.com/css-tricks.com/wp-content/uploads/2025/10/s_3EDD0D53C943B8C049C7F94CCE0401810B7E10ADC9A32E2DF8EBA64F2023E651_1759072103124_2025-09-28-5.webp?w=1920&amp;ssl=1 1920w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2025/10/s_3EDD0D53C943B8C049C7F94CCE0401810B7E10ADC9A32E2DF8EBA64F2023E651_1759072103124_2025-09-28-5.webp?resize=300%2C169&amp;ssl=1 300w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2025/10/s_3EDD0D53C943B8C049C7F94CCE0401810B7E10ADC9A32E2DF8EBA64F2023E651_1759072103124_2025-09-28-5.webp?resize=1024%2C576&amp;ssl=1 1024w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2025/10/s_3EDD0D53C943B8C049C7F94CCE0401810B7E10ADC9A32E2DF8EBA64F2023E651_1759072103124_2025-09-28-5.webp?resize=768%2C432&amp;ssl=1 768w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2025/10/s_3EDD0D53C943B8C049C7F94CCE0401810B7E10ADC9A32E2DF8EBA64F2023E651_1759072103124_2025-09-28-5.webp?resize=1536%2C864&amp;ssl=1 1536w" sizes="auto, (min-width: 735px) 864px, 96vw" /><figcaption class="wp-element-caption">Patty&#8217;s image set between two text columns. <a href="https://stuffandnonsense.co.uk/lab/shape-outside.html#example-1" rel="noopener">See this example in my lab.</a></figcaption></figure>



<p>Patty’s bio content is split across two symmetrical columns:</p>



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



<p>To create the illusion of text flowing around both sides of her image, I first split it into two parts: one for the left and the other for the right, both of which are half, or 50%, of the original width.</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/2025/10/s_3EDD0D53C943B8C049C7F94CCE0401810B7E10ADC9A32E2DF8EBA64F2023E651_1759072125839_2025-09-28-6-1024x576.webp?resize=1024%2C576&#038;ssl=1" alt="A silhouette of Patty's image with a dotted line dividing the image vertically against a transparent background." class="wp-image-389562" srcset="https://i0.wp.com/css-tricks.com/wp-content/uploads/2025/10/s_3EDD0D53C943B8C049C7F94CCE0401810B7E10ADC9A32E2DF8EBA64F2023E651_1759072125839_2025-09-28-6.webp?resize=1024%2C576&amp;ssl=1 1024w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2025/10/s_3EDD0D53C943B8C049C7F94CCE0401810B7E10ADC9A32E2DF8EBA64F2023E651_1759072125839_2025-09-28-6.webp?resize=300%2C169&amp;ssl=1 300w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2025/10/s_3EDD0D53C943B8C049C7F94CCE0401810B7E10ADC9A32E2DF8EBA64F2023E651_1759072125839_2025-09-28-6.webp?resize=768%2C432&amp;ssl=1 768w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2025/10/s_3EDD0D53C943B8C049C7F94CCE0401810B7E10ADC9A32E2DF8EBA64F2023E651_1759072125839_2025-09-28-6.webp?resize=1536%2C864&amp;ssl=1 1536w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2025/10/s_3EDD0D53C943B8C049C7F94CCE0401810B7E10ADC9A32E2DF8EBA64F2023E651_1759072125839_2025-09-28-6.webp?w=1920&amp;ssl=1 1920w" sizes="auto, (min-width: 735px) 864px, 96vw" /><figcaption class="wp-element-caption">Splitting the image into two pieces.</figcaption></figure>



<p>Then I placed one image in the left column, the other in the right:</p>



<pre rel="HTML" class="wp-block-csstricks-code-block language-markup" data-line=""><code markup="tt">&lt;section id="dolly">
  &lt;div>
    &lt;img src="patty-left.webp" alt="">
    [...]
  &lt;/div>
  
  &lt;div>
    &lt;img src="patty-right.webp" alt="">
    [...]
  &lt;/div>
&lt;/section></code></pre>



<p>To give the illusion that text flows around both sides of a single image, I floated the left column’s half to the right:</p>



<pre rel="CSS" class="wp-block-csstricks-code-block language-css" data-line=""><code markup="tt">#dolly > *:nth-child(1) img {
  float: right;
  width: 40%;
  shape-outside: url("patty-left.webp");
  shape-margin: 2%;
}</code></pre>



<p>&#8230;and the right column’s half to the left, so that both halves of Patty’s image combine right in the middle:</p>



<pre rel="CSS" class="wp-block-csstricks-code-block language-css" data-line=""><code markup="tt">#dolly > *:nth-child(2) img {
  float: left;
  width: 40%;
  shape-outside: url("patty-right.webp");
  shape-margin: 2%;
}</code></pre>



<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/2025/10/s_3EDD0D53C943B8C049C7F94CCE0401810B7E10ADC9A32E2DF8EBA64F2023E651_1759072159431_2025-09-28-7.webp?resize=1920%2C1080&#038;ssl=1" alt="Patty's image centered between two columns of white text flowing around it." class="wp-image-389564" srcset="https://i0.wp.com/css-tricks.com/wp-content/uploads/2025/10/s_3EDD0D53C943B8C049C7F94CCE0401810B7E10ADC9A32E2DF8EBA64F2023E651_1759072159431_2025-09-28-7.webp?w=1920&amp;ssl=1 1920w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2025/10/s_3EDD0D53C943B8C049C7F94CCE0401810B7E10ADC9A32E2DF8EBA64F2023E651_1759072159431_2025-09-28-7.webp?resize=300%2C169&amp;ssl=1 300w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2025/10/s_3EDD0D53C943B8C049C7F94CCE0401810B7E10ADC9A32E2DF8EBA64F2023E651_1759072159431_2025-09-28-7.webp?resize=1024%2C576&amp;ssl=1 1024w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2025/10/s_3EDD0D53C943B8C049C7F94CCE0401810B7E10ADC9A32E2DF8EBA64F2023E651_1759072159431_2025-09-28-7.webp?resize=768%2C432&amp;ssl=1 768w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2025/10/s_3EDD0D53C943B8C049C7F94CCE0401810B7E10ADC9A32E2DF8EBA64F2023E651_1759072159431_2025-09-28-7.webp?resize=1536%2C864&amp;ssl=1 1536w" sizes="auto, (min-width: 735px) 864px, 96vw" /><figcaption class="wp-element-caption">Faux-centred image. <a href="https://stuffandnonsense.co.uk/lab/shape-outside.html#example-2" rel="noopener">See this example in my lab.</a></figcaption></figure>


<h3 class="wp-block-heading" id="faux-background-image">Faux background image</h3>


<p>So far, my designs for Patty’s biography have included a cut-out portrait with a clearly defined alpha channel. But, I often need to make a design that feels looser and more natural.</p>



<figure class="wp-block-image size-full"><img data-recalc-dims="1" loading="lazy" decoding="async" width="1860" height="1046" src="https://i0.wp.com/css-tricks.com/wp-content/uploads/2025/10/s_3EDD0D53C943B8C049C7F94CCE0401810B7E10ADC9A32E2DF8EBA64F2023E651_1759072179393_2025-09-28-8.webp?resize=1860%2C1046&#038;ssl=1" alt="Image of Patty sitting on a chair and playing an acoustic guitar. White text on a black background flows around it on the right." class="wp-image-389567" srcset="https://i0.wp.com/css-tricks.com/wp-content/uploads/2025/10/s_3EDD0D53C943B8C049C7F94CCE0401810B7E10ADC9A32E2DF8EBA64F2023E651_1759072179393_2025-09-28-8.webp?w=1860&amp;ssl=1 1860w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2025/10/s_3EDD0D53C943B8C049C7F94CCE0401810B7E10ADC9A32E2DF8EBA64F2023E651_1759072179393_2025-09-28-8.webp?resize=300%2C169&amp;ssl=1 300w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2025/10/s_3EDD0D53C943B8C049C7F94CCE0401810B7E10ADC9A32E2DF8EBA64F2023E651_1759072179393_2025-09-28-8.webp?resize=1024%2C576&amp;ssl=1 1024w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2025/10/s_3EDD0D53C943B8C049C7F94CCE0401810B7E10ADC9A32E2DF8EBA64F2023E651_1759072179393_2025-09-28-8.webp?resize=768%2C432&amp;ssl=1 768w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2025/10/s_3EDD0D53C943B8C049C7F94CCE0401810B7E10ADC9A32E2DF8EBA64F2023E651_1759072179393_2025-09-28-8.webp?resize=1536%2C864&amp;ssl=1 1536w" sizes="auto, (min-width: 735px) 864px, 96vw" /><figcaption class="wp-element-caption">Faux background image. <a href="https://stuffandnonsense.co.uk/lab/shape-outside.html#example-4" rel="noopener">See this example in my lab.</a></figcaption></figure>



<p>Ordinarily, I would place a picture as a <code>background-image</code>, but for this design, I wanted the content to flow loosely around Patty and her guitar.</p>



<figure class="wp-block-image size-full"><img data-recalc-dims="1" loading="lazy" decoding="async" width="1860" height="1046" src="https://i0.wp.com/css-tricks.com/wp-content/uploads/2025/10/s_3EDD0D53C943B8C049C7F94CCE0401810B7E10ADC9A32E2DF8EBA64F2023E651_1759072199244_2025-09-28-9.webp?resize=1860%2C1046&#038;ssl=1" alt="A large photo of Patty sitting on a chair playing an acoustic guitar. She is positioned slightly to the left of the frame." class="wp-image-389571" srcset="https://i0.wp.com/css-tricks.com/wp-content/uploads/2025/10/s_3EDD0D53C943B8C049C7F94CCE0401810B7E10ADC9A32E2DF8EBA64F2023E651_1759072199244_2025-09-28-9.webp?w=1860&amp;ssl=1 1860w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2025/10/s_3EDD0D53C943B8C049C7F94CCE0401810B7E10ADC9A32E2DF8EBA64F2023E651_1759072199244_2025-09-28-9.webp?resize=300%2C169&amp;ssl=1 300w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2025/10/s_3EDD0D53C943B8C049C7F94CCE0401810B7E10ADC9A32E2DF8EBA64F2023E651_1759072199244_2025-09-28-9.webp?resize=1024%2C576&amp;ssl=1 1024w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2025/10/s_3EDD0D53C943B8C049C7F94CCE0401810B7E10ADC9A32E2DF8EBA64F2023E651_1759072199244_2025-09-28-9.webp?resize=768%2C432&amp;ssl=1 768w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2025/10/s_3EDD0D53C943B8C049C7F94CCE0401810B7E10ADC9A32E2DF8EBA64F2023E651_1759072199244_2025-09-28-9.webp?resize=1536%2C864&amp;ssl=1 1536w" sizes="auto, (min-width: 735px) 864px, 96vw" /><figcaption class="wp-element-caption">Large featured image</figcaption></figure>



<p>So, I inserted Patty’s picture as an inline image, floated it, and set its width to 100%;</p>



<pre rel="HTML" class="wp-block-csstricks-code-block language-markup" data-line=""><code markup="tt">&lt;section id="kenny">
  &lt;img src="patty.webp" alt="">
  [...]
&lt;/section></code></pre>



<pre rel="CSS" class="wp-block-csstricks-code-block language-css" data-line=""><code markup="tt">#kenny > img {
  float: left;
  width: 100%;
  max-width: 100%;
}</code></pre>



<p>There are two methods I might use to flow text around Patty and her guitar. First, I might edit the image, removing non-essential parts to create a soft-edged alpha channel. Then, I could use the <a href="https://css-tricks.com/almanac/properties/s/shape-image-threshold/"><code>shape-image-threshold</code></a> property to control which parts of the alpha channel form the contours for text wrapping:</p>



<pre rel="CSS" class="wp-block-csstricks-code-block language-css" data-line=""><code markup="tt">#kenny > img {
  shape-outside: url("patty.webp");
  shape-image-threshold: 2;
}</code></pre>



<figure class="wp-block-image size-full"><img data-recalc-dims="1" loading="lazy" decoding="async" width="1860" height="1046" src="https://i0.wp.com/css-tricks.com/wp-content/uploads/2025/10/s_3EDD0D53C943B8C049C7F94CCE0401810B7E10ADC9A32E2DF8EBA64F2023E651_1759072234404_2025-09-28-10.webp?resize=1860%2C1046&#038;ssl=1" alt="The same image of Patty sitting in a chair playing an acoustic guitar. The right side has been removed following her shape, leaving a transparent area around her." class="wp-image-389573" srcset="https://i0.wp.com/css-tricks.com/wp-content/uploads/2025/10/s_3EDD0D53C943B8C049C7F94CCE0401810B7E10ADC9A32E2DF8EBA64F2023E651_1759072234404_2025-09-28-10.webp?w=1860&amp;ssl=1 1860w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2025/10/s_3EDD0D53C943B8C049C7F94CCE0401810B7E10ADC9A32E2DF8EBA64F2023E651_1759072234404_2025-09-28-10.webp?resize=300%2C169&amp;ssl=1 300w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2025/10/s_3EDD0D53C943B8C049C7F94CCE0401810B7E10ADC9A32E2DF8EBA64F2023E651_1759072234404_2025-09-28-10.webp?resize=1024%2C576&amp;ssl=1 1024w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2025/10/s_3EDD0D53C943B8C049C7F94CCE0401810B7E10ADC9A32E2DF8EBA64F2023E651_1759072234404_2025-09-28-10.webp?resize=768%2C432&amp;ssl=1 768w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2025/10/s_3EDD0D53C943B8C049C7F94CCE0401810B7E10ADC9A32E2DF8EBA64F2023E651_1759072234404_2025-09-28-10.webp?resize=1536%2C864&amp;ssl=1 1536w" sizes="auto, (min-width: 735px) 864px, 96vw" /><figcaption class="wp-element-caption">Edited image with a soft-edged alpha channel</figcaption></figure>



<p>However, this method is destructive, since much of the texture behind Patty is removed. Instead, I created a polygon <code><a href="https://css-tricks.com/almanac/properties/c/clip-path/">clip-path</a></code> and applied that as the <code>shape-outside</code>, around which text flows while preserving all the detail of my original image:</p>



<pre rel="CSS" class="wp-block-csstricks-code-block language-css" data-line=""><code markup="tt">#kenny > img {
  float: left;
  width: 100%;
  max-width: 100%;
  shape-outside: polygon(&#8230;);
  shape-margin: 20px;
}</code></pre>



<figure class="wp-block-image size-full"><img data-recalc-dims="1" loading="lazy" decoding="async" width="1860" height="1046" src="https://i0.wp.com/css-tricks.com/wp-content/uploads/2025/10/s_3EDD0D53C943B8C049C7F94CCE0401810B7E10ADC9A32E2DF8EBA64F2023E651_1759072262174_2025-09-28-11.webp?resize=1860%2C1046&#038;ssl=1" alt="A white dotted line shows the image's clipped area." class="wp-image-389575" srcset="https://i0.wp.com/css-tricks.com/wp-content/uploads/2025/10/s_3EDD0D53C943B8C049C7F94CCE0401810B7E10ADC9A32E2DF8EBA64F2023E651_1759072262174_2025-09-28-11.webp?w=1860&amp;ssl=1 1860w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2025/10/s_3EDD0D53C943B8C049C7F94CCE0401810B7E10ADC9A32E2DF8EBA64F2023E651_1759072262174_2025-09-28-11.webp?resize=300%2C169&amp;ssl=1 300w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2025/10/s_3EDD0D53C943B8C049C7F94CCE0401810B7E10ADC9A32E2DF8EBA64F2023E651_1759072262174_2025-09-28-11.webp?resize=1024%2C576&amp;ssl=1 1024w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2025/10/s_3EDD0D53C943B8C049C7F94CCE0401810B7E10ADC9A32E2DF8EBA64F2023E651_1759072262174_2025-09-28-11.webp?resize=768%2C432&amp;ssl=1 768w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2025/10/s_3EDD0D53C943B8C049C7F94CCE0401810B7E10ADC9A32E2DF8EBA64F2023E651_1759072262174_2025-09-28-11.webp?resize=1536%2C864&amp;ssl=1 1536w" sizes="auto, (min-width: 735px) 864px, 96vw" /><figcaption class="wp-element-caption">Original image with a non-destructive <code>clip-path</code>.</figcaption></figure>



<p>I have little time for writing polygon path points by hand, so I rely on Bennett Feely’s <a href="https://bennettfeely.com/clippy/" rel="noopener">CSS clip-path maker</a>. I add my image URL, draw a custom polygon shape, then copy the <code>clip-path</code> values to my <code>shape-outside</code> property.</p>



<figure class="wp-block-image size-full"><img data-recalc-dims="1" loading="lazy" decoding="async" width="1860" height="1046" src="https://i0.wp.com/css-tricks.com/wp-content/uploads/2025/10/s_3EDD0D53C943B8C049C7F94CCE0401810B7E10ADC9A32E2DF8EBA64F2023E651_1759072284088_2025-09-28-12.webp?resize=1860%2C1046&#038;ssl=1" alt="Editing the Patty image in Clippy, Bennett Feely’s CSS clip path maker." class="wp-image-389577" srcset="https://i0.wp.com/css-tricks.com/wp-content/uploads/2025/10/s_3EDD0D53C943B8C049C7F94CCE0401810B7E10ADC9A32E2DF8EBA64F2023E651_1759072284088_2025-09-28-12.webp?w=1860&amp;ssl=1 1860w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2025/10/s_3EDD0D53C943B8C049C7F94CCE0401810B7E10ADC9A32E2DF8EBA64F2023E651_1759072284088_2025-09-28-12.webp?resize=300%2C169&amp;ssl=1 300w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2025/10/s_3EDD0D53C943B8C049C7F94CCE0401810B7E10ADC9A32E2DF8EBA64F2023E651_1759072284088_2025-09-28-12.webp?resize=1024%2C576&amp;ssl=1 1024w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2025/10/s_3EDD0D53C943B8C049C7F94CCE0401810B7E10ADC9A32E2DF8EBA64F2023E651_1759072284088_2025-09-28-12.webp?resize=768%2C432&amp;ssl=1 768w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2025/10/s_3EDD0D53C943B8C049C7F94CCE0401810B7E10ADC9A32E2DF8EBA64F2023E651_1759072284088_2025-09-28-12.webp?resize=1536%2C864&amp;ssl=1 1536w" sizes="auto, (min-width: 735px) 864px, 96vw" /><figcaption class="wp-element-caption">Bennett Feely’s CSS clip path maker.</figcaption></figure>


<h3 class="wp-block-heading" id="text-between-shapes">Text between shapes</h3>


<p>Patty Meltt likes to push the boundaries of country music, and I wanted to do the same with my design of her biography. I planned to flow text between two photomontages, where elements overlap and parts of the images spill out of their containers to create depth.</p>



<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/2025/10/s_3EDD0D53C943B8C049C7F94CCE0401810B7E10ADC9A32E2DF8EBA64F2023E651_1759072311579_2025-09-28-13.webp?resize=1920%2C1080&#038;ssl=1" alt="Two large montages of Patty with a column of white text on a background in between them, following the shapes." class="wp-image-389580" srcset="https://i0.wp.com/css-tricks.com/wp-content/uploads/2025/10/s_3EDD0D53C943B8C049C7F94CCE0401810B7E10ADC9A32E2DF8EBA64F2023E651_1759072311579_2025-09-28-13.webp?w=1920&amp;ssl=1 1920w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2025/10/s_3EDD0D53C943B8C049C7F94CCE0401810B7E10ADC9A32E2DF8EBA64F2023E651_1759072311579_2025-09-28-13.webp?resize=300%2C169&amp;ssl=1 300w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2025/10/s_3EDD0D53C943B8C049C7F94CCE0401810B7E10ADC9A32E2DF8EBA64F2023E651_1759072311579_2025-09-28-13.webp?resize=1024%2C576&amp;ssl=1 1024w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2025/10/s_3EDD0D53C943B8C049C7F94CCE0401810B7E10ADC9A32E2DF8EBA64F2023E651_1759072311579_2025-09-28-13.webp?resize=768%2C432&amp;ssl=1 768w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2025/10/s_3EDD0D53C943B8C049C7F94CCE0401810B7E10ADC9A32E2DF8EBA64F2023E651_1759072311579_2025-09-28-13.webp?resize=1536%2C864&amp;ssl=1 1536w" sizes="auto, (min-width: 735px) 864px, 96vw" /><figcaption class="wp-element-caption">Text between shapes. <a href="https://stuffandnonsense.co.uk/lab/shape-outside.html#example-5" rel="noopener">See this example in my lab.</a></figcaption></figure>



<p>So, I made two montages with irregularly shaped alpha channels.</p>



<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/2025/10/s_3EDD0D53C943B8C049C7F94CCE0401810B7E10ADC9A32E2DF8EBA64F2023E651_1759072331332_2025-09-28-14.webp?resize=1920%2C1080&#038;ssl=1" alt="Showing silhouettes of the irregularly shaped alpha channels against a transparent background." class="wp-image-389584" srcset="https://i0.wp.com/css-tricks.com/wp-content/uploads/2025/10/s_3EDD0D53C943B8C049C7F94CCE0401810B7E10ADC9A32E2DF8EBA64F2023E651_1759072331332_2025-09-28-14.webp?w=1920&amp;ssl=1 1920w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2025/10/s_3EDD0D53C943B8C049C7F94CCE0401810B7E10ADC9A32E2DF8EBA64F2023E651_1759072331332_2025-09-28-14.webp?resize=300%2C169&amp;ssl=1 300w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2025/10/s_3EDD0D53C943B8C049C7F94CCE0401810B7E10ADC9A32E2DF8EBA64F2023E651_1759072331332_2025-09-28-14.webp?resize=1024%2C576&amp;ssl=1 1024w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2025/10/s_3EDD0D53C943B8C049C7F94CCE0401810B7E10ADC9A32E2DF8EBA64F2023E651_1759072331332_2025-09-28-14.webp?resize=768%2C432&amp;ssl=1 768w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2025/10/s_3EDD0D53C943B8C049C7F94CCE0401810B7E10ADC9A32E2DF8EBA64F2023E651_1759072331332_2025-09-28-14.webp?resize=1536%2C864&amp;ssl=1 1536w" sizes="auto, (min-width: 735px) 864px, 96vw" /><figcaption class="wp-element-caption">Irregularly shaped alpha channels</figcaption></figure>



<p>I placed both images above the content:</p>



<pre rel="HTML" class="wp-block-csstricks-code-block language-markup" data-line=""><code markup="tt">&lt;section id="johnny">
  &lt;img src="patty-1.webp" alt="">
  &lt;img src="patty-2.webp" alt="">
  [&#8230;]
&lt;/section></code></pre>



<p>…and used those same image URLs as values for <code>shape-outside</code>:</p>



<pre rel="CSS" class="wp-block-csstricks-code-block language-css" data-line=""><code markup="tt">#johnny img:nth-of-type(1) {
  float: left;
  width: 45%;
  shape-outside: url("patty-1.webp");
  shape-margin: 2%;
}

#johnny img:nth-of-type(2) {
  float: right;
  width: 35%;
  shape-outside: url("img/patty-2.webp");
  shape-margin: 2%;
}</code></pre>



<p>Content now flows like a river in a country song, between the two image montages, filling the design with energy and movement.</p>


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


<p>Too often, images in long-form content end up boxed in and isolated, as if they were dropped into the page as an afterthought. CSS Shapes — and especially <code>shape-outside</code> — give us a chance to treat images and text as part of the same composition.</p>



<p>This matters because design isn’t just about making things usable; it’s about shaping how people feel. Wrapping text around the curve of a guitar or the edge of a portrait slows readers down, invites them to linger, and makes their experience more immersive. It brings rhythm and personality to layouts that might otherwise feel flat.</p>



<p>So, next time you reach for a rectangle, pause and think about how <code>shape-outside</code> can help turn an ordinary page into something memorable.</p>
<hr />
<p><small><a rel="nofollow" href="https://css-tricks.com/getting-creative-with-shape-outside/">Getting Creative With shape-outside</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/getting-creative-with-shape-outside/feed/</wfw:commentRss>
			<slash:comments>3</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">389550</post-id>	</item>
		<item>
		<title>Same Idea, Different Paint Brush</title>
		<link>https://css-tricks.com/same-idea-different-paint-brush/</link>
					<comments>https://css-tricks.com/same-idea-different-paint-brush/#respond</comments>
		
		<dc:creator><![CDATA[Geoff Graham]]></dc:creator>
		<pubDate>Wed, 01 Oct 2025 13:02:47 +0000</pubDate>
				<category><![CDATA[Articles]]></category>
		<category><![CDATA[inspiration]]></category>
		<guid isPermaLink="false">https://css-tricks.com/?p=389238</guid>

					<description><![CDATA[<p>Naturally, everything looks like code when I'm staring at a blank canvas. That's whether the canvas is paper, a screen, some Figma artboard, or what have you.</p>
<hr />
<p><small><a rel="nofollow" href="https://css-tricks.com/same-idea-different-paint-brush/">Same Idea, Different Paint Brush</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>There&#8217;s <a href="https://quoteinvestigator.com/2014/05/08/hammer-nail/" rel="noopener">the idiom</a> that says everything looks like a nail when all you have is a hammer. I also like <a href="https://jnathancorbitt.com/2014/01/18/awareness-and-ignorance/" rel="noopener">the one about worms in horseradish</a> seeing the world as horseradish.</p>



<p>That&#8217;s what it felt like for me as <a href="https://geoffgraham.me/im-working-on-an-album/" rel="noopener">I worked on music</a> for an <a href="https://geoffgraham.me/new-music-tuesday/" rel="noopener">album of covers I released yesterday</a>.</p>



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



<p>I was raised by my mother, a former high school art teacher (and a <a href="https://geoffgraham.me/my-moms-painting/" rel="noopener">gifted artist</a> in her own right), who exposed me to a lot of different tools and materials for painting and drawing. I&#8217;m convinced that&#8217;s what pointed me in the direction of web development, even though we&#8217;re talking years before the internet of AOL and 56K dial-up modems. And just as there&#8217;s art and craft to producing a creative 2D visual on paper with wet paint on a brush, there&#8217;s a level of art and craft to designing user interfaces that are written in code.</p>



<p>You might even say there&#8217;s a <a href="https://css-tricks.com/hearts-in-html-and-css/">poetry to code</a>, just as there&#8217;s code to writing poetry.</p>



<p>I&#8217;ve been painting with code for 20 years. HTML, CSS, JavaScript, and friends are my medium, and I&#8217;ve created a bunch of works since then. I know my mom made a bunch of artistic works in her 25+ years teaching and studying art. In a sense, we&#8217;re both artists using a different brush to produce works in different mediums.</p>



<p>Naturally, everything looks like code when I&#8217;m staring at a blank canvas. That&#8217;s whether the canvas is paper, a screen, some Figma artboard, or what have you. Code is my horseradish and I&#8217;ve been marinating in this horseradish ocean for quite a while.</p>



<p>This is what&#8217;s challenging to me about performing and producing an album of music. The work is done in a different medium. The brush is no longer code (<a href="https://www.youtube.com/watch?v=HkgV_-nJOuE&amp;feature=youtu.be" rel="noopener">though it can be</a>) but sounds, be them vibrations that come from a physical instrument or digital waves that come from a programmed beat or sample.</p>



<p>There are parallels between painting with code and painting with sound, and it is mostly a matter of approach. The concepts, tasks, and challenges are the same, but the brush and canvas are totally different.</p>


<h3 class="wp-block-heading" id="whats-in-your-stack">What&#8217;s in your stack?</h3>


<p>Sound is no different than the web when it comes to choosing the right tools to do the work. Just as you need a stack of technical tools to produce a website or app, you will need technical tools to capture and produce sounds, and the decision affects how that work happens.</p>



<p>For example, <a href="https://geoffgraham.me/colophon/" rel="noopener">my development environment</a> might include an editor app for writing code, a virtual server to see my work locally, GitHub for version control and collaboration, some build process that compiles and deploys my code, and a host that serves the final product to everyone on the web to see.</p>



<p>Making music? I have recording software, microphones, gobs of guitars, and an audio interface that connects them together so that the physical sounds I make are captured and converted to digital sound waves. And, of course, I need a distributor to serve the music to be heard by others just as a host would serve code to be rendered as webpages.</p>



<p>Can your website&#8217;s technical stack be as simple as writing HTML in a plain text editor and manually uploading the file to a hosting service via FTP? Of course! Your album&#8217;s technical stack can just as easily be a boombox with a built in mic and recording. Be as <a href="https://css-tricks.com/am-i-on-the-indieweb-yet/">indie</a> or <a href="https://robinrendle.com/notes/anxious-punk-rock-web-design/" rel="noopener">punk</a> as you want!</p>



<p>Either way, you&#8217;ve gotta establish a working environment to do the work, and that environment requires you to make decisions that affect the way you work, be it code, music, or painting for that matter. <a href="https://css-tricks.com/personalize-it/">Personalize</a> your process and make it <a href="https://css-tricks.com/make-joyful-things/">joyful</a>.</p>



<p>It&#8217;s the &#8220;Recording Experience&#8221; (EX) to what we think of as <a href="https://css-tricks.com/what-is-developer-experience-dx/">Developer Experience (DX)</a>.</p>


<h3 class="wp-block-heading" id="whatre-you-painting-on">What&#8217;re you painting on?</h3>


<p>If you&#8217;re painting, it could be paper. But what kind of paper? Is college-rule cool or do you need something more substantial with heavier card stock? You&#8217;re going to want something that supports the type of paint you&#8217;re using, whether it&#8217;s oil, water, acrylic&#8230; or lead? That wouldn&#8217;t be good.</p>



<p>On the web, you&#8217;re most often painting on a screen that measures its space in pixel units. Screens are different than paper because they&#8217;re not limited by physical constraints. Sure, the hardware may pose a constraint as far as how large a certain screen can be. But the scene itself is limitless where we can scroll to any portion of it that is not in the current frame. But please, avoid <a href="https://scrollmagic.io/examples/advanced/infinite_scrolling.html" rel="noopener">AJAX-based infinite scrolling patterns</a> in your work for everyone&#8217;s sake.</p>



<p>I&#8217;m also painting music on a screen that&#8217;s as infinite as the canvas of a webpage. My recording software simply shows me a timeline and I paint sound on top of time, often layering multiple sounds at the same point in time — <a href="https://kennethwomack.com/books/beatlesbooks/sound-pictures-the-life-of-beatles-producer-george-martin-the-later-years-1966-2016/" rel="noopener">sound pictures</a>, if you will.</p>



<figure class="wp-block-image size-full"><img data-recalc-dims="1" decoding="async" src="https://i0.wp.com/css-tricks.com/wp-content/uploads/2025/09/garageband-timeline-view-1.png" alt="Screenshot of the GarageBand recording app showing seven recorded tracks positioned along a progressive timeline based on beats per measure." class="wp-image-389245"/></figure>



<p>That&#8217;s simply one way to look at it. In some apps, it&#8217;s possible to view the canvas as movements that hold buckets of sound samples.</p>



<figure class="wp-block-image size-full"><img data-recalc-dims="1" loading="lazy" decoding="async" width="2260" height="1386" src="https://i0.wp.com/css-tricks.com/wp-content/uploads/2025/09/ableton-live-loop-view.png?resize=2260%2C1386&#038;ssl=1" alt="Screenshot of the Ableton Live recording app in grid view. There are nine colored columns representing song sections containing sound samples." class="wp-image-389239" srcset="https://i0.wp.com/css-tricks.com/wp-content/uploads/2025/09/ableton-live-loop-view.png?w=2260&amp;ssl=1 2260w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2025/09/ableton-live-loop-view.png?resize=300%2C184&amp;ssl=1 300w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2025/09/ableton-live-loop-view.png?resize=1024%2C628&amp;ssl=1 1024w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2025/09/ableton-live-loop-view.png?resize=768%2C471&amp;ssl=1 768w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2025/09/ableton-live-loop-view.png?resize=1536%2C942&amp;ssl=1 1536w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2025/09/ableton-live-loop-view.png?resize=2048%2C1256&amp;ssl=1 2048w" sizes="auto, (min-width: 735px) 864px, 96vw" /></figure>



<p>Same thing with code. Authoring code is as likely to happen in a code editor you type into as it is to happen with a point-and-click setup in a visual interface that doesn&#8217;t require touching any code at all (<a href="https://www.adobe.com/products/dreamweaver.html" rel="noopener">Dreamweaver,</a> anyone?). Heck, the kids are even <a href="https://en.wikipedia.org/wiki/Vibe_coding" rel="noopener">&#8220;vibe&#8221; coding</a> now without any awareness of how the code actually comes together. Or maybe you&#8217;re super low-fi and like to <a href="https://css-tricks.com/why-and-how-i-write-code-with-pencil-and-paper/">sketch your code</a> before sitting behind a keyboard.</p>


<h3 class="wp-block-heading" id="howre-people-using-it">How&#8217;re people using it?</h3>


<p>Web developers be like all obsessed with how their work looks on whatever device someone is using. I know you know what I&#8217;m talking about because you not only resize browsers to check responsiveness but probably also have tried opening your site (and others!) on a slew of different devices.</p>



<details class="wp-block-details is-layout-flow wp-block-details-is-layout-flow"><summary>&#x26a0;&#xfe0f; Auto-playing media</summary>
<figure class="wp-block-image size-full"><img data-recalc-dims="1" loading="lazy" decoding="async" width="1246" height="339" src="https://i0.wp.com/css-tricks.com/wp-content/uploads/2015/11/rwd-vs-adapt-example.gif?resize=1246%2C339&#038;ssl=1" alt="Animated gif showing a browser viewport being resized and how the layout adjusts to the changes." class="wp-image-234835"/></figure>



<p></p>
</details>



<p>It&#8217;s no different with sound. I&#8217;ve listened to each song I&#8217;ve recorded countless times because the way they sound varies from speaker to speaker. There&#8217;s one song in particular that I nearly scrapped because I struggled to get it sounding good on my AirPods Max headphones that are bass-ier than your typical speaker. I couldn&#8217;t handle the striking difference between that and a different output source that might be more widely used, like car speakers.</p>



<p>Will anyone actually listen to that song on a pair of AirPods Max headphones? Probably not. Then again, I don&#8217;t know if anyone is viewing my sites on some screen built into their fridge or washing machine, but you don&#8217;t see me rushing out to test that. I certainly do try to look at the sites I make on as many devices as possible to make sure nothing is completely busted.</p>



<figure class="wp-block-image size-full"><img data-recalc-dims="1" loading="lazy" decoding="async" width="1200" height="600" src="https://i0.wp.com/css-tricks.com/wp-content/uploads/2020/05/devices-pattern.png?resize=1200%2C600" alt="A collage of various phone devices drawn in black and white line illustrations." class="wp-image-308204" srcset="https://i0.wp.com/css-tricks.com/wp-content/uploads/2020/05/devices-pattern.png?w=1200&amp;ssl=1 1200w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2020/05/devices-pattern.png?resize=300%2C150&amp;ssl=1 300w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2020/05/devices-pattern.png?resize=1024%2C512&amp;ssl=1 1024w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2020/05/devices-pattern.png?resize=768%2C384&amp;ssl=1 768w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2020/05/devices-pattern.png?resize=1000%2C500&amp;ssl=1 1000w" sizes="auto, (min-width: 735px) 864px, 96vw" /></figure>



<p><a href="https://css-tricks.com/test-your-site-with-real-users/">You can&#8217;t control what device someone uses to look at a website.</a> You can&#8217;t control what speakers someone uses to listen to music. There&#8217;s a level of user experience and quality assurance that both fields share. There&#8217;s a whole other layer about accessibility and inclusive design that fits here as well.</p>



<p>There is one <em>big</em> difference: The cringe of listening to your own voice. I never feel personally attached to the websites I make, but listening to my sounds takes a certain level of vulnerability and humility that I have to cope with.</p>


<h3 class="wp-block-heading" id="the-creative-process">The creative process</h3>


<p>I mentioned it earlier, but I think the way music is created shares a lot of overlap with how websites are generally built.</p>



<p>For example, a song rarely (if ever) comes fully formed. Most accounts I read of musicians discussing their creative process talk about the &#8220;magic&#8221; of a melody in which it pretty much falls in the writer&#8217;s lap. It often starts as the germ of an idea and it might take minutes, days, weeks, months, or even years to develop it into a comprehensive piece of work. I keep my phone&#8217;s Voice Memos app at the ready so that I&#8217;m able to quickly &#8220;sketch&#8221; ideas that strike me in the moment. It might simply be something I hum into the phone. It could be strumming a few chords on the guitar that sound really nice together. Whatever it is, I like to think of those recordings as little low-fidelity sketches, not totally unlike sketching website layouts and content blocks with paper and pencil.</p>



<figure class="wp-block-image size-full"><img data-recalc-dims="1" decoding="async" src="https://i0.wp.com/geoffgraham.me/wp-content/uploads/work-livrada-ereader-sketch.jpg?ssl=1" alt="A close up photo of a set of paper wireframes for a website project I worked on years ago." class="wp-image-2145"/><figcaption class="wp-element-caption">I&#8217;m partial to sketching websites on paper and pencil before jumping straight into code.</figcaption></figure>


<h3 class="wp-block-heading" id="its-go-time">It&#8217;s go time!</h3>


<p>And, of course, there&#8217;s what you do when it&#8217;s time to release your work. I&#8217;m waist-deep in this part of the music and I can most definitely say that shipping an album has as many moving parts, if not more, than deploying a website. But they both require a lot of steps and dependencies that complicate the process. It&#8217;s no exaggeration that I&#8217;m more confused and lost about music publishing and distribution than I ever felt learning about publishing and deploying websites.</p>



<p>It&#8217;s perfectly understandable <a href="https://css-tricks.com/the-differences-in-web-hosting-go-with-the-happy-path/">that someone might get lost when hosting a website</a>. There&#8217;s so many ways to go about it, and the &#8220;right&#8221; way is shrouded in the cloak of &#8220;it depends&#8221; based on what you&#8217;re trying to accomplish.</p>



<figure class="wp-block-image size-full"><img data-recalc-dims="1" loading="lazy" decoding="async" width="2774" height="1552" src="https://i0.wp.com/css-tricks.com/wp-content/uploads/2018/08/sftp.png?resize=2774%2C1552&#038;ssl=1" alt="Screenshot of an  FTP application with local files on the left and server connection settings on the right." class="wp-image-275350" srcset="https://i0.wp.com/css-tricks.com/wp-content/uploads/2018/08/sftp.png?w=2774&amp;ssl=1 2774w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2018/08/sftp.png?resize=300%2C168&amp;ssl=1 300w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2018/08/sftp.png?resize=1024%2C573&amp;ssl=1 1024w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2018/08/sftp.png?resize=768%2C430&amp;ssl=1 768w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2018/08/sftp.png?resize=1536%2C859&amp;ssl=1 1536w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2018/08/sftp.png?resize=2048%2C1146&amp;ssl=1 2048w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2018/08/sftp.png?resize=1000%2C559&amp;ssl=1 1000w" sizes="auto, (min-width: 735px) 864px, 96vw" /></figure>



<p>Well, same goes for music, apparently. I&#8217;ve signed up for a professional rights organization that establishes me as the owner of the recordings, very similar to how I need to register myself as the owner of a particular web domain. On top of that, I&#8217;ve enlisted the help of a distributor to make the songs available for anyone to hear and it is exactly the same concept as needing a host to distribute your website over the wire.</p>



<p>I just wish I could programmatically push changes to my music catalog. Uploading and configuring the content for an album release reminds me so much of manually uploading hosted files with FTP. Nothing wrong with that, of course, but it&#8217;s certainly an opportunity to improve the <s>developer</s> recording experience.</p>


<h3 class="wp-block-heading" id="so-what">So, what?</h3>


<p>I guess what triggered this post is the realization that I&#8217;ve been in a self-made rut. Not a bad one, mind you, but more like being run by an automated script programmed to run efficiently in one direction. Working on a music project forced me into a new context where my development environment and paint brush of code are way less effective than what I need to get the job done.</p>



<p>It&#8217;s sort of like <a href="https://css-tricks.com/breaking-css-grid-explained/">breaking out of the grid</a>. My layout has been pretty fixed for some time and I&#8217;m drawing new grid tracks that open my imagination up to a whole new way of work that&#8217;s been right in front of me the entire time, but drowned in my horseradish ocean. There&#8217;s so much we can learn from other disciplines, be it music, painting, engineering, architecture, working on cars&#8230; turns out <a href="https://geoffgraham.me/front-end-development-is-like/" rel="noopener">front-end development is like a lot of other things</a>.</p>



<p>So, what&#8217;s your horseradish and what helps you look past it?</p>
<hr />
<p><small><a rel="nofollow" href="https://css-tricks.com/same-idea-different-paint-brush/">Same Idea, Different Paint Brush</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/same-idea-different-paint-brush/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">389238</post-id>	</item>
		<item>
		<title>Touring New CSS Features in Safari 26</title>
		<link>https://css-tricks.com/touring-new-css-features-in-safari-26/</link>
					<comments>https://css-tricks.com/touring-new-css-features-in-safari-26/#comments</comments>
		
		<dc:creator><![CDATA[Juan Diego Rodríguez]]></dc:creator>
		<pubDate>Mon, 29 Sep 2025 14:31:16 +0000</pubDate>
				<category><![CDATA[Articles]]></category>
		<category><![CDATA[browser]]></category>
		<category><![CDATA[news]]></category>
		<category><![CDATA[safari]]></category>
		<guid isPermaLink="false">https://css-tricks.com/?p=389331</guid>

					<description><![CDATA[<p>Safari 26 adds:75 new features, 3 deprecations, and 171 other improvements. Here's all the CSS goodness you'll want to know about.</p>
<hr />
<p><small><a rel="nofollow" href="https://css-tricks.com/touring-new-css-features-in-safari-26/">Touring New CSS Features in Safari 26</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 couple of days ago, the Apple team released Safari 26.0! Is it a big deal? I mean, browsers release new versions all the time, where they sprinkle in a couple or few new features. They are, of course, all useful, but there aren&#8217;t usually a lot of &#8220;big leaps&#8221; between versions. Safari 26 is different, though. It introduces a <em>lot</em>&nbsp;of new stuff. To be precise, it adds: <strong>75</strong> new features, <strong>3</strong> deprecations, and <strong>171</strong> other improvements.</p>



<p>I&#8217;d officially call that a big deal.</p>



<p>The&nbsp;<a href="https://webkit.org/blog/17333/webkit-features-in-safari-26-0/" rel="noopener">WebKit blog post</a>&nbsp;does an amazing job breaking down each of the new (not only CSS) features. But again, there are so many that the new stuff coming to CSS deserves its own spotlight. So, today I want to check (and also try) what I think are the most interesting features coming to Safari.</p>



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



<p class="is-style-explanation">If you are like me and <span style="margin: 0px; padding: 0px;">don</span>&#8216;t have macOS to test Safari, you can&nbsp;<a href="https://joyofcode.xyz/test-your-site-in-every-browser" rel="noopener">use Playwright&nbsp;instead</a>.</p>


<h3 class="wp-block-heading" id="what-s-new-to-safari-">What&#8217;s new (to Safari)?</h3>


<p>Safari 26 introduces several features you may already know from prior Chrome releases. And&#8230; I can&#8217;t blame Safari for seemingly lagging behind because Chrome is shipping new CSS at a scarily fast pace. I appreciate that browsers stagger releases so they can refine things against each other. Remember when Chrome initially shipped&nbsp;<a href="https://css-tricks.com/almanac/properties/p/position-area/"><code>position-area</code></a> as&nbsp;<code>inset-area</code>? We got better naming between the two implementations.</p>



<p>I think what you&#8217;ll find (as I did) that many of these overlapping features are part of the bigger effort towards <a href="https://wpt.fyi/interop-2025" rel="noopener">Interop 2025</a>, something <a href="https://webkit.org/blog/16458/announcing-interop-2025/" rel="noopener">WebKit is committed to</a>. So, let&#8217;s look specifically at what&#8217;s new in Safari 26&#8230; at least that&#8217;s new <em>to</em> Safari.</p>


<h4 class="wp-block-heading" id="anchor-positioning">Anchor positioning</h4>



<baseline-status class="wp-block-css-tricks-baseline-status" featureId="anchor-positioning"></baseline-status>



<p>Anchor positioning is one of my favorite features (I wrote <a href="https://css-tricks.com/css-anchor-positioning-guide/">the guide</a> on it!), so I am so glad it&#8217;s arrived in Safari. We are now one step closer to widely available support which means we&#8217;re that much closer to using anchor positioning in our production work.</p>



<p>With CSS Anchor Positioning, we can attach an absolutely-positioned element (that we may call a &#8220;target&#8221;) to another element (that we may call an &#8220;anchor&#8221;). This makes creating things like tooltips, modals, and pop-ups trivial in CSS, although it can be used for a&nbsp;<a href="https://frontendmasters.com/blog/drawing-a-line-to-connect-elements-with-css-anchor-positioning/" rel="noopener">variety of layouts</a>.</p>



<p>Using anchor positioning, we can attach any two elements, like these, together. <a href="https://css-tricks.com/anchor-positioning-just-dont-care-about-source-order/">It doesn&#8217;t even matter where they are in the markup.</a></p>



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



<p class="is-style-explanation"><strong>Heads up:</strong> Even though the source order does not matter for positioning, it does for accessibility, so it&#8217;s a good idea to establish a relationship between the anchor and target&nbsp;<a href="https://css-tricks.com/css-anchor-positioning-guide/#aa-accessibility">using ARIA attributes</a> for better experiences that rely on assistive tech.</p>



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



<p>We register the&nbsp;<code>.anchor</code>&nbsp;element using the&nbsp;<a href="https://css-tricks.com/almanac/properties/a/anchor-name/"><code>anchor-name</code></a>&nbsp;property, which takes a dashed ident. We then use that ident to attach the&nbsp;<code>.target</code>&nbsp;to the&nbsp;<code>.anchor</code>&nbsp;using the&nbsp;<a href="https://css-tricks.com/almanac/properties/p/position-anchor/"><code>position-anchor</code></a>&nbsp;property.</p>



<pre rel="CSS" class="wp-block-csstricks-code-block language-css" data-line=""><code markup="tt">.anchor {
  anchor-name: --my-anchor; /* the ident */
}

.target {
  position: absolute;
  position-anchor: --my-anchor; /* attached! */
}</code></pre>



<p>This positions the <code>.target</code> at the center of the <code>.anchor</code> — again, no matter the source order! If we want to position it somewhere else, the simplest way is using the&nbsp;<a href="https://css-tricks.com/almanac/properties/p/position-area/"><code>position-area</code></a>&nbsp;property.</p>



<p>With <code>position-area</code>, we can define a region <em>around</em> the <code>.anchor</code> and place the <code>.target</code> in it. Think of it like drawing a grid of squares that are mapped to the <code>.anchor</code>&#8216;s <code>center</code>,&nbsp;<code>top</code>,&nbsp;<code>right</code>,&nbsp;<code>bottom</code>&nbsp;and&nbsp;<code>left</code>.</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/2024/09/position-area-9.png?resize=1024%2C576" alt="Anchor element with target elements spanning around it." class="wp-image-380907" srcset="https://i0.wp.com/css-tricks.com/wp-content/uploads/2024/09/position-area-9.png?resize=1024%2C576&amp;ssl=1 1024w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2024/09/position-area-9.png?resize=300%2C169&amp;ssl=1 300w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2024/09/position-area-9.png?resize=768%2C432&amp;ssl=1 768w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2024/09/position-area-9.png?resize=1536%2C864&amp;ssl=1 1536w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2024/09/position-area-9.png?w=1920&amp;ssl=1 1920w" sizes="auto, (min-width: 735px) 864px, 96vw" /></figure>



<p> For example, if we wish to place the target at the anchor&#8217;s top-right corner, we can write&#8230;</p>



<pre rel="CSS" class="wp-block-csstricks-code-block language-css" data-line=""><code markup="tt">.target {
  /* ... */
  position-area: top right;
}</code></pre>



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



<p>This is just a taste since anchor positioning is a world unto itself. I&#8217;d encourage you to read our <a href="https://css-tricks.com/css-anchor-positioning-guide/">full guide on it</a>.</p>


<h4 class="wp-block-heading" id="scroll-driven-animations">Scroll-driven animations</h4>



<baseline-status class="wp-block-css-tricks-baseline-status" featureId="scroll-driven-animations"></baseline-status>



<p><a href="https://css-tricks.com/unleash-the-power-of-scroll-driven-animations/">Scroll-driven animations</a> link CSS animations (created from&nbsp;<a href="https://css-tricks.com/almanac/rules/k/keyframes/"><code>@keyframes</code></a>) to an element&#8217;s scroll position. So instead of running an animation for a given time, the animation will depend on where the user scrolls.</p>



<p>We can link an animation to two types of scroll-driven events:</p>



<ul class="wp-block-list">
<li>Linking the animation to a scrollable container using the&nbsp;<code><strong><a href="https://css-tricks.com/almanac/functions/s/scroll/">scroll()</a></strong></code>&nbsp;function.</li>



<li>Linking the animation to an element&#8217;s position on the viewport using the&nbsp;<code><strong><a href="https://css-tricks.com/almanac/functions/v/view/">view()</a></strong></code>&nbsp;function.</li>
</ul>



<p>Both of these functions are used inside the&nbsp;<a href="https://css-tricks.com/almanac/properties/a/animation-timeline/"><code>animation-timeline</code></a>, which links the animation progress to the type of timeline we&#8217;re using, be it scroll or view. What&#8217;s the difference?</p>



<p>With&nbsp;<code>scroll()</code>, the animation runs as the user scrolls the page. The simplest example is one of those reading bars that you might see grow as you read down the page. First, we define our everyday animation and add it to the bar element:</p>



<pre rel="CSS" class="wp-block-csstricks-code-block language-css" data-line=""><code markup="tt">@keyframes grow {
  from {
    transform: scaleX(0);
  }
  to {
    transform: scaleX(1);
  }
}

.progress {
  transform-origin: left center;
  animation: grow linear;
}</code></pre>



<p class="is-style-explanation"><strong>Note:</strong> I am setting&nbsp;<code>transform-origin</code>&nbsp;to&nbsp;<code>left</code>&nbsp;so it the animation progresses from the left instead of expanding from the center.</p>



<p>Then, instead of giving the animation a duration, we can plug it into the scroll position like this:</p>



<pre rel="CSS" class="wp-block-csstricks-code-block language-css" data-line=""><code markup="tt">.progress {
  /* ... */
  animation-timeline: scroll();
}</code></pre>



<p>Assuming you&#8217;re using Safari 26 or the latest version of Chrome, the bar grows in width from left to right as you scroll down the viewport.</p>



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



<p>The <code>view()</code> function is similar, but it bases the animation on the element&#8217;s position <strong>when it is in view of the viewport</strong>. That way, an animation can start or stop at specific points on the page. Here&#8217;s an example making images &#8220;pop&#8221; up as they enter view.</p>



<pre rel="CSS" class="wp-block-csstricks-code-block language-css" data-line=""><code markup="tt">@keyframes popup {
  from {
    opacity: 0;
    transform: translateY(100px);
  }
  to {
    opacity: 1;
    transform: translateY(0px);
  }
}

img {
  animation: popup linear;
}</code></pre>



<p>Then, to make the animation progress as the element enters the viewport, we plug the&nbsp;<code>animation-timeline</code>&nbsp;to&nbsp;<code>view()</code>.</p>



<pre rel="CSS" class="wp-block-csstricks-code-block language-css" data-line=""><code markup="tt">img {
    animation: popup linear;
    animation-timeline: view();
}</code></pre>



<p>If we leave like this, though, the animation ends just as the element leaves the screen. The user doesn&#8217;t see the whole thing! What we want is for the animation to end when the user is in the middle of the viewport so the full timeline runs in view.</p>



<p>This is where we can reach for the&nbsp;<code>animation-range</code>&nbsp;property. It lets us set the animation&#8217;s start and end points relative to the viewport. In this specific example, let&#8217;s say I want the animation to start when the element enters the screen (i.e., the <code>0%</code> mark) and finishes a little bit before it reaches the direct center of the viewport (we&#8217;ll say <code>40%</code>):</p>



<pre rel="CSS" class="wp-block-csstricks-code-block language-css" data-line=""><code markup="tt">img {
  animation: popup linear;
  animation-timeline: view();
  animation-range: 0% 40%;
}</code></pre>



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



<p>Once again, scroll-driven animations go way beyond these two basic examples. For a quick intro to all there is to them, I recommend&nbsp;<a href="https://css-tricks.com/unleash-the-power-of-scroll-driven-animations/#aa-core-concepts-view-and-viewtimeline">Geoff&#8217;s notes</a>.</p>



<p>I feel safer using scroll-drive animations in my production work because it&#8217;s more of a <a href="https://piccalil.li/blog/its-about-time-i-tried-to-explain-what-progressive-enhancement-actually-is/" rel="noopener">progressive enhancement</a> that won&#8217;t break an experience even if it is not supported by the browser. Even so, someone may prefer reduced (or no) animation at all, meaning we&#8217;d better progressively enhance it anyway with <code><a href="https://css-tricks.com/almanac/rules/m/media/prefers-reduced-motion/">prefers-reduced-motion</a></code>.</p>


<h4 class="wp-block-heading" id="the-progress-function">The <code>progress()</code> function</h4>


<p>This is another feature we got in Chrome that has made its way to Safari 26. Funny enough, I missed it in Chrome when it released a few months ago, so it makes me twice as happy to see such a handy feature baked into two major browsers.</p>



<p>The&nbsp;<code>progress()</code>&nbsp;function tells you how much a value has progressed in a range between a starting point and an ending point:</p>



<pre rel="HTML" class="wp-block-csstricks-code-block language-markup" data-line=""><code markup="tt">progress(&lt;value>, &lt;start>, &lt;end>)</code></pre>



<p>If the <code>&lt;value&gt;</code> is less than the <code>&lt;start&gt;</code>, the result is <code>0</code>. If the <code>&lt;value&gt;</code> reaches the <code>&lt;end&gt;</code>, the result is <code>1</code>. Anything in between returns a decimal between <code>0</code> and <code>1</code>.</p>



<p>Technically, this is something we can already do in a&nbsp;<code>calc()</code>-ulation:</p>



<pre rel="CSS" class="wp-block-csstricks-code-block language-css" data-line=""><code markup="tt">calc((value - start) / (end - start))</code></pre>



<p><strong>But there&#8217;s a key difference!</strong> With <code>progress()</code>, we can calculate values from mixed data types (like adding <code>px</code> to <code>rem</code>), which isn&#8217;t currently possible with <code>calc()</code>. For example, we can get the progress value formatted in viewport units from a numeric range formatted in pixels:</p>



<pre rel="CSS" class="wp-block-csstricks-code-block language-css" data-line=""><code markup="tt">progress(100vw, 400px, 1000px);</code></pre>



<p>&#8230;and it will return&nbsp;<code>0</code>&nbsp;when the viewport is&nbsp;<code>400px</code>, and as the screen grows to <code>1000px</code>, it progresses to&nbsp;<code>1</code>. This means it can typecast different units into a number, and as a consequence, we can transition properties like&nbsp;<code>opacity</code>&nbsp;(which takes a number or percentage) based on the viewport (which is a distance length).</p>



<p>There&#8217;s another workaround that accomplishes this using&nbsp;<a href="https://dev.to/janeori/css-type-casting-to-numeric-tanatan2-scalars-582j" rel="noopener"><code>tan()</code>&nbsp;and&nbsp;<code>atan2()</code>&nbsp;functions</a>. I have used that approach before to create&nbsp;<a href="https://css-tricks.com/typecasting-and-viewport-transitions-in-css-with-tanatan2/">smooth viewport transitions</a>. But&nbsp;<code>progress()</code> greatly simplifies the work, making it much more maintainable.</p>



<p>Case in point: We can orchestrate multiple animations as the screen size changes. This next demo takes one of the demos I made for the article about <code>tan()</code>&nbsp;and&nbsp;<code>atan2()</code>, but swaps that out with <code>progress()</code>. Works like a charm!</p>



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



<p>That&#8217;s a pretty wild example. Something more practical might be reducing an image&#8217;s opacity as the screen shrinks:</p>



<pre rel="CSS" class="wp-block-csstricks-code-block language-css" data-line=""><code markup="tt">img {
  opacity: clamp(0.25, progress(100vw, 400px, 1000px), 1);
}</code></pre>



<p>Go ahead and resize the demo to update the image&#8217;s opacity, assuming you&#8217;re looking at it in Safari 26 or the latest version of Chrome.</p>



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



<p>I&#8217;ve <code><a href="https://css-tricks.com/almanac/functions/c/clamp/">clamp()</a></code>-ed the&nbsp;<code>progress()</code>&nbsp;between&nbsp;<code>0.25</code>&nbsp;and&nbsp;<code>1</code>. But, by default, <code>progress()</code> already clamps the <code>&lt;value&gt;</code> between <code>0</code> and <code>1</code>. According to the WebKit release notes, the current implementation <em>isn&#8217;t</em> clamped by default, but upon testing, it does seem to be. So, if you&#8217;re wondering why I&#8217;m clamping something that&#8217;s supposedly clamped already, that&#8217;s why. </p>



<p>An&nbsp;<a href="https://github.com/w3c/csswg-drafts/issues/11825" rel="noopener">unclamped version may come in the future</a>, though.</p>


<h4 class="wp-block-heading" id="self-alignment-properties-align-self-and-justify-self-in-absolute-positioning">Self-alignment in absolute positioning</h4>


<p>And, hey, check this out! We can&nbsp;<code>align-self</code>&nbsp;and&nbsp;<code>justify-self</code>&nbsp;content inside absolutely-positioned elements. This isn&#8217;t as big a deal as the other features we&#8217;ve looked at, but it does have a handy use case.</p>



<p>For example, I sometimes want to place an absolutely-positioned element directly in the center of the viewport, but <code><a href="https://css-tricks.com/almanac/properties/i/inset/">inset</a></code>-related properties (i.e., <code>top</code>,&nbsp;<code>right</code>,&nbsp;<code>bottom</code>,&nbsp;<code>left</code>, etc.) are relative to the element&#8217;s top-left corner. That means we don&#8217;t get perfectly centered with something like this as we&#8217;d expect:</p>



<pre rel="CSS" class="wp-block-csstricks-code-block language-css" data-line=""><code markup="tt">.absolutely-positioned {
  position: absolute;
  top: 50%;
  left: 50%;
}</code></pre>



<p>From here, we could translate the element by half to get things perfectly centered. But now we have the <code>center</code> keyword supported by <code>align-self</code>&nbsp;and&nbsp;<code>justify-self</code>, meaning fewer moving pieces in the code:</p>



<pre rel="CSS" class="wp-block-csstricks-code-block language-css" data-line=""><code markup="tt">.absolutely-positioned {
  position: absolute;
  justify-self: center;
}</code></pre>



<p>Weirdly enough, I noticed that&nbsp;<code>align-self: center</code>&nbsp;doesn&#8217;t seem to center the element relative to the viewport, but instead relative to itself. I found out that can use the <code>anchor-center</code> value to center the element relative to its default anchor, which is the viewport in this specific example:</p>



<pre rel="CSS" class="wp-block-csstricks-code-block language-css" data-line=""><code markup="tt">.absolutely-positioned {
  position: absolute;
  align-self: anchor-center;
  justify-self: center;
}</code></pre>



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



<p>And, of course, <code>place-self</code> is a shorthand for the <code>align-self</code> and <code>justify-self</code> properties, so we could combine those for brevity:</p>



<pre rel="CSS" class="wp-block-csstricks-code-block language-css" data-line=""><code markup="tt">.absolutely-positioned {
  position: absolute;
  place-self: anchor-center center;
}</code></pre>


<h3 class="wp-block-heading" id="what-s-new-for-the-web-">What&#8217;s new (for the web)?</h3>


<p>Safari 26 isn&#8217;t just about keeping up with Chrome. There&#8217;s a lot of exciting <em>new</em> stuff in here that we&#8217;re getting our hands on for the first time, or that is refined from other browser implementations. Let&#8217;s look at those features.</p>


<h4 class="wp-block-heading" id="the-constrast-color-function">The <code>constrast-color()</code> function</h4>



<baseline-status class="wp-block-css-tricks-baseline-status" featureId="contrast-color"></baseline-status>



<p>The&nbsp;<code>constrast-color()</code>&nbsp;isn&#8217;t new by any means. It&#8217;s actually been in Safari Technology Preview since 2021 where it was <a href="https://css-tricks.com/exploring-color-contrast-for-the-first-time/">originally called <code>color-contrast()</code></a>. In Safari 26, we get the updated naming as well as some polish.</p>



<p>Given a certain color value,&nbsp;<code>contrast-color()</code>&nbsp;returns either <code>white</code> or <code>black</code>, whichever produces a sharper contrast with that color. So, if we were to provide <code>coral</code> as the color value for a background, we can let the browser decide whether the text color is more contrasted with the background as either <code>white</code> or <code>black</code>:</p>



<pre rel="CSS" class="wp-block-csstricks-code-block language-css" data-line="4"><code markup="tt">h1 {
  --bg-color: coral;
  background-color: var(--bg-color);
  color: contrast-color(var(--bg-color));
}</code></pre>



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



<p>Our own Daniel Schwarz recently <a href="https://css-tricks.com/exploring-the-css-contrast-color-function-a-second-time/">explored the <code>contrast-color()</code> function</a> and found it&#8217;s actually not that great at determining the best contrast between colors:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p>Undoubtedly, the number one shortcoming is that&nbsp;<code>contrast-color()</code>&nbsp;only resolves to either black or white. If you don’t want black or white, well… that sucks.</p>
</blockquote>



<p>It sucks because there are cases where neither <code>white</code> nor <code>black</code> produces enough contrast with the provided color to meet <a href="https://www.w3.org/WAI/WCAG21/Understanding/contrast-minimum.html" rel="noopener">WCAG color contrast guidelines</a>. There is an intent to extend&nbsp;<code>contrast-color()</code>&nbsp;so it can return additional color values, but there still would be concerns about how exactly&nbsp;<code>contrast-color()</code>&nbsp;arrives at the &#8220;best&#8221; color, since we would still need to take into consideration the font&#8217;s width, size, and even family. <a href="https://css-tricks.com/color-contrast-accessibility-tools/">Always check the actual contrast!</a></p>



<p>So, while it&#8217;s great to finally have <code>constrat-color()</code>, I do hope we see improvements added in the future.</p>


<h4 class="wp-block-heading" id="pretty-text">Pretty text wrapping</h4>



<baseline-status class="wp-block-css-tricks-baseline-status" featureId="text-wrap-pretty"></baseline-status>



<p>Safari 26 also introduces&nbsp;<code>text-wrap: pretty</code>, which is <em>pretty</em> (get it?) straightforward: it makes paragraphs wrap in a prettier way.</p>



<p>You may remember that <a href="https://developer.chrome.com/blog/css-text-wrap-pretty/" rel="noopener">Chrome shipped&nbsp;this back in 2023</a>. But take notice that there is a&nbsp;<em>pretty</em>&nbsp;(OK, that&#8217;s the last time) <a href="https://css-tricks.com/pretty-is-in-the-eye-of-the-beholder/">big difference between the implementations</a>. Chrome only avoids typographic orphans (short last lines). Safari does more to prettify the way text wraps:</p>



<ul class="wp-block-list">
<li><strong>Prevents short lines.</strong> Avoids single words at the end of the paragraph.</li>



<li><strong>Improves rag.</strong> Keeps each line relatively the same length.</li>



<li><strong>Reduces hyphenation.</strong> When enabled, hyphenation improves rag but also breaks words apart. In general, hyphenation should be kept to a minimum.</li>
</ul>



<p><a href="https://webkit.org/blog/16547/better-typography-with-text-wrap-pretty/" rel="noopener">The WebKit blog gets into much greater detail</a> if you&#8217;re curious about what considerations they put into it.</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/2025/09/text-wrap-pretty_tdin3d-1024x576.png?resize=1024%2C576&#038;ssl=1" alt="Comparing the same paragraph of text wrapping in Safari and Chrome using text-wrap: pretty. They produce different results." class="wp-image-389476" srcset="https://i0.wp.com/css-tricks.com/wp-content/uploads/2025/09/text-wrap-pretty_tdin3d.png?resize=1024%2C576&amp;ssl=1 1024w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2025/09/text-wrap-pretty_tdin3d.png?resize=300%2C169&amp;ssl=1 300w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2025/09/text-wrap-pretty_tdin3d.png?resize=768%2C432&amp;ssl=1 768w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2025/09/text-wrap-pretty_tdin3d.png?resize=1536%2C864&amp;ssl=1 1536w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2025/09/text-wrap-pretty_tdin3d.png?w=1920&amp;ssl=1 1920w" sizes="auto, (min-width: 735px) 864px, 96vw" /><figcaption class="wp-element-caption">Safari takes additional actions to ensure &#8220;pretty&#8221; text wrapping, including the overall ragging along the text.</figcaption></figure>


<h3 class="wp-block-heading" id="this-is-just-the-beggining-">This is just the beginning!</h3>


<p>I think these are all the CSS features coming to Safari that you should look out for, but I don&#8217;t want you to think they are the only features in the release. As I mentioned at the top, we&#8217;re talking about 75 new Web Platform features, including <a href="https://webkit.org/blog/17333/webkit-features-in-safari-26-0/#hdr-images" rel="noopener">HDR Images</a>, support for <a href="https://webkit.org/blog/17333/webkit-features-in-safari-26-0/#svg-icons" rel="noopener">SVG favicons</a>, <a href="https://css-tricks.com/css-logical-properties-and-values/">logical property</a> support for <code><a href="https://css-tricks.com/almanac/properties/o/overflow/">overflow</a></code> properties, <a href="https://css-tricks.com/almanac/properties/m/margin-trim/">margin trimming</a>, and much, much more. It&#8217;s worth perusing the <a href="https://webkit.org/blog/17333/webkit-features-in-safari-26-0/" rel="noopener">full release notes</a>.</p>
<hr />
<p><small><a rel="nofollow" href="https://css-tricks.com/touring-new-css-features-in-safari-26/">Touring New CSS Features in Safari 26</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/touring-new-css-features-in-safari-26/feed/</wfw:commentRss>
			<slash:comments>5</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">389331</post-id>	</item>
		<item>
		<title>Recreating Gmail’s Google Gemini Animation</title>
		<link>https://css-tricks.com/recreating-gmails-google-gemini-animation/</link>
					<comments>https://css-tricks.com/recreating-gmails-google-gemini-animation/#comments</comments>
		
		<dc:creator><![CDATA[John Rhea]]></dc:creator>
		<pubDate>Fri, 26 Sep 2025 14:45:13 +0000</pubDate>
				<category><![CDATA[Articles]]></category>
		<category><![CDATA[animation]]></category>
		<guid isPermaLink="false">https://css-tricks.com/?p=389344</guid>

					<description><![CDATA[<p>John Rhea challenged himself to recreate the fancy button using the new CSS <code>shape()</code> function sprinkled with animation to get things pretty close.</p>
<hr />
<p><small><a rel="nofollow" href="https://css-tricks.com/recreating-gmails-google-gemini-animation/">Recreating Gmail’s Google Gemini Animation</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 always see this Google Gemini button up in the corner in Gmail. When you hover over it, it does this cool animation where the little four-pointed star spins and the outer shape morphs between a couple different shapes that are also spinning.</p>



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



<figure class="wp-block-image size-full is-resized ticss-fad86f9f"><img data-recalc-dims="1" loading="lazy" decoding="async" width="92" height="92" src="https://i0.wp.com/css-tricks.com/wp-content/uploads/2025/09/s_772DD1240E939BB7207B787AFAEF6FD6A538BB31ECB901932A44C14EE29E1539_1758040071291_geminianimation.gif?resize=92%2C92&#038;ssl=1" alt="Animated gif of the Gemini button morphing between shapes in blue and purple," class="wp-image-389350" style="object-fit:cover;width:150px;height:150px"/></figure>



<p>I challenged myself to recreate the button using the new CSS <a href="https://css-tricks.com/almanac/functions/s/shape/"><code>shape()</code></a> function sprinkled with animation to get things pretty close. Let me walk you through it.</p>


<h3 class="wp-block-heading" id="drawing-the-shapes">Drawing the Shapes</h3>


<p>Breaking it down, we need five shapes in total:</p>



<ol class="wp-block-list">
<li>Four-pointed star</li>



<li>Flower-ish thing (yes, that’s the technical term)</li>



<li>Cylinder-ish thing (also the correct technical term)</li>



<li>Rounded hexagon</li>



<li>Circle</li>
</ol>



<p>I drew these shapes in a graphics editing program (I like <a href="https://affinity.serif.com/en-us/designer/" rel="noopener">Affinity Designer</a>, but any app that lets you draw vector shapes should work), outputted them in SVG, and then used a tool, like <a href="https://css-generators.com/svg-to-css/" target="_blank" rel="noreferrer noopener">Temani Afif’s generator</a>, to translate the SVG paths the program generated to the CSS <code>shape()</code> syntax.</p>



<p>Now, before I exported the shapes from Affinity Designer, I made sure the flower, hexagon, circle, and cylinder all had the same number of anchor points. If they don’t have the same number, then the shapes will jump from one to the next and won’t do any morphing. So, let’s use a consistent number of anchor points in each shape — even the circle — and we can watch these shapes morph into each other.</p>



<figure class="wp-block-image size-full is-resized"><img loading="lazy" decoding="async" width="100" height="100" src="https://css-tricks.com/wp-content/uploads/2025/09/s_772DD1240E939BB7207B787AFAEF6FD6A538BB31ECB901932A44C14EE29E1539_1758152803569_shapespoints.svg" alt="A two by two grid of shapes. Top row, circle and flower. Bottom row cylinder and hexagon." class="wp-image-389348" style="width:750px;height:auto"/></figure>



<p>I set twelve anchor points on each shape because that was the highest amount used (the hexagon had two points near each curved corner).</p>



<p>Something related (and possibly hard to solve, depending on your graphics program) is that some of my shapes were wildly contorted when animating between shapes. For example, many shapes became smaller and began spinning before morphing into the next shape, while others were much more seamless. I eventually figured out that the interpolation was matching each shape’s starting point and continued matching points as it followed the shape.</p>



<p>The result is that the matched points move between shapes, so if the starting point for one shape is on opposite side of the starting point of the second shape, a lot of movement is necessary to transition from one shape’s starting point to the next shape’s starting point.</p>



<figure class="wp-block-image size-full"><img data-recalc-dims="1" loading="lazy" decoding="async" width="1000" height="1000" src="https://i0.wp.com/css-tricks.com/wp-content/uploads/2025/09/pointmatching.jpg?resize=1000%2C1000&#038;ssl=1" alt="A circle shape and a flower shape sown next to each other with 12 points along each share. A third shape is shown overlapping the two shapes one on top of the other." class="wp-image-389368" srcset="https://i0.wp.com/css-tricks.com/wp-content/uploads/2025/09/pointmatching.jpg?w=1000&amp;ssl=1 1000w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2025/09/pointmatching.jpg?resize=300%2C300&amp;ssl=1 300w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2025/09/pointmatching.jpg?resize=150%2C150&amp;ssl=1 150w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2025/09/pointmatching.jpg?resize=768%2C768&amp;ssl=1 768w" sizes="auto, (min-width: 735px) 864px, 96vw" /></figure>



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



<p>Luckily, the circle was the only shape that gave me trouble, so I was able to spin it (with some trial and error) until its starting point more closely matched the other starting points.</p>



<p>Another issue I ran into was that the cylinder-ish shape had two individual straight lines in <code>shape()</code> with <code>line</code> commands rather than using the <code>curve</code> command. This prevented the animation from morphing into the next shape. It immediately snapped to the next image without animating the transition, skipping ahead to the next shape (both when going into the cylinder and coming out of it).</p>



<p>I went back into Affinity Designer and ever-so-slightly added curvature to the two lines, and then it morphed perfectly. I initially thought this was a <code>shape()</code> quirk, but the same thing happened when I attempted the animation with the <a href="https://css-tricks.com/almanac/functions/p/path/"><code>path()</code></a> function, suggesting it’s more an interpolation limitation than it is a <code>shape()</code> limitation.</p>



<p>Once I finished adding my <code>shape()</code> values, I defined a CSS variable for each shape. This makes the later uses of each <code>shape()</code> more readable, not to mention easier to maintain. With twelve lines per shape the code is stinkin&#8217; long (technical term) so we&#8217;ve put it behind an accordion menu.</p>



<details >
  <summary>
          View Shape Code      </summary>
  

<pre rel="CSS" class="wp-block-csstricks-code-block language-css" data-line=""><code markup="tt">:root {
  --hexagon: shape(
    evenodd from 6.47% 67.001%,
    curve by 0% -34.002% with -1.1735% -7.7% / -1.1735% -26.302%, 
    curve by 7.0415% -12.1965% with 0.7075% -4.641% / 3.3765% -9.2635%, 
    curve by 29.447% -17.001% with 6.0815% -4.8665% / 22.192% -14.1675%, 
    curve by 14.083% 0% with 4.3725% -1.708% / 9.7105% -1.708%, 
    curve by 29.447% 17.001% with 7.255% 2.8335% / 23.3655% 12.1345%, 
    curve by 7.0415% 12.1965% with 3.665% 2.933% / 6.334% 7.5555%, 
    curve by 0% 34.002% with 1.1735% 7.7% / 1.1735% 26.302%, 
    curve by -7.0415% 12.1965% with -0.7075% 4.641% / -3.3765% 9.2635%, 
    curve by -29.447% 17.001% with -6.0815% 4.8665% / -22.192% 14.1675%, 
    curve by -14.083% 0% with -4.3725% 1.708% / -9.7105% 1.708%, 
    curve by -29.447% -17.001% with -7.255% -2.8335% / -23.3655% -12.1345%, 
    curve by -7.0415% -12.1965% with -3.665% -2.933% / -6.334% -7.5555%, 
    close
  );

  --flower: shape(
    evenodd from 17.9665% 82.0335%,
    curve by -12.349% -32.0335% with -13.239% -5.129% / -18.021% -15.402%, 
    curve by -0.0275% -22.203% with -3.1825% -9.331% / -3.074% -16.6605%, 
    curve by 12.3765% -9.8305% with 2.3835% -4.3365% / 6.565% -7.579%, 
    curve by 32.0335% -12.349% with 5.129% -13.239% / 15.402% -18.021%, 
    curve by 20.4535% -0.8665% with 8.3805% -2.858% / 15.1465% -3.062%, 
    curve by 11.58% 13.2155% with 5.225% 2.161% / 9.0355% 6.6475%, 
    curve by 12.349% 32.0335% with 13.239% 5.129% / 18.021% 15.402%, 
    curve by 0.5715% 21.1275% with 2.9805% 8.7395% / 3.0745% 15.723%, 
    curve by -12.9205% 10.906% with -2.26% 4.88% / -6.638% 8.472%, 
    curve by -32.0335% 12.349% with -5.129% 13.239% / -15.402% 18.021%, 
    curve by -21.1215% 0.5745% with -8.736% 2.9795% / -15.718% 3.0745%, 
    curve by -10.912% -12.9235% with -4.883% -2.2595% / -8.477% -6.6385%, 
    close
  );

  --cylinder: shape(
    evenodd from 10.5845% 59.7305%, 
    curve by 0% -19.461% with -0.113% -1.7525% / -0.11% -18.14%, 
    curve by 10.098% -26.213% with 0.837% -10.0375% / 3.821% -19.2625%, 
    curve by 29.3175% -13.0215% with 7.2175% -7.992% / 17.682% -13.0215%, 
    curve by 19.5845% 5.185% with 7.1265% 0% / 13.8135% 1.887%, 
    curve by 9.8595% 7.9775% with 3.7065% 2.1185% / 7.035% 4.8195%, 
    curve by 9.9715% 26.072% with 6.2015% 6.933% / 9.4345% 16.082%, 
    curve by 0% 19.461% with 0.074% 1.384% / 0.0745% 17.7715%, 
    curve by -13.0065% 29.1155% with -0.511% 11.5345% / -5.021% 21.933%, 
    curve by -26.409% 10.119% with -6.991% 6.288% / -16.254% 10.119%, 
    curve by -20.945% -5.9995% with -7.6935% 0% / -14.8755% -2.199%, 
    curve by -8.713% -7.404% with -3.255% -2.0385% / -6.1905% -4.537%, 
    curve by -9.7575% -25.831% with -6.074% -6.9035% / -9.1205% -15.963%, 
    close
  );

  --star: shape(
    evenodd from 50% 24.787%, 
    curve by 7.143% 18.016% with 0% 0% / 2.9725% 13.814%, 
    curve by 17.882% 7.197% with 4.171% 4.2025% / 17.882% 7.197%, 
    curve by -17.882% 8.6765% with 0% 0% / -13.711% 4.474%, 
    curve by -7.143% 16.5365% with -4.1705% 4.202% / -7.143% 16.5365%, 
    curve by -8.6115% -16.5365% with 0% 0% / -4.441% -12.3345%, 
    curve by -16.4135% -8.6765% with -4.171% -4.2025% / -16.4135% -8.6765%, 
    curve by 16.4135% -7.197% with 0% 0% / 12.2425% -2.9945%, 
    curve by 8.6115% -18.016% with 4.1705% -4.202% / 8.6115% -18.016%, 
    close
  );

  --circle: shape(
    evenodd from 13.482% 79.505%, 
    curve by -7.1945% -12.47% with -1.4985% -1.8575% / -6.328% -10.225%, 
    curve by 0.0985% -33.8965% with -4.1645% -10.7945% / -4.1685% -23.0235%, 
    curve by 6.9955% -12.101% with 1.72% -4.3825% / 4.0845% -8.458%, 
    curve by 30.125% -17.119% with 7.339% -9.1825% / 18.4775% -15.5135%, 
    curve by 13.4165% 0.095% with 4.432% -0.6105% / 8.9505% -0.5855%, 
    curve by 29.364% 16.9% with 11.6215% 1.77% / 22.102% 7.9015%, 
    curve by 7.176% 12.4145% with 3.002% 3.7195% / 5.453% 7.968%, 
    curve by -0.0475% 33.8925% with 4.168% 10.756% / 4.2305% 22.942%, 
    curve by -7.1135% 12.2825% with -1.74% 4.4535% / -4.1455% 8.592%, 
    curve by -29.404% 16.9075% with -7.202% 8.954% / -18.019% 15.137%, 
    curve by -14.19% -0.018% with -4.6635% 0.7255% / -9.4575% 0.7205%, 
    curve by -29.226% -16.8875% with -11.573% -1.8065% / -21.9955% -7.9235%, 
    close
  );
}</code></pre>


</details>


<p>If all that looks like gobbledygook to you, it largely does to me too (and I wrote the <a href="https://css-tricks.com/almanac/functions/s/shape/"><code>shape()</code> Almanac entry</a>). As I said above, I converted them from stuff I drew to <code>shape()</code>s with a tool. If you can recognize the shapes from the custom property names, then you&#8217;ll have all you need to know to keep following along.</p>


<h3 class="wp-block-heading" id="breaking-down-the-animation">Breaking Down the Animation</h3>


<p>After staring at the Gmail animation for longer than I would like to admit, I was able to recognize six distinct phases:</p>



<p>First, on hover:</p>



<ol class="wp-block-list">
<li>The four-pointed star spins to the right and changes color.</li>



<li>The fancy blue shape spreads out from underneath the star shape.</li>



<li>The fancy blue shape morphs into another shape while spinning.</li>



<li>The purplish color is wiped across the fancy blue shape.</li>
</ol>



<p>Then, after hover:</p>



<ol start="5" class="wp-block-list">
<li>The fancy blue shape contracts (basically the reverse of Phase 2).</li>



<li>The four-pointed star spins left and returns to its initial color (basically the reverse of Phase 1).</li>
</ol>



<p>That’s the run sheet we’re working with! We’ll write the CSS for all that in a bit, but first I’d like to set up the HTML structure that we’re hooking into.</p>


<h3 class="wp-block-heading" id="the-html">The HTML</h3>


<p>I’ve always wanted to be one of those front-enders who make jaw-dropping art out of CSS, like illustrating the Sistine chapel ceiling with a single <code>div</code> (cue someone commenting with a CodePen doing just that). But, alas, I decided I needed two <code>div</code>s to accomplish this challenge, and I thank you for looking past my shame. To those of you who turned up your nose and stopped reading after that admission: I can safely call you a Flooplegerp and you’ll never know it.</p>



<p>(To those of you still with me, I don’t actually know what a Flooplegerp is. But I’m sure it’s bad.)</p>



<p>Because the animation needs to spread out the blue shape from underneath the star shape, they need to be two separate shapes. And we can’t shrink or clip the main element to do this because that would obscure the star.</p>



<p>So, yeah, that’s why I’m reaching for a second <code>div</code>: to handle the fancy shape and how it needs to move and interact with the star shape.</p>



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


<h3 class="wp-block-heading" id="the-basic-css-styling">The Basic CSS Styling</h3>


<p>Each shape is essentially defined with the same box with the same dimensions and margin spacing.</p>



<pre rel="CSS" class="wp-block-csstricks-code-block language-css" data-line=""><code markup="tt">#geminianimation {
  width: 200px;
  aspect-ratio: 1/1;
  margin: 50px auto;
  position: relative;
}</code></pre>



<p>We can clip the box to a particular shape using a pseudo-element. For example, let’s clip a star shape using the CSS variable (<code>--star</code>) we defined for it and set a background color on it:</p>



<pre rel="CSS" class="wp-block-csstricks-code-block language-css" data-line=""><code markup="tt">#geminianimation {
  width: 200px;
  aspect-ratio: 1;
  margin: 50px auto;
  position: relative;

  &amp;::before {
    content: "";
    clip-path: var(--star);
    width: 100%;
    height: 100%;
    position: absolute;
    background-color: #494949;
  }
}</code></pre>



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



<p>We can hook into the container’s child <code>div</code> and use it to establish the animation’s starting shape, which is the flower (clipped with our <code>--flower</code> variable):</p>



<pre rel="CSS" class="wp-block-csstricks-code-block language-css" data-line=""><code markup="tt">#geminianimation div {
  width: 100%;
  height: 100%;
  clip-path: var(--flower);
  background: linear-gradient(135deg, #217bfe, #078efb, #ac87eb, #217bfe);
}</code></pre>



<p>What we get is a star shape stacked right on top of a flower shape. We’re almost done with our initial CSS, but in order to recreate the animated color wipes, we need a much larger surface that allows us to “change” colors by moving the background gradient’s position. Let’s move the gradient so that it is declared on a pseudo element instead of the child <code>div</code>, and size it up by <code>400%</code> to give us additional breathing room.</p>



<pre rel="CSS" class="wp-block-csstricks-code-block language-css" data-line=""><code markup="tt">#geminianimation div {
  width: 100%;
  height: 100%;
  clip-path: var(--flower);

  &amp;::after {
    content: "";
    background: linear-gradient(135deg, #217bfe, #078efb, #ac87eb, #217bfe);
    width: 400%;
    height: 400%;
    position: absolute;
  }
}</code></pre>



<p>Now we can clearly see how the shapes are positioned relative to each other:</p>



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


<h3 class="wp-block-heading" id="animating-phases-1-and-6">Animating Phases 1 and 6</h3>


<p>Now, I’ll admit, in my own hubris, I’ve turned up my very own schnoz at the humble <a href="https://css-tricks.com/almanac/properties/t/transition/"><code>transition</code></a> property because my thinking is typically, <q>Transitions are great for getting started in animation and for quick things, but <em>real</em> animations are done with CSS keyframes.</q> (Perhaps I, too, am a Flooplegerp.)</p>



<p>But now I see the error of my ways. I can write a set of keyframes that rotate the star 180 degrees, turn its color white(ish), and have it stay that way for as long as the element is hovered. What I can’t do is animate the star back to what it was when the element is un-hovered.</p>



<p>I can, however, do that with the <code>transition</code> property. To do this, we add <code>transition: 1s ease-in-out;</code> on the <code>::before</code>, adding the new background color and rotating things on <code>:hover</code> over the <code>#geminianimation</code> container. This accounts for the first and sixth phases of the animation we outlined earlier.</p>



<pre rel="CSS" class="wp-block-csstricks-code-block language-css" data-line=""><code markup="tt">#geminianimation {
  &amp;::before {
    /* Existing styles */
    transition: 1s ease-in-out;
  }
  &amp;:hover {
    &amp;::before {
      transform: rotate(180deg);
      background-color: #FAFBFE;
    }
  }
}</code></pre>


<h3 class="wp-block-heading" id="animating-phases-2-and-5">Animating Phases 2 and 5</h3>


<p>We can do something similar for the second and fifth phases of the animation since they are mirror reflections of each other. Remember, in these phases, we’re spreading and contracting the fancy blue shape.</p>



<p>We can start by shrinking the inner <code>div</code>’s <code>scale</code> to zero initially, then expand it back to its original size (<code>scale: 1</code>) on <code>:hover</code> (again using transitions):</p>



<pre rel="CSS" class="wp-block-csstricks-code-block language-css" data-line=""><code markup="tt">#geminianimation {
  div {
    scale: 0;
    transition: 1s ease-in-out;
  }
  &amp;:hover {
    div {
      scale: 1;
  }
}</code></pre>



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


<h3 class="wp-block-heading" id="animating-phase-3">Animating Phase 3</h3>


<p>Now, we very well <em>could</em> tackle this with a <code>transition</code> like we did the last two sets, but we probably <em>should not</em> do it… that is, unless you want to weep bitter tears and curse the day you first heard of CSS… not that I know from personal experience or anything&#8230; ha ha&#8230; ha.</p>



<p>CSS keyframes are a better fit here because there are multiple states to animate between that would require defining and orchestrating several different transitions. <a href="https://css-tricks.com/using-multi-step-animations-transitions/">Keyframes are more adept at tackling multi-step animations.</a></p>



<p>What we’re basically doing is animating between different shapes that we’ve already defined as CSS variables that clip the shapes. The browser will handle interpolating between the shapes, so all we need is to tell CSS which shape we want clipped at each phase (or “section”) of this set of keyframes:</p>



<pre rel="CSS" class="wp-block-csstricks-code-block language-css" data-line=""><code markup="tt">@keyframes shapeshift {
  0% { clip-path: var(--circle); }
  25% { clip-path: var(--flower); }
  50% { clip-path: var(--cylinder); }
  75% { clip-path: var(--hexagon); }
  100% { clip-path: var(--circle); }
}</code></pre>



<p>Yes, we could combine the first and last keyframes (<code>0%</code> and <code>100%</code>) into a single step, but we’ll need them separated in a second because we also want to animate the rotation at the same time. We’ll set the initial rotation to <code>0turn</code> and the final rotation <code>1turn</code> so that it can keep spinning smoothly as long as the animation is continuing:</p>



<pre rel="CSS" class="wp-block-csstricks-code-block language-css" data-line=""><code markup="tt">@keyframes shapeshift {
  0% {
    clip-path: var(--circle);
    rotate: 0turn;
  }
  25% {
    clip-path: var(--flower);
  }
  50% {
    clip-path: var(--cylinder);
  }
  75% {
    clip-path: var(--hexagon);
  }
  100% {
    clip-path: var(--circle);
    rotate: 1turn;
  }
}</code></pre>



<p class="is-style-explanation"><strong>Note:</strong> Yes, <code>turn</code> is indeed a <a href="https://css-tricks.com/css-length-units/">CSS unit</a>, albeit one that often goes overlooked.</p>



<p>We want the animation to be smooth as it interpolates between shapes. So, I’m setting the animation’s timing function with <code>ease-in-out</code>. Unfortunately, this will also slow down the rotation as it starts and ends. However, because we’re both beginning and ending with the circle shape, the fact that the rotation slows coming out of 0% and slows again as it heads into 100% is not noticeable — a circle looks like a circle no matter its rotation. If we were ending with a different shape, the easing would be visible and I would use two separate sets of keyframes — one for the shape-shift and one for the rotation — and call them both on the <code>#geminianimation</code> child <code>div</code> .</p>



<pre rel="CSS" class="wp-block-csstricks-code-block language-css" data-line=""><code markup="tt">#geminianimation:hover {
  div {
    animation: shapeshift 5s ease-in-out infinite forwards;
  }
}</code></pre>


<h3 class="wp-block-heading" id="animating-phase-4">Animating Phase 4</h3>


<p>That said, we still do need one more set of keyframes, specifically for changing the shape’s color. Remember how we set a linear gradient on the parent container’s <code>::after</code> pseudo, then we also increased the pseudo’s <code>width</code> and <code>height</code>? Here’s that bit of code again:</p>



<pre rel="CSS" class="wp-block-csstricks-code-block language-css" data-line=""><code markup="tt">#geminianimation div {
  width: 100%;
  height: 100%;
  clip-path: var(--flower);

  &amp;::after {
    content: "";
    background: linear-gradient(135deg, #217bfe, #078efb, #ac87eb, #217bfe);
    width: 400%;
    height: 400%;
    position: absolute;
  }
}</code></pre>



<p>The gradient is that large because we’re only showing part of it at a time. And that means we can <code>translate</code> the gradient’s position to move the gradient at certain keyframes. <code>400%</code> can be nicely divided into quarters, so we can move the gradient by, say, three-quarters of its size. Since its parent, the <code>#geminianimation div</code>, is already spinning, we don’t need any fancy movements to make it feel like the color is coming from different directions. We just <code>translate</code> it linearly and the spin adds some variability to what direction the color wipe comes from.</p>



<pre rel="CSS" class="wp-block-csstricks-code-block language-css" data-line=""><code markup="tt">@keyframes gradientMove {
  0% {
    translate: 0 0;
  }
  100% {
    translate: -75% -75%;
  }
}</code></pre>


<h3 class="wp-block-heading" id="one-final-refinement">One final refinement</h3>


<p>Instead of using the flower as the default shape, let&#8217;s change it to circle. This smooths things out when the hover interaction causes the animation to stop and return to its initial position.</p>



<p>And there you have it:</p>



<div class="wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper"><iframe id="cp_embed_NPGQroO" src="//codepen.io/anon/embed/NPGQroO?height=450&amp;theme-id=1&amp;slug-hash=NPGQroO&amp;default-tab=result" height="450" scrolling="no" frameborder="0" allowfullscreen allowpaymentrequest name="CodePen Embed NPGQroO" title="CodePen Embed NPGQroO" 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>We did it! Is this exactly how Google accomplished the same thing? Probably not. In all honesty, I never inspected the animation code because I wanted to approach it from a clean slate and figure out how I would do it purely in CSS.</p>



<p>That’s the fun thing about a challenge like this: there are different ways to accomplish the same thing (or something similar), and your way of doing it is likely to be different than mine. It’s fun to see a variety of approaches.</p>



<p>Which leads me to ask: How would you have approached the Gemini button animation? What considerations would you take into account that maybe I haven’t?</p>
<hr />
<p><small><a rel="nofollow" href="https://css-tricks.com/recreating-gmails-google-gemini-animation/">Recreating Gmail’s Google Gemini Animation</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/recreating-gmails-google-gemini-animation/feed/</wfw:commentRss>
			<slash:comments>2</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">389344</post-id>	</item>
		<item>
		<title>CSS Typed Arithmetic</title>
		<link>https://css-tricks.com/css-typed-arithmetic/</link>
					<comments>https://css-tricks.com/css-typed-arithmetic/#respond</comments>
		
		<dc:creator><![CDATA[Amit Sheen]]></dc:creator>
		<pubDate>Wed, 24 Sep 2025 12:49:22 +0000</pubDate>
				<category><![CDATA[Articles]]></category>
		<category><![CDATA[animation]]></category>
		<category><![CDATA[CSS functions]]></category>
		<category><![CDATA[math]]></category>
		<guid isPermaLink="false">https://css-tricks.com/?p=389371</guid>

					<description><![CDATA[<p>Starting in Chrome 140, we'll be able to calculate numeric values with mixed data types. Sounds small, but Amit demonstrates how big a deal this is, calling it Computational CSS.</p>
<hr />
<p><small><a rel="nofollow" href="https://css-tricks.com/css-typed-arithmetic/">CSS Typed Arithmetic</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>CSS typed arithmetic is genuinely exciting! It opens the door to new kinds of layout composition and animation logic we could only hack before. The first time I published something that leaned on typed arithmetic was in this animation:</p>



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



<figure class="wp-block-video"><video controls src="https://css-tricks.com/wp-content/uploads/2025/09/swirl.mp4" playsinline></video></figure>



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



<p>But before we dive into what is happening in there, let&#8217;s pause and get clear on what <strong>typed arithmetic</strong> actually is and why it matters for CSS.</p>



<p class="is-style-explanation"><strong>Browser Support:</strong> The CSS feature discussed in this article, typed arithmetic, is on the cutting edge. As of the time of writing, browser support is very limited and experimental. To ensure all readers can understand the concepts, the examples throughout this article are accompanied by videos and images, demonstrating the results for those whose browsers do not yet support this functionality. Please check resources like MDN or Can I Use for the latest support status.</p>


<h3 class="wp-block-heading" id="the-types">The Types</h3>


<p>If you really want to get what a &#8220;type” is in CSS, think about TypeScript. Now forget about TypeScript. This is a CSS article, where semantics actually matter.</p>



<p>In CSS, a <strong>type</strong> describes the unit space a value lives in, and is called a <code>data-type</code>. Every CSS value belongs to a specific type, and each CSS property and function only accepts the data type (or types) it expects.</p>



<ul class="wp-block-list">
<li>Properties like <code>opacity</code> or <code>scale</code> use a plain <code>&lt;number&gt;</code> with no units.</li>



<li><code>width</code>, <code>height</code>, other box metrics, and many additional properties use <code>&lt;length&gt;</code> units like <code>px</code>, <code>rem</code>, <code>cm</code>, etc.</li>



<li>Functions like <code>rotate()</code> or <code>conic-gradient()</code> use an <code>&lt;angle&gt;</code> with <code>deg</code>, <code>rad</code>, or <code>turn</code>.</li>



<li><code>animation</code> and <code>transition</code> use <code>&lt;time&gt;</code> for their duration in seconds (<code>s</code>) or milliseconds (<code>ms</code>).</li>
</ul>



<p class="is-style-explanation"><strong>Note:</strong> You can identify CSS data types in the specs, on MDN, and other official references by their angle brackets: <code>&lt;data-type&gt;</code>.</p>



<p>There are many more data types like <code>&lt;percentage&gt;</code>, <code>&lt;frequency&gt;</code>, and <code>&lt;resolution&gt;</code>, but the types mentioned above cover most of our daily use cases and are all we will need for our discussion today. The mathematical concept remains the same for (almost) all types.</p>



<p>I say “almost” all types for one reason: not every data type is calculable. For instance, types like <code>&lt;color&gt;</code>, <code>&lt;string&gt;</code>, or <code>&lt;image&gt;</code> cannot be used in mathematical operations. An expression like <code>"foo" * red</code> would be meaningless. So, when we discuss mathematics in general, and typed arithmetic in particular, it is crucial to use types that are inherently calculable, like <code>&lt;length&gt;</code>, <code>&lt;angle&gt;</code>, or <code>&lt;number&gt;</code>.</p>


<h3 class="wp-block-heading" id="the-rules-of-typed-arithmetic">The Rules of Typed Arithmetic</h3>


<p>Even when we use calculable data types, there are still limitations and important rules to keep in mind when performing mathematical operations on them.</p>


<h4 class="wp-block-heading" id="addition-and-subtraction">Addition and Subtraction</h4>


<p>Sadly, a mix-and-match approach doesn&#8217;t really work here. Expressions like <code>calc(3em + 45deg)</code> or <code>calc(6s - 3px)</code> will not produce a logical result. When adding or subtracting, you must stick to the same data type.</p>



<p>Of course, you can add and subtract different units within the same type, like <code>calc(4em + 20px)</code> or <code>calc(300deg - 1rad)</code>.</p>


<h4 class="wp-block-heading" id="multiplication">Multiplication</h4>


<p>With multiplication, you can only multiply by a plain <code>&lt;number&gt;</code> type. For example: <code>calc(3px * 7)</code>, <code>calc(10deg * 6)</code>, or <code>calc(40ms * 4)</code>. The result will always adopt the type and unit of the first value, with the new value being the product of the multiplication.</p>



<p>But why can you only multiply by a number? If we tried something like <code>calc(10px * 10px)</code> and assumed it followed “regular” math, we would expect a result of <code>100px²</code>. However, there are no squared pixels in CSS, and certainly no square degrees (though that could be interesting&#8230;). Because such a result is invalid, CSS only permits multiplying typed values by unitless numbers.</p>


<h4 class="wp-block-heading" id="division">Division</h4>


<p>Here, too, mixing and matching incompatible types is not allowed, and you can divide by a number just as you can multiply a number. But what happens when you divide a type by the same type?</p>



<p><strong>Hint:</strong> this is where things get interesting.</p>



<p>Again, if we were thinking in terms of regular math, we would expect the units to cancel each other out, leaving only the calculated value. For example, <code>90x / 6x = 15</code>. In CSS, however, this isn&#8217;t the case. Sorry, it <em>wasn&#8217;t</em> the case.</p>



<p>Previously, an expression like <code>calc(70px / 10px)</code> would have been invalid. But starting with <a href="https://webkit.org/blog/16301/webkit-features-in-safari-18-2/" rel="noopener">Safari 18.2</a> and <a href="https://developer.chrome.com/release-notes/140#css_typed_arithmetic" rel="noopener">Chrome 140</a> (and hopefully soon in all other browsers), <strong>this expression now returns a valid number</strong>, which winds up being <code>7</code> in this case. This is the major change that typed arithmetic enables.</p>


<h3 class="wp-block-heading" id="is-that-all-">Is that all?!</h3>


<p>That little division? Is that the big thing I called &#8220;genuinely exciting&#8221;? Yes! Because this one little feature opens the door to a world of creative possibilities. Case in point: we can convert values from one data type to another and mathematically condition values of one type based on another, just like in the swirl example I demoed at the top.</p>



<p>So, to understand what is happening there, let&#8217;s look at a more simplified swirl:</p>



<figure class="wp-block-image size-full ticss-9d1a627a"><img data-recalc-dims="1" loading="lazy" decoding="async" width="540" height="540" src="https://i0.wp.com/css-tricks.com/wp-content/uploads/2025/09/s_E6C8CBB10A180F0ED5B0539230839B9D2F2BA2F61936EC29516626927C49E146_1758506945721_image.png?resize=540%2C540&#038;ssl=1" alt="A long series of white dots forming a spiral against a stark black background. The dots get closer together as they swirl." class="wp-image-389373" srcset="https://i0.wp.com/css-tricks.com/wp-content/uploads/2025/09/s_E6C8CBB10A180F0ED5B0539230839B9D2F2BA2F61936EC29516626927C49E146_1758506945721_image.png?w=540&amp;ssl=1 540w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2025/09/s_E6C8CBB10A180F0ED5B0539230839B9D2F2BA2F61936EC29516626927C49E146_1758506945721_image.png?resize=300%2C300&amp;ssl=1 300w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2025/09/s_E6C8CBB10A180F0ED5B0539230839B9D2F2BA2F61936EC29516626927C49E146_1758506945721_image.png?resize=150%2C150&amp;ssl=1 150w" sizes="auto, (min-width: 735px) 864px, 96vw" /></figure>



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



<p>I have a container<code>&lt;div&gt;</code> with 36 <code>&lt;i&gt;</code> elements in the markup that are arranged in a spiral with CSS. Each element has an angle relative to the center point, <code>rotate(var(--angle))</code>, and a distance from that center point, <code>translateX(var(--distance))</code>.</p>



<p>The angle calculation is quite direct. I take the index of each <code>&lt;i&gt;</code> element using <a href="https://css-tricks.com/almanac/functions/s/sibling-index/"><code>sibling-index()</code></a> and multiply it by <code>10deg</code>. So, the first element with an index of <code>1</code> will be rotated by 10 degrees (<code>1 * 10deg</code>), the second by 20 degrees (<code>2 * 10deg</code>), the third by 30 degrees (<code>3 * 10deg</code>), and so on.</p>



<pre rel="CSS" class="wp-block-csstricks-code-block language-css" data-line=""><code markup="tt">i { --angle: calc(sibling-index() * 10deg); }</code></pre>



<p>As for the distance, I want it to be directly proportional to the angle. I first use typed arithmetic to divide the angle by 360 degrees: <code>var(--angle) / 360deg</code>.</p>



<p>This returns the angle&#8217;s value, but as a unitless number, which I can then use anywhere. In this case, I can multiply it by a <code>&lt;length&gt;</code> value (e.g. <code>180px</code>) that determines the element&#8217;s distance from the center point.</p>



<pre rel="CSS" class="wp-block-csstricks-code-block language-css" data-line=""><code markup="tt">i {
  --angle: calc(sibling-index() * 10deg);
  --distance: calc(var(--angle) / 360deg * 180px);
}</code></pre>



<p>This way, the ratio between the angle and the distance remains constant. Even if we set the angle of each element differently, or to a new value, the elements will still align on the same spiral.</p>


<h3 class="wp-block-heading" id="the-importance-of-the-divisor-s-unit">The Importance of the Divisor&#8217;s Unit</h3>


<p>It’s important to clarify that when using typed arithmetic this way, you get a unitless number, but its value is <strong>relative to the unit of the divisor</strong>.</p>



<p>In our simplified spiral, we divided the angle by <code>360deg</code>. The resulting unitless number, therefore, represents the value in degrees. If we had divided by <code>1turn</code> instead, the result would be completely different — even though <code>1turn</code> is equivalent to <code>360deg</code>, the resulting unitless number would represent the value in turns.</p>



<p>A clearer example can be seen with <a href="https://css-tricks.com/css-length-units/"><code>&lt;length&gt;</code></a> <a href="https://css-tricks.com/css-length-units/">values</a>.</p>



<p>Let&#8217;s say we are working with a screen width of <code>1080px</code>. If we divide the screen width (<code>100vw</code>) by <code>1px</code>, we get the number of pixels that fit into the screen width, which is, of course, <code>1080</code>.</p>



<pre rel="CSS" class="wp-block-csstricks-code-block language-css" data-line=""><code markup="tt">calc(100vw / 1px) /* 1080 */</code></pre>



<p>However, if we divide that same width by <code>1em</code> (and assume a font size of <code>16px</code>), we get the number of <code>em</code> units that fit across the screen.</p>



<pre rel="CSS" class="wp-block-csstricks-code-block language-css" data-line=""><code markup="tt">calc(100vw / 1em) /* 67.5 */</code></pre>



<p>The resulting number is unitless in both cases, but its meaning is entirely dependent on the unit of the value we divided by.</p>


<h3 class="wp-block-heading" id="from-length-to-angle">From Length to Angle</h3>


<p>Of course, this conversion doesn&#8217;t have to be from a type <code>&lt;angle&gt;</code> to a type <code>&lt;length&gt;</code>. Here is an example that calculates an element&#8217;s angle based on the screen width (<code>100vw</code>), creating a new and unusual kind of responsiveness.</p>



<figure class="wp-block-video"><video controls src="https://css-tricks.com/wp-content/uploads/2025/09/demo-02.mp4" playsinline></video></figure>



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



<p>And get this: <strong>There are no media queries in here!</strong> it&#8217;s all happening in a single line of CSS doing the calculations.</p>



<p>To determine the angle, I first define the width range I want to work within. <code>clamp(300px, 100vw, 700px)</code> gives me a closed range of <code>400px</code>, from <code>300px</code> to <code>700px</code>. I then subtract <code>700px</code> from this range, which gives me a new range, from <code>-400px</code> to <code>0px</code>.</p>



<p>Using typed arithmetic, I then divide this range by <code>400px</code>, which gives me a normalized, unitless number between <code>-1</code> and <code>0</code>. And finally, I convert this number into an <code>&lt;angle&gt;</code> by multiplying it by <code>-90deg</code>.</p>



<p>Here’s what that looks like in CSS when we put it all together:</p>



<pre rel="CSS" class="wp-block-csstricks-code-block language-css" data-line=""><code markup="tt">p {
  rotate: calc(((clamp(300px, 100vw, 700px) - 700px) / 400px) * -90deg);
}</code></pre>


<h3 class="wp-block-heading" id="from-length-to-opacity">From Length to Opacity</h3>


<p>Of course, the resulting unitless number can be used as-is in any property that accepts a <code>&lt;number&gt;</code> data type, such as <code>opacity</code>. What if I want to determine the font&#8217;s opacity based on its size, making smaller fonts more opaque and therefore clearer? Is it possible? Absolutely.</p>



<figure class="wp-block-image size-full ticss-15eb7cf5"><img data-recalc-dims="1" loading="lazy" decoding="async" width="540" height="540" src="https://i0.wp.com/css-tricks.com/wp-content/uploads/2025/09/s_E6C8CBB10A180F0ED5B0539230839B9D2F2BA2F61936EC29516626927C49E146_1758507750089_image.png?resize=540%2C540&#038;ssl=1" alt="Five sentences stacked vertically, each getting larger and more opaque from top to bottom." class="wp-image-389375" srcset="https://i0.wp.com/css-tricks.com/wp-content/uploads/2025/09/s_E6C8CBB10A180F0ED5B0539230839B9D2F2BA2F61936EC29516626927C49E146_1758507750089_image.png?w=540&amp;ssl=1 540w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2025/09/s_E6C8CBB10A180F0ED5B0539230839B9D2F2BA2F61936EC29516626927C49E146_1758507750089_image.png?resize=300%2C300&amp;ssl=1 300w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2025/09/s_E6C8CBB10A180F0ED5B0539230839B9D2F2BA2F61936EC29516626927C49E146_1758507750089_image.png?resize=150%2C150&amp;ssl=1 150w" sizes="auto, (min-width: 735px) 864px, 96vw" /></figure>



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



<p>In this example, I am setting a different <code>font-size</code> value for each <code>&lt;p&gt;</code> element using a <code>--font-size</code> custom property. and since the range of this variable is from <code>0.8rem</code> to <code>2rem</code>, I first subtract <code>0.8rem</code> from it to create a new range of <code>0</code> to <code>1.2rem</code>.</p>



<p>I could divide this range by <code>1.2rem</code> to get a normalized, unitless value between <code>0</code> and <code>1</code>. However, because I don&#8217;t want the text to become fully transparent, I divide it by twice that amount (<code>2.4rem</code>). This gives me a result between <code>0</code> and <code>0.5</code>, which I then subtract from the maximum opacity of <code>1</code>.</p>



<pre rel="CSS" class="wp-block-csstricks-code-block language-css" data-line=""><code markup="tt">p {
  font-size: var(--font-size, 1rem);
  opacity: calc(1 - (var(--font-size, 1rem) - 0.8rem) / 2.4rem);
}</code></pre>



<p>Notice that I am displaying the font size in pixel units even though the size is defined in <code>rem</code> units. I simply use typed arithmetic to divide the font size by <code>1px</code>, which gives me the size in pixels as a unitless value. I then inject this value into the <code>content</code> of the the paragraph’s <code>::after</code> pseudo-element.</p>



<pre rel="CSS" class="wp-block-csstricks-code-block language-css" data-line=""><code markup="tt">p::after {
  counter-reset: px calc(var(--font-size, 1rem) / 1px);
  content: counter(px) 'px';
}</code></pre>


<h3 class="wp-block-heading" id="dynamic-width-colors">Dynamic Width Colors</h3>


<p>Of course, the real beauty of using native CSS math functions, compared to other approaches, is that everything happens dynamically at runtime. Here, for example, is a small demo where I color the element&#8217;s background relative to its rendered width.</p>



<pre rel="CSS" class="wp-block-csstricks-code-block language-css" data-line=""><code markup="tt">p {
  --hue: calc(100cqi / 1px);
  background-color: hsl(var(--hue, 0) 75% 25%);
}</code></pre>



<p>You can drag the bottom-right corner of the element to see how the color changes in real-time.</p>



<figure class="wp-block-video"><video controls src="https://css-tricks.com/wp-content/uploads/2025/09/demo-4.mp4" playsinline></video></figure>



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



<p>Here’s something neat about this demo: because the element&#8217;s default width is 50% of the screen width and the color is directly proportional to that width, it&#8217;s possible that the element will initially appear in completely different colors on different devices with different screens. <strong>Again, this is all happening without any media queries or JavaScript.</strong></p>


<h3 class="wp-block-heading" id="an-extreme-example-chaining-conversions">An Extreme Example: Chaining Conversions</h3>


<p>OK, so we&#8217;ve established that typed arithmetic is cool and opens up new and exciting possibilities. Before we put a bow on this, I wanted to pit this concept against a more <em>extreme</em> example. I tried to imagine what would happen if we took a <code>&lt;length&gt;</code> type, converted it to a <code>&lt;number&gt;</code> type, then to an <code>&lt;angle&gt;</code> type, back to a <code>&lt;number&gt;</code> type, <em>and</em>, from there, back to a <code>&lt;length&gt;</code> type.</p>



<p>Phew!</p>



<p>I couldn&#8217;t find a real-world use case for such a chain, but I did wonder what would happen if we were to animate an element&#8217;s width and use that width to determine the height of something else. All the calculations might not be necessary (maybe?), but I think I found something that looks pretty cool.</p>



<figure class="wp-block-video"><video controls src="https://css-tricks.com/wp-content/uploads/2025/09/demo-5.mp4"></video></figure>



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



<p>In this demo, the animation is on the solid line along the bottom. The vertical position of the ball, i.e. its height, relative to the line, is proportional to the line’s width. So, as the line expands and contracts, so does the path of the bouncing ball.</p>



<p>To create the parabolic arc that the ball moves along, I take the element&#8217;s width (<code>100cqi</code>) and, using typed arithmetic, divide it by <code>300px</code> to get a unitless number between <code>0</code> and <code>1</code>. I multiply that by <code>180deg</code> to get an angle that I use in a <code>sin()</code> function (Juan Diego has a <a href="https://css-tricks.com/the-most-hated-css-feature-cos-and-sin/">great article on this</a>), which returns another unitless number between <code>0</code> and <code>1</code>, but with a parabolic distribution of values.</p>



<p>Finally, I multiply this number by <code>-200px</code>, which outputs the ball&#8217;s vertical position relative to the line.</p>



<pre rel="CSS" class="wp-block-csstricks-code-block language-css" data-line=""><code markup="tt">.ball {
  --translateY: calc(sin(calc(100cqi / 300px) * 180deg) * -200px) ;
  translate: -50% var(--translateY, 0);
}</code></pre>



<p>And again, because the ball&#8217;s position is relative to the line&#8217;s width, the ball&#8217;s position will remain on the same arc, no matter how we define that width.</p>


<h3 class="wp-block-heading" id="wrapping-up-the-dawn-of-computational-css">Wrapping Up: The Dawn of Computational CSS</h3>


<p>The ability to divide one typed value by another to produce a unitless number might seem like no big deal; more like a minor footnote in <a href="https://css-tricks.com/category/history/">the grand history of CSS</a>.</p>



<p>But as we&#8217;ve seen, this single feature is a quiet revolution. It dismantles the long-standing walls between different CSS data types, transforming them from isolated silos into a connected, interoperable system. We&#8217;ve moved beyond simple calculations, and entered the era of true <strong>Computational CSS</strong>.</p>



<p>This isn&#8217;t just about finding new ways to style a button or animate a loading spinner. <strong>It represents a fundamental shift in our mental model.</strong> We are no longer merely <em>declaring</em> static styles, but rather defining dynamic, mathematical <em>relationships</em> between properties. The width of an element can now intrinsically know about its color, an angle can dictate a distance, and a font&#8217;s size can determine its own visibility.</p>



<p>This is CSS becoming self-aware, capable of creating complex behaviors and responsive designs that adapt with a precision and elegance that previously required JavaScript.</p>



<p>So, the next time you find yourself reaching for JavaScript to bridge a gap between two CSS properties, pause for a moment. Ask yourself if there&#8217;s a mathematical relationship you can define instead. You might be surprised at how far you can go with just a few lines of CSS.</p>


<h4 class="wp-block-heading" id="the-future-is-calculable">The Future is Calculable</h4>


<p>The examples in this article are just the first steps into a much larger world. What happens when we start mixing these techniques with scroll-driven animations, view transitions, and other modern CSS features? The potential for creating intricate data visualizations, generative art, and truly fluid user interfaces, all natively in CSS, is immense. We are being handed a new set of creative tools, and the instruction manual is still being written.</p>
<hr />
<p><small><a rel="nofollow" href="https://css-tricks.com/css-typed-arithmetic/">CSS Typed Arithmetic</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-typed-arithmetic/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		<enclosure url="https://css-tricks.com/wp-content/uploads/2025/09/swirl.mp4" length="14785658" type="video/mp4" />
<enclosure url="https://css-tricks.com/wp-content/uploads/2025/09/demo-02.mp4" length="4291914" type="video/mp4" />
<enclosure url="https://css-tricks.com/wp-content/uploads/2025/09/demo-4.mp4" length="4423712" type="video/mp4" />
<enclosure url="https://css-tricks.com/wp-content/uploads/2025/09/demo-5.mp4" length="102789" type="video/mp4" />

		<post-id xmlns="com-wordpress:feed-additions:1">389371</post-id>	</item>
		<item>
		<title>On inclusive personas and inclusive user research</title>
		<link>https://css-tricks.com/on-inclusive-personas-and-inclusive-user-research/</link>
					<comments>https://css-tricks.com/on-inclusive-personas-and-inclusive-user-research/#respond</comments>
		
		<dc:creator><![CDATA[Geoff Graham]]></dc:creator>
		<pubDate>Fri, 19 Sep 2025 13:58:37 +0000</pubDate>
				<category><![CDATA[Notes]]></category>
		<category><![CDATA[accessibility]]></category>
		<category><![CDATA[UI/IX Design]]></category>
		<guid isPermaLink="false">https://css-tricks.com/?p=389157</guid>

					<description><![CDATA[<p>A set of notes taken from Eric Bailey's article about the use of inclusive personas and user research.</p>
<hr />
<p><small><a rel="nofollow" href="https://css-tricks.com/on-inclusive-personas-and-inclusive-user-research/">On inclusive personas and inclusive user research</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 inclined to take a few notes on <a href="https://ericwbailey.website/published/on-inclusive-personas-and-inclusive-user-research/" rel="noopener">Eric Bailey&#8217;s grand post</a> about the use of inclusive personas in user research. As someone who has been in roles that have both used and created user personas, there&#8217;s so much in here </p>



<p>What&#8217;s the big deal, right? We&#8217;re often taught and encouraged to think about users early in the design process. It&#8217;s user&#8217; centric design, so let&#8217;s personify 3-4 of the people we think represent our target audiences so our work is aligned with their objectives and needs. My master&#8217;s program was big on that and went deep into different approaches, strategies, and templates for documenting that research.</p>



<p>And, yes, it is research. The idea, in theory, is that by understanding the motivations and needs of specific users (gosh, isn&#8217;t &#8220;users&#8221; an <a href="https://jnd.org/words-matter-talk-about-people-not-customers-not-consumers-not-users/" rel="noopener">awkward term</a>?), we can &#8220;design backwards&#8221; so that the end goal is aligned to actions that get them there.</p>



<p>Eric sees holes in that process, particularly when it comes to research centered around <em>inclusiveness</em>. Why is that? Very good reasons that I&#8217;m compiling here so I can reference it later. There&#8217;s a lot to take in, so you&#8217;d do yourself a solid by <a href="https://ericwbailey.website/published/on-inclusive-personas-and-inclusive-user-research/" rel="noopener">reading Eric&#8217;s post in full</a>. Your takeaways may be different than mine.</p>



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


<h3 class="wp-block-heading" id="traditional-vs-inclusive-user-research">Traditional vs. Inclusive user research</h3>


<p>First off, I love how Eric distinguishes what we typically refer to as the general type of user personas, like the ones I made to <em>generalize</em> an audience, from inclusive user personas that are based on <em>individual experiences</em>.</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p><a href="https://survicate.com/blog/inclusive-user-research/" rel="noopener">Inclusive user research</a>&nbsp;practices are different than a lot of traditional user research. While there is some high-level overlap in approach, know the majority of inclusive user research is more&nbsp;<strong>focused on the individual experience</strong>&nbsp;and less about more general trends of behavior.</p>
</blockquote>



<p>So, right off the bat we have to reframe what we&#8217;re talking about. There&#8217;s blanket personas that are placeholders for abstracting what we think we know about specific <em>groups</em> of people versus individual people that represent specific <em>experiences</em> that impact usability and access to content.</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p>A primary goal in inclusive user research is often to&nbsp;<strong>identify concrete barriers that prevent someone from accessing the content they want or need</strong>. While the techniques people use are varied, these barriers represent insurmountable obstacles that stymie a whole host of navigation techniques and approaches.</p>



<p>If you’re looking for patterns, trends, and customer insights, know that what you want is&nbsp;<strong>regular user testing</strong>. Here, know that the same motivating factors you’re looking to uncover also exist for disabled people. This is because they’re also, you know, people.</p>
</blockquote>


<h3 class="wp-block-heading" id="assistive-technology-is-not-exclusive-to-disabilities">Assistive technology is not exclusive to disabilities</h3>


<p>It&#8217;s so easy to assume that using assistive tools automatically means accommodating a disability or impairment, but that&#8217;s not always the case. Choice points from Eric:</p>



<ul class="wp-block-list">
<li>First is that&nbsp;<strong>assistive technology is a means, and not an end</strong>.</li>



<li>Some disabled people&nbsp;<a href="https://ashleemboyer.com/blog/disability-is-not-a-single-selection-field" rel="noopener">use more than one form of assistive technology</a>, both concurrently and switching them in and out as needed.</li>



<li>Some disabled people don’t use assistive technology at all.</li>



<li>Not everyone who uses assistive technology has also mastered it.</li>



<li><strong>Disproportionate attention placed on one kind of assistive technology</strong>&nbsp;at the expense of others.</li>



<li><strong>It’s entirely possible to have a solution that is technically compliant, yet unintuitive or near-impossible to use</strong>&nbsp;in the actual.&nbsp;</li>
</ul>



<p>I like to keep in mind that assistive technologies are for <em>everyone</em>. I often think about examples in the physical world where everyone benefits from an accessibility enhancement, such as cutting curbs in sidewalks (great for skateboarders!), taking elevators (you don&#8217;t <em>have</em> to climb stairs in some cases), and using TV subtitles (I often have to keep the volume low for sleeping kids).</p>



<p>That&#8217;s the <em>inclusive</em> part of this. Everyone benefits rather than a specific subset of people.</p>


<h3 class="wp-block-heading" id="different-personas-different-priorities">Different personas, different priorities</h3>


<p>What happens when inclusive research is documented separately from general user research?</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p>Another folly of inclusive personas is that they’re decoupled from regular personas. This means they’re&nbsp;<strong>easily dismissible</strong>&nbsp;as considerations.</p>



<p>[&#8230;]</p>



<p><strong>Disability is diversity</strong>, and the plain and honest truth is that diversity is missing from your personas if disability conditions are not present in at least some of them. This, in turn, means your personas are misrepresentative of the people in the abstract you claim to serve.</p>
</blockquote>



<p>In practice, that means:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p>[&#8230;] we also want to hold space for things that need direct accessibility support and remediation when this consideration of accessibility fails to happen.&nbsp;<strong>It’s all about approach</strong>.</p>



<p>An example of how to consider your approach is when adding drag and drop support to an experience.&nbsp;[&#8230;] [W]e want to&nbsp;<strong>identify if drag and drop is even needed to achieve the outcome the organization needs</strong>.</p>
</blockquote>



<p>Thinking of a slick new feature that will impress your users? Great! Let&#8217;s make sure it doesn&#8217;t step on the toes of other experiences in the process, because that&#8217;s antithetical to inclusiveness. I recognize this temptation in my own work, particularly if I land on a novel UI pattern that excites me. The excitement and tickle I get from a &#8220;clever&#8221; idea gives me a blind side to evaluating the overall effectiveness of it.</p>


<h3 class="wp-block-heading" id="radical-participatory-design">Radical participatory design</h3>


<p>Gosh dang, why didn&#8217;t my schoolwork ever cover this! I had to spend a little time reading the <a href="https://www.cambridge.org/core/journals/design-science/article/an-introduction-to-radical-participatory-design-decolonising-participatory-design-processes/63F70ECC408844D3CD6C1A5AC7D35F4D" rel="noopener">Cambridge University Press article</a> explaining <strong>radical participatopry design</strong> (RPD) that Eric linked up.</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p>Therefore, we introduce the term RPD to differentiate and represent a type of PD that is participatory to the root or core: full inclusion as equal and full members of the research and design team. Unlike other uses of the term PD, RPD is not merely interaction, a method, a way of doing a method, nor a methodology. It is a meta-methodology, or a way of doing a methodology.&nbsp;</p>
</blockquote>



<p>Ah, a method for methodology! We&#8217;re talking about not only including community members into the internal design process, but <strong>make them equal stakeholders as well</strong>. They get the power to make decisions, something the article&#8217;s author describes as a form of decolonization.</p>



<p>Or, as Eric nicely describes it:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p>Existing power structures are flattened and more evenly distributed with this approach.</p>
</blockquote>



<p>Bonus points for surfacing the <strong><a href="https://mindworkscollab.org/model-minority-myth/" rel="noopener">model minority theory</a></strong>:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p>The term&nbsp;<a href="https://www.learningforjustice.org/magazine/what-is-the-model-minority-myth" rel="noopener">“model minority”</a>&nbsp;describes a minority group that society regards as high-performing and successful, especially when compared to other groups. The narrative paints Asian American children as high-achieving prodigies, with fathers who practice medicine, science, or law and fierce mothers who force them to work harder than their classmates and hold them to standards of perfection.</p>
</blockquote>
</blockquote>



<p>It introduces exclusiveness in the quest to pursue inclusiveness — a stereotype within a stereotype.</p>


<h3 class="wp-block-heading" id="thinking-bigger">Thinking bigger</h3>


<p>Eric caps things off with a great compilation of actionable takeaways for avoiding the pitfalls of inclusive user personas:</p>



<ul class="wp-block-list">
<li>Letting go of control leads to better outcomes.</li>



<li>Member checking: letting participants review, comment on, and correct the content you’ve created based on their input.</li>



<li>Take time to scrutinize the functions of our roles and how our organizations compel us to undertake them in order to be successful within them.</li>



<li>Organizations can turn inwards and consider the artifacts their existing design and research processes produce. They can then identify opportunities for participants to provide additional clarity and corrections along the way.</li>
</ul>
<hr />
<p><small><a rel="nofollow" href="https://css-tricks.com/on-inclusive-personas-and-inclusive-user-research/">On inclusive personas and inclusive user research</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/on-inclusive-personas-and-inclusive-user-research/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">389157</post-id>	</item>
		<item>
		<title>Is it Time to Un-Sass?</title>
		<link>https://css-tricks.com/is-it-time-to-un-sass/</link>
					<comments>https://css-tricks.com/is-it-time-to-un-sass/#comments</comments>
		
		<dc:creator><![CDATA[Jeff Bridgforth]]></dc:creator>
		<pubDate>Wed, 17 Sep 2025 14:02:25 +0000</pubDate>
				<category><![CDATA[Articles]]></category>
		<category><![CDATA[css preprocessors]]></category>
		<guid isPermaLink="false">https://css-tricks.com/?p=389101</guid>

					<description><![CDATA[<p>Many of the Sass features we've grown to love have made their way into native CSS in some shape or form. So, should we still use Sass? This is how developer Jeff Bridgforth is thinking about it.</p>
<hr />
<p><small><a rel="nofollow" href="https://css-tricks.com/is-it-time-to-un-sass/">Is it Time to Un-Sass?</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>Several weeks ago, I participated in <a href="https://events.indieweb.org/2025/08/front-end-study-hall-034-UzkXwuTEzyuG" rel="noopener">Front End Study Hall</a>. Front End Study Hall is an HTML and CSS focused meeting held on Zoom every two weeks. It is an opportunity to learn from one another as we share our common interest in these two building blocks of the Web. Some weeks, there is more focused discussion while other weeks are more open ended and members will ask questions or bring up topics of interest.</p>



<p>Joe, the moderator of the group, usually starts the discussion with something he has been thinking about. In this particular meeting, he asked us about <a href="https://sass-lang.com/" rel="noopener">Sass</a>. He asked us if we used it, if we liked&nbsp;it, and then to share our experience with it. I had planned to answer the question but the conversation drifted into another topic before I had the chance to answer. I saw it as an opportunity to write and to share some of the things that I have been thinking about recently.</p>



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


<h3 class="wp-block-heading" id="beginnings">Beginnings</h3>


<p>I started using Sass in March 2012. I had been hearing about it through different things I read. I believe I heard Chris Coyier talk about it on his then-new podcast, <a href="https://shoptalkshow.com/003/" rel="noopener">ShopTalk Show</a>. I had been interested in redesigning my personal website and I thought it would be a great chance to learn Sass. I bought an e-book version of <em><a href="https://www.amazon.com/Pragmatic-Guide-Hampton-Lintorn-Catlin/dp/1934356840" rel="noopener">Pragmatic Guide to Sass</a></em> and then put what I was learning into practice as I built a new version of my website. The book suggested using&nbsp;<a href="https://web.archive.org/web/20120228013912/http://compass-style.org/" rel="noopener">Compass</a>&nbsp;to process my Sass into CSS. I chose to use SCSS syntax instead of indented syntax because SCSS was similar to plain CSS. I thought it was important to stay close to the CSS syntax because I might not always have the chance to use Sass, and I wanted to continue to build my CSS skills.</p>



<p>It was very easy to get up and running with Sass. I used a GUI tool called Scout to run Compass. After some frustration trying to update Ruby on my computer, Scout gave me an environment to get up and going quickly. I didn&#8217;t even have to use the command line. I just pressed &#8220;Play&#8221; to tell my computer to watch my files. Later I learned how to use Compass through the command line. I liked the simplicity of that tool and wish that at least one of today&#8217;s build tools incorporated that same simplicity.</p>



<p>I enjoyed using Sass out of the gate. I liked that I was able to create reusable variables in my code. I could set up colors and typography and have consistency across my code. I had not planned on using nesting much but after I tried it, I was hooked. I really liked that I could write less code and manage all the relationships with nesting. It was great to be able to nest a media query inside a selector and not have to hunt for it in another place in my code.</p>



<p>Fast-forward a bit&#8230;</p>



<p>After my successful first experience using Sass in a personal project, I decided to start using it in my professional work. And I encouraged my teammates to embrace it. One of the things I liked most about Sass was that you could use as little or as much as you liked. I was still writing CSS but now had the superpower that the different helper functions in Sass enabled.</p>



<p>I did not get as deep into Sass as I could have. I used the Sass <a href="https://sass-lang.com/documentation/at-rules/extend/" rel="noopener"><code>@extend</code> rule</a> more in the beginning. There are a lot of features that I did not take advantage of, like <a href="https://sass-lang.com/documentation/style-rules/placeholder-selectors/" rel="noopener">placeholder selectors</a> and <a href="https://sass-lang.com/documentation/at-rules/function/" rel="noopener"><code>for</code> loops</a>. I have never been one to rely much on shortcuts. I use very few of the shortcuts on my Mac. I have dabbled in things like <a href="https://css-tricks.com/emmet/">Emmet</a> but tend to quickly abandon them because I am just use to writing things out and have not developed the muscle memory of using shortcuts.</p>


<h3 class="wp-block-heading" id="is-it-time-to-un-sass-">Is it time to un-Sass?</h3>


<p>By my count, I have been using Sass for over 13 years. I chose Sass over <a href="https://lesscss.org" rel="noopener">Less.js</a> because I thought it was a better direction to go at the time. <a href="https://css-tricks.com/sass-vs-less/">And my bet paid off.</a> That is one of the difficult things about working in the technical space. There are a lot of good tools but some end up rising to the top and others fall away. I have been pretty fortunate that most of the decisions I have made have gone the way that they have. All the agencies I have worked for have used Sass.</p>



<p>At the beginning of this year, I finally jumped into building a prototype for a personal project that I have been thinking about for years: my own memory keeper. One of the few things that I liked about Facebook was the Memories feature. I enjoyed visiting that page each day to remember what I had been doing on that specific day in years past. But I felt at times that Facebook was not giving me all of my memories. And my life doesn&#8217;t just happen on Facebook. I also wanted a way to view memories from other days besides just the current date.</p>



<p>As I started building my prototype, I wanted to keep it simple. I didn&#8217;t want to have to set up any build tools. I decided to write CSS without Sass.</p>



<p>Okay, so that was my intention. But I soon realized that that I was using nesting. I had been working on it a couple of days before I realized it.</p>



<p>But my code was working. That is when I realized that the <a href="https://css-tricks.com/css-nesting-specificity-and-you/">native nesting in CSS</a> works much the same nesting in Sass. I had followed <a href="https://css-tricks.com/help-choose-the-syntax-for-css-nesting/">the discussion about implementing nesting</a> in native CSS. At one point, the syntax was going to be very different. To be honest, I lost track of where things had landed because I was continuing to use Sass. Native CSS nesting was not a big concern to me right then.</p>



<p>I was amazed when I realized that nesting works just the same way. And it was in that moment that I began to wonder:</p>



<p>Is this finally the time to <em>un</em>-Sass?</p>



<p>I want to give credit where credit is due. I&#8217;m borrowing the term &#8220;un-Sass&#8221; from <a href="https://www.alwaystwisted.com" rel="noopener">Stu Robson</a>, who is actually in the middle of writing a series called <a href="https://css-tricks.com/compiling-multiple-css-files-into-one/">&#8220;Un-Sass&#8217;ing my CSS&#8221;</a> as I started thinking about writing this post. I love the term &#8220;un-Sass&#8221; because it is easy to remember and so spot on to describe what I have been thinking about.</p>



<p>Here is what I am taking into consideration:</p>


<h3 class="wp-block-heading" id="custom-properties">Custom Properties</h3>


<p>I knew that a lot about what I liked about Sass had started to make its way into native CSS. Custom properties were one of the first things. <a href="https://css-tricks.com/a-complete-guide-to-custom-properties/">Custom properties</a> are more powerful than <a href="https://sass-lang.com/documentation/variables/" rel="noopener">Sass variables</a> because you can assign a new value to a custom property in a media query or in a theming system, like light and dark modes. That&#8217;s something Sass is unable to do since variables become static once they are compiled into vanilla CSS. You can also assign and update custom properties with JavaScript. Custom properties also work with inheritance and have a broader scope than Sass variables.</p>



<p>So, yeah. I found that not only was I already fairly familiar with the concept of variables, thanks to Sass, but&nbsp;<a href="https://css-tricks.com/difference-between-types-of-css-variables/">the native CSS version was much more powerful</a>.</p>



<p>I first used CSS Custom Properties when building two different themes (light and dark) for a client project. I also used them several times with JavaScript and liked how it gave me new possibilities for using CSS and JavaScript together. In <a href="https://jeffbridgforth.com/get-to-know-me-roh/" rel="noopener">my new job</a>, we use custom properties extensively and I have completely switched over to using them in any new code that I write. I made use of custom properties extensively when I redesigned my personal site last year. I took advantage of it to create a light and dark theme and I utilized it with <a href="https://utopia.fyi" rel="noopener">Utopia</a> for typography and spacing utilities.</p>


<h3 class="wp-block-heading" id="nesting">Nesting</h3>


<p>When Sass introduced nesting, it simplified the writing of CSS code because you write style rules within another style rule (usually a parent). This means that you no longer had to write out the full descendent selector as a separate rule. You could also nest media queries, feature queries, and container queries.</p>



<p>This ability to group code together made it easier to see the relationships between parent and child selectors. It was also useful to have the media queries, container queries, or feature queries grouped inside those selectors rather than grouping all the media query rules together further down in the stylesheet.</p>



<p>I already mentioned that I stumbled across native CSS nesting when writing code for my memory keeper prototype. I was very excited that the specification extended what I already knew about nesting from Sass.</p>



<p>Two years ago, the nesting specification was going to require you to start the nested query with the &amp; symbol, which was different from how it worked in Sass.</p>



<pre rel="SCSS" class="wp-block-csstricks-code-block language-scss" data-line=""><code markup="tt">.footer {
  a { color: blue }
}</code></pre>



<pre rel="CSS" class="wp-block-csstricks-code-block language-css" data-line=""><code markup="tt">/* 2023 */
.footer {
  &amp; a { color: blue } /* This was valid then */
}</code></pre>



<p>But that changed sometime in the last two years and you no longer need the ampersand (<code>&amp;</code>) symbol to write a nested query. You can write just as you had been writing it in Sass. I am very happy about this change because it means native CSS nesting is just like I have been writing it in Sass.</p>



<pre rel="CSS" class="wp-block-csstricks-code-block language-css" data-line=""><code markup="tt">/* 2025 */
.footer {
  a { color: blue } /* Today's valid syntax */
}</code></pre>



<p>There are some differences in the native implementation of nesting versus Sass. One difference is that you cannot create concatenated selectors with CSS. If you love <a href="https://css-tricks.com/bem-101/">BEM</a>, then you probably made use of this feature in Sass. But it does not work in native CSS.</p>



<pre rel="CSS" class="wp-block-csstricks-code-block language-css" data-line=""><code markup="tt">.card {
  &amp;__title {}
  &amp;__body {}
  &amp;__footer {}
}</code></pre>



<p>It does not work because the <code>&amp;</code> symbol is a live object in native CSS and it is always treated as a separate selector. Don&#8217;t worry, if you don&#8217;t understand that, neither do I. The important thing is to understand the implication – you cannot concatenate selectors in native CSS nesting.</p>



<p>If you are interested in reading a bit more about this, I would suggest Kevin Powell&#8217;s, <a href="https://thecascade.dev/article/native-css-nesting-vs-scss-nesting/" rel="noopener">&#8220;Native CSS Nesting vs. Sass Nesting&#8221;</a> from 2023. Just know that the information about having to use the <code>&amp;</code> symbol before an element selector in native CSS nesting is out of date.</p>



<p>I never took advantage of concatenated selectors in my Sass code so this will not have an impact on my work. For me, nesting is native CSS is equivalent to how I was using it in Sass and is one of the reasons why to consider un-Sassing.</p>



<p>My advice is to be careful with nesting. I would suggest trying to keep your nested code to three levels at the most. Otherwise, you end up with very long selectors that may be more difficult to override in other places in our codebase. Keep it simple.</p>


<h3 class="wp-block-heading" id="the-color-mix-function">The <code>color-mix()</code> function</h3>


<p>I liked using the Sass color module to lighten or darken a color. I would use this most often with buttons where I wanted the hover color to be different. It was really easy to do with Sass. (I am using <code>$color</code> to stand in for the color value).</p>



<pre rel="SCSS" class="wp-block-csstricks-code-block language-scss" data-line=""><code markup="tt">background-color: darken($color, 20%);</code></pre>



<p>The <a href="https://css-tricks.com/almanac/functions/c/color-mix/"><code>color-mix()</code></a> function in native CSS allows me to do the same thing and I have used it extensively in the past few months since learning about it from <a href="https://gomakethings.com/mixing-colors-with-css/" rel="noopener">Chris Ferdinandi</a>.</p>



<pre rel="CSS" class="wp-block-csstricks-code-block language-css" data-line=""><code markup="tt">background-color: color-mix(in oklab, var(--color), #000000 20%);</code></pre>


<h3 class="wp-block-heading" id="mixins-and-functions">Mixins and functions</h3>


<p>I know that a lot of developers who use Sass make extensive use of <a href="https://css-tricks.com/videos/132-quick-useful-case-sass-math-mixins/">mixins</a>. In the past, I used a fair number of mixins. But a lot of the time, I was just pasting mixins from previous projects. And many times, I didn&#8217;t make as much use of them as I could because I would just plain forget that I had them. They were always nice helper functions and allowed me to not have to remember things like <a href="https://css-tricks.com/clearfix-a-lesson-in-web-development-evolution/">clearfix</a> or font smoothing. But those were also techniques that I found myself using less and less.</p>



<p>I also utilized&nbsp;<a href="https://sass-lang.com/documentation/at-rules/function/" rel="noopener">functions</a>&nbsp;in Sass and created several of my own, mostly to do some math on the fly.&nbsp;I mainly used them to convert pixels into <code>em</code>s because I liked being able to define my typography and spacing as relative and creating relationships in my code. I also had written a function to covert pixels to <code>em</code>s for custom media queries that did not fit within the breakpoints I normally used. I had learned that it was a much better practice to use <code>em</code>s in media queries so that layouts would not break when a user used page zoom.</p>



<p>Currently, we do not have a way to do mixins and functions in native CSS. But there is work being done to add that functionality. <a href="https://css-tricks.com/css-functions-and-mixins-module-notes/">Geoff wrote about the CSS Functions and Mixins Module.</a></p>



<p>I did a little experiment for the use case I was using Sass functions for. I wanted to calculate <code>em</code> units from pixels in a custom media query. My standard practice is to set the body text size to <code>100%</code> which equals 16 pixels by default. So, I wrote a <a href="https://css-tricks.com/a-complete-guide-to-calc-in-css/"><code>calc()</code> function</a> to see if I could replicate what my Sass function provided me.</p>



<pre rel="CSS" class="wp-block-csstricks-code-block language-css" data-line=""><code markup="tt">@media (min-width: calc((600 / 16) * 1em));</code></pre>



<p>This custom media query is for a minimum width of <code>600px</code>. This would work based on my setting the base font size to <code>100%</code>. It could be modified.</p>


<h3 class="wp-block-heading" id="tired-of-tooling">Tired of tooling</h3>


<p>Another reason to consider un-Sassing is that I am simply <em>tired</em> of tooling. Tooling has gotten more and more complex over the years, and not necessarily with a better <a href="https://css-tricks.com/what-is-developer-experience-dx/">developer experience</a>. From what I have observed, today&#8217;s tooling is predominantly geared towards JavaScript-first developers, or anyone using a framework like React. All I need is a tool that is easy to set up and maintain. I don&#8217;t want to have to learn a complex system in order to do very simple tasks.</p>



<p>Another issue is dependencies. At my current job, I needed to add some new content and styles to an older WordPress site that had not been updated in several years. The site used Sass, and after a bit of digging, I discovered that the previous developer had used <a href="https://codekitapp.com/" rel="noopener">CodeKit</a> to process the code. I renewed my Codekit license so that I could add CSS to style the content I was adding. It took me a bit to get the settings correct because the settings in the repo were not saving the processed files to the correct location.</p>



<p>Once I finally got that set, I continued to encounter errors. <a href="https://sass-lang.com/dart-sass/" rel="noopener">Dart Sass</a>, the engine that powers Sass, introduced changes to the syntax that broke the existing code. I started refactoring a large amount of code to update the site to the correct syntax, allowing me to write new code that would be processed.&nbsp;</p>



<p>I spent about 10 minutes attempting to refactor the older code, but was still getting errors. I just needed to add a few lines of CSS to style the new content I was adding to the site. So, I decided to go rogue and write the new CSS I needed directly in the WordPress template. I have had similar experiences with other legacy codebases, and that’s the sort of thing that can happen when you’re super reliant on third-party dependencies. You spend more time trying to refactor the Sass code so you can get to the point where you can add new code and have it compiled.</p>



<p>All of this has left me tired of tooling. I am fortune enough at my new position that the tooling is all set up through the Django CMS. But even with that system, <a href="https://jeffbridgforth.com/weeknotes-july-10-2025/#django-and-sass" rel="noopener">I have run into issues</a>. For example, I tried using a mixture of percentage and pixels values in a <a href="https://css-tricks.com/almanac/functions/m/minmax/"><code>minmax()</code></a> function and Sass was trying to evaluate it as a math function and the units were incompatible.</p>



<pre rel="CSS" class="wp-block-csstricks-code-block language-css" data-line=""><code markup="tt">grid-template-columns: repeat(auto-fill, minmax(min(200px, 100%), 1fr));</code></pre>



<p>I needed to be able to escape and not have Sass try to evaluate the code as a math function:</p>



<pre rel="SCSS" class="wp-block-csstricks-code-block language-scss" data-line=""><code markup="tt">grid-template-columns: repeat(auto-fill, minmax(unquote("min(200px, 100%)"), 1fr));</code></pre>



<p>This is not a huge pain point but it was something that I had to take some time to investigate that I could have been using to write HTML or CSS. Thankfully, that is something <a href="https://css-tricks.com/when-sass-and-new-css-features-collide/">Ana Tudor has written about</a>.</p>



<p>All of these different pain points lead me to be tired of having to mess with tooling. It is another reason why I have considered un-Sassing.</p>


<h3 class="wp-block-heading" id="verdict">Verdict</h3>


<p>So what is my verdict — is it time to un-Sass?</p>



<p>Please don&#8217;t hate me, but my conclusion is: <strong>it depends</strong>. Maybe not the definitive answer you were looking for.</p>



<p>But you probably are not surprised. If you have been working in web development even a short amount of time, you know that there are very few definitive ways of doing things. There are a lot of different approaches, and just because someone else solves it differently, does not mean you are right and they are wrong (or vice versa). Most things come down to the project you are working on, your audience, and a host of other factors.</p>



<p>For my personal site, yes, I would like to un-Sass. I want to kick the build process to the curb and eliminate those dependencies. I would also like for other developers to be able to view source on my CSS. You can&#8217;t view source on Sass. And part of the reason I write on my site is to share solutions that might benefit others, and making code more accessible is a nice maintenance enhancement.</p>



<p>My personal site does not have a very large codebase. I could probably easily un-Sass it in a couple of days or over a weekend.</p>



<p>But for larger sites, like the codebase I work with at my job. I wouldn&#8217;t suggest un-Sassing it. There is way too much code that would have to be refactored and I am unable to justify the cost for that kind of effort. And honestly, it is not something I feel motivated to tackle. It works just fine the way that it is. And Sass is still a very good tool to use. It&#8217;s not &#8220;breaking&#8221; anything.</p>



<p>Your project may be different and there might be more gains from un-Sassing than the project I work on. Again, it depends.</p>


<h3 class="wp-block-heading" id="the-way-forward">The way forward</h3>


<p>It is an exciting time to be a CSS developer. The language is continuing to evolve and mature. And every day, it is incorporating new features that first came to us through other third-party tools such as Sass. It is always a good idea to stop and re-evaluate your technology decisions to determine if they still hold up or if more modern approaches would be a better way forward.</p>



<p>That does not mean we have to go back and &#8220;fix&#8221; all of our old projects. And it might not mean doing a complete overhaul. A lot of newer techniques can live side by side with the older ones. We have a mix of both Sass variables and CSS custom properties in our codebase. They don&#8217;t work against each other. The great thing about web technologies is that they build on each other and there is usually backward compatibility.</p>



<p>Don&#8217;t be afraid to try new things. And don&#8217;t judge your past work based on what you know today. You did the best you could given your skill level, the constraints of the project, and the technologies you had available. You can start to incorporate newer ways right alongside the old ones. <a href="https://github.com/melanierichards/just-build-websites" rel="noopener">Just build websites!</a></p>
<hr />
<p><small><a rel="nofollow" href="https://css-tricks.com/is-it-time-to-un-sass/">Is it Time to Un-Sass?</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/is-it-time-to-un-sass/feed/</wfw:commentRss>
			<slash:comments>13</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">389101</post-id>	</item>
		<item>
		<title>The “Most Hated” CSS Feature: cos() and sin()</title>
		<link>https://css-tricks.com/the-most-hated-css-feature-cos-and-sin/</link>
					<comments>https://css-tricks.com/the-most-hated-css-feature-cos-and-sin/#comments</comments>
		
		<dc:creator><![CDATA[Juan Diego Rodríguez]]></dc:creator>
		<pubDate>Mon, 15 Sep 2025 14:31:06 +0000</pubDate>
				<category><![CDATA[Articles]]></category>
		<category><![CDATA[animations]]></category>
		<category><![CDATA[math]]></category>
		<guid isPermaLink="false">https://css-tricks.com/?p=389130</guid>

					<description><![CDATA[<p>I want to look at practical uses for CSS trigonometric functions. And we'll start with what may be the most popular functions of the "worst" feature: <code>sin()</code> and <code>cos()</code>.</p>
<hr />
<p><small><a rel="nofollow" href="https://css-tricks.com/the-most-hated-css-feature-cos-and-sin/">The “Most Hated” CSS Feature: cos() and sin()</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>No feature is truly &#8220;the worst&#8221; in CSS, right? After all, it&#8217;s all based on opinion and personal experience<span style="margin: 0px; padding: 0px;">, but if we had to reach a consensus, checking the&nbsp;<a href="https://2025.stateofcss.com/en-US/" target="_blank" rel="noopener">State of CSS 2025</a>&nbsp;results would be a good starting point</span>. I did exactly that, jumped into the&nbsp;<a href="https://2025.stateofcss.com/en-US/awards/" rel="noopener">awards section</a>,&nbsp;and there I found it: the &#8220;Most Hated Feature,&#8221; a title no CSS should have bear&#8230;</p>



<span id="more-389130"></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/2025/09/most_hated_feature.jpg?resize=1920%2C1080&#038;ssl=1" alt="State of CSS screenshot revealing the survey's most hated CSS feature, trigonometric functions." class="wp-image-389164" srcset="https://i0.wp.com/css-tricks.com/wp-content/uploads/2025/09/most_hated_feature.jpg?w=1920&amp;ssl=1 1920w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2025/09/most_hated_feature.jpg?resize=300%2C169&amp;ssl=1 300w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2025/09/most_hated_feature.jpg?resize=1024%2C576&amp;ssl=1 1024w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2025/09/most_hated_feature.jpg?resize=768%2C432&amp;ssl=1 768w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2025/09/most_hated_feature.jpg?resize=1536%2C864&amp;ssl=1 1536w" sizes="auto, (min-width: 735px) 864px, 96vw" /></figure>



<p>This shocks me, if I&#8217;m being honest. Are really trigonometric functions really <em>that</em> hated? I know &#8220;hated&#8221; is not the same as saying something is &#8220;worst&#8221;, but it still has an awful ring to it. And I know I&#8217;m being a little dramatic here, since only &#8220;9.1% of respondents truly hate trigonometry.&#8221; But that&#8217;s still too much shade being thrown for my taste.</p>



<p>I want to eliminate that 9.1%. So, in this series, I want to look at practical uses for CSS trigonometric functions. We&#8217;ll tackle them in pieces because there&#8217;s a lot to take in and I find it easiest to learn and retain information when it&#8217;s chunked into focused, digestible pieces. And we&#8217;ll start with what may be the most popular  functions of the &#8220;worst&#8221; feature: <code>sin()</code>&nbsp;and&nbsp;<code>cos()</code>.</p>



<div class="wp-block-group ticss-ad1a3c1b"><div class="wp-block-group__inner-container is-layout-flow wp-block-group-is-layout-flow"><h4 class="wp-block-heading" id="css-trigonometric-functions-the-most-hated-css-feature">CSS Trigonometric Functions: The “Most Hated” CSS Feature</h4>


<ol class="wp-block-list">
<li><strong><a href="https://css-tricks.com/the-most-hated-css-feature-cos-and-sin/"><code>sin()</code> and <code>cos()</code></a></strong> (<em>You are here!</em>)</li>



<li><strong><code>tan()</code></strong> <em>(Coming soon)</em></li>



<li><strong><code>asin()</code>,&nbsp;<code>acos()</code>,&nbsp;<code>atan()</code>&nbsp;and&nbsp;<code>atan2()</code></strong> <em>(Coming soon)</em></li>
</ol>
</div></div>


<h3 class="wp-block-heading" id="what-are-even-cos-and-sin-">What the heck are <code>cos()</code> and <code>sin()</code> anyway?</h3>


<p class="is-style-explanation">This section is for those who&nbsp;<code>cos()</code>&nbsp;and&nbsp;<code>sin()</code>&nbsp;don&#8217;t quite click yet, or simply want a refresher. If you aced trigonometry quizzes in high school, feel free to skip ahead to the <a href="#aa-circular-layouts">next section</a>!</p>



<p>What I find funny about&nbsp;<code>cos()</code>&nbsp;and&nbsp;<code>sin()</code>— and also why I think there is confusion around them — is the many ways we can describe them. We don&#8217;t have to look too hard. A quick glance at this&nbsp;<a href="https://en.wikipedia.org/wiki/Sine_and_cosine" rel="noopener">Wikipedia page</a>&nbsp;has an eye-watering number of super nuanced definitions.</p>



<p>This is a <a href="https://css-tricks.com/now-you-see-it/">learning problem in the web development field</a>. I feel like some of those definitions are far too general and lack detail about the essence of what trigonometric functions like&nbsp;<code>sin()</code>&nbsp;and&nbsp;<code>cos()</code> can do. Conversely, other definitions are overly complex and academic, making them tough to grok without an advanced degree.</p>



<p>Let&#8217;s stick to the sweet middle spot: the&nbsp;<strong>unit circle</strong>.</p>



<p>Meet the unit circle. It is a circle with a radius of one unit:</p>



<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/2025/09/unit_circle_mdwuda.jpg?resize=1920%2C1080&#038;ssl=1" alt="A circle in a white dashed outline against a black background. A purple line from the center to the outer border indicates the shape's radius, equal to 1." class="wp-image-389165" srcset="https://i0.wp.com/css-tricks.com/wp-content/uploads/2025/09/unit_circle_mdwuda.jpg?w=1920&amp;ssl=1 1920w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2025/09/unit_circle_mdwuda.jpg?resize=300%2C169&amp;ssl=1 300w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2025/09/unit_circle_mdwuda.jpg?resize=1024%2C576&amp;ssl=1 1024w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2025/09/unit_circle_mdwuda.jpg?resize=768%2C432&amp;ssl=1 768w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2025/09/unit_circle_mdwuda.jpg?resize=1536%2C864&amp;ssl=1 1536w" sizes="auto, (min-width: 735px) 864px, 96vw" /></figure>



<p>Right now it&#8217;s alone&#8230; in space. Let&#8217;s place it on the Cartesian coordinate system (the classic chart with X and Y&nbsp;axes). We describe each point in space in Cartesian coordinates:</p>



<ol class="wp-block-list">
<li><strong>The X coordinate:</strong> The horizontal axis, plotting the point towards the left or right.</li>



<li><strong>The Y coordinate:</strong> The vertical axis, plotting the point towards the top or bottom.</li>
</ol>



<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/2025/09/unit_circle_grid_fgfexz.jpg?resize=1920%2C1080&#038;ssl=1" alt="Same circle placed on a grid with labels indicating the coordinates." class="wp-image-389166" srcset="https://i0.wp.com/css-tricks.com/wp-content/uploads/2025/09/unit_circle_grid_fgfexz.jpg?w=1920&amp;ssl=1 1920w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2025/09/unit_circle_grid_fgfexz.jpg?resize=300%2C169&amp;ssl=1 300w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2025/09/unit_circle_grid_fgfexz.jpg?resize=1024%2C576&amp;ssl=1 1024w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2025/09/unit_circle_grid_fgfexz.jpg?resize=768%2C432&amp;ssl=1 768w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2025/09/unit_circle_grid_fgfexz.jpg?resize=1536%2C864&amp;ssl=1 1536w" sizes="auto, (min-width: 735px) 864px, 96vw" /></figure>



<p>We can move through the unit circle by an angle, which is measured from the positive X-axis going counter-clockwise.</p>



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



<p class="is-style-explanation">We can go in a clockwise direction by using negative angles. As my physics teacher used to say, &#8220;Time is negative!&#8221;</p>



<p>Notice how each angle lands on a unique point in the unit circle. How else can we describe that point using&nbsp;Cartesian coordinates?</p>



<p>When the angle is&nbsp;<code>0°</code>&nbsp;the X and Y coordinates are 1 and 0 (<code>1</code>, <code>0</code>), respectively. We can deduce the Cartesian coordinates for other angles just as easily, like&nbsp;<code>90°</code>,&nbsp;<code>180°</code>&nbsp;and&nbsp;<code>270°</code>. But for any other angle, we don&#8217;t know where the point is initially located on the unit circle.</p>



<p>If only there were a pair of functions that take an angle and give us our desired coordinates&#8230;</p>



<p><strong>You guessed it, the CSS&nbsp;<code>cos()</code>&nbsp;and&nbsp;<code>sin()</code>&nbsp;functions do exactly that.</strong> And they&#8217;re very closely related, where <code>cos()</code> is designed to handle the X coordinate and <code>sin()</code> returns the Y coordinate.</p>



<p>Play with the toggle slider in the following demo to see the relationship between the two functions, and notice how they form a right triangle with the initial point on the unit circle:</p>



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



<p>I think that&#8217;s all you really need to know about&nbsp;<code>cos()</code>&nbsp;and&nbsp;<code>sin()</code> for the moment. They&#8217;re mapped to Cartesian coordinates, which allows us to track a point along the unit circle with an angle, no matter what size that circle happens to be.</p>



<p>Let&#8217;s dive into what we can actually use <code>cos()</code> and <code>sin()</code> for our everyday CSS work. It&#8217;s always good to put a little real-world context to theoretical concepts like math.</p>


<h3 class="wp-block-heading" id="circular-layouts">Circular layouts</h3>


<p>If we go by the unit circle definition of&nbsp;<code>cos()</code>&nbsp;and&nbsp;<code>sin()</code>, then it&#8217;s easy to see how they might be used to create circular layouts in CSS.  The initial setup is a single row of circular elements:</p>



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



<p>Say we want to place each circular item around the outline of a larger circle instead. First, we would let CSS know the total number of elements and also each element&#8217;s index (the order it&#8217;s in), something we can do with an inline CSS variable that holds each order in the position:</p>



<pre rel="HTML" class="wp-block-csstricks-code-block language-markup" data-line=""><code markup="tt">&lt;ul style="--total: 9">
  &lt;li style="--i: 0">0&lt;/li>
  &lt;li style="--i: 1">1&lt;/li>
  &lt;li style="--i: 2">2&lt;/li>
  &lt;li style="--i: 3">3&lt;/li>
  &lt;li style="--i: 4">4&lt;/li>
  &lt;li style="--i: 5">5&lt;/li>
  &lt;li style="--i: 6">6&lt;/li>
  &lt;li style="--i: 7">7&lt;/li>
  &lt;li style="--i: 8">8&lt;/li>
&lt;/ul></code></pre>



<p class="is-style-explanation"><strong>Note:</strong> This step will become <em>much</em> easier and concise when the&nbsp;<a href="https://css-tricks.com/almanac/functions/s/sibling-index/"><code>sibling-index()</code></a>&nbsp;and&nbsp;<code><a href="https://css-tricks.com/almanac/functions/s/sibling-count/">sibling-count()</a></code> functions gain support (and <a href="https://css-tricks.com/how-to-wait-for-the-sibling-count-and-sibling-index-functions/">they&#8217;re <em>really</em> neat</a>). I&#8217;m hardcoding the indexes with inline CSS variables in the meantime.</p>



<p>To place the items around the outline of a larger circle, we have to space them evenly by a certain angle. And to get that angle, we can divide&nbsp;<code>360deg</code>&nbsp;(a full turn around the circle) by the total number of items, which is 8 in this specific example. Then, to get each element&#8217;s specific angle, we can multiply the angle spacing by the element&#8217;s index (i.e., position):</p>



<pre rel="CSS" class="wp-block-csstricks-code-block language-css" data-line=""><code markup="tt">li {
  --rotation: calc(360deg / var(--total) * var(--i));
}</code></pre>



<p>We also need to push the items away from the center, so we&#8217;ll assign a <code>--radius</code> value for the circle using another variable.</p>



<pre rel="CSS" class="wp-block-csstricks-code-block language-css" data-line=""><code markup="tt">ul {
  --radius: 10rem;
}</code></pre>



<p>We have the element&#8217;s angle and radius. What&#8217;s left is to calculate the X and Y coordinates for each item. </p>



<p><strong>That&#8217;s where&nbsp;<code>cos()</code>&nbsp;and&nbsp;<code>sin()</code>&nbsp;come into the picture.</strong> We use them to get the X and Y coordinates that place each item around the unit circle, then multiply each coordinate by the <code>--radius</code> value to get an item&#8217;s final position on the bigger circle:</p>



<pre rel="CSS" class="wp-block-csstricks-code-block language-css" data-line=""><code markup="tt">li {
  /* ... */
  position: absolute;

  transform: translateX(calc(cos(var(--rotation)) * var(--radius))) 
             translateY(calc(sin(var(--rotation)) * var(--radius)));
}</code></pre>



<p>That&#8217;s it! We have a series of eight circular items placed evenly around the outline of a larger circle:</p>



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



<p>And we didn&#8217;t need to use a bunch of magic numbers to do it! All we provide CSS with is the unit circle&#8217;s radius, and then CSS does all the trigonometric gobbledygook that makes so many of us call this the &#8220;worst&#8221; CSS feature. Hopefully, I&#8217;ve convinced you to soften your opinions on them if that&#8217;s what was holding you back!</p>



<p>We aren&#8217;t limited to full circles, though! We can also have a semicircular arrangement by choosing&nbsp;<code>180deg</code>&nbsp;instead of&nbsp;<code>360deg</code>.</p>



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



<p>This opens up lots of layout possibilities. Like, what if we want a circular menu that expands from a center point by transitioning the radius of the circle? We can totally do that:</p>



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



<p>Click or hover the heading and the menu items form around the circle!</p>


<h3 class="wp-block-heading" id="wavy-layouts">Wavy layouts</h3>


<p>There&#8217;s still more we can do with layouts! If, say, we plot the&nbsp;<code>cos()</code>&nbsp;and&nbsp;<code>sin()</code> coordinates on a two-axis graph, notice how they give us a pair of waves that periodically go up and down. And notice they are offset from each other along the horizontal (X) axis:</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/2025/09/sine_cosine_waves-1024x576.jpg?resize=1024%2C576&#038;ssl=1" alt="Comparing sine and cosine waves on a coordinate plane. They are essentially the same shape, but offset horizontally on the x-axis." class="wp-image-389167" srcset="https://i0.wp.com/css-tricks.com/wp-content/uploads/2025/09/sine_cosine_waves.jpg?resize=1024%2C576&amp;ssl=1 1024w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2025/09/sine_cosine_waves.jpg?resize=300%2C169&amp;ssl=1 300w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2025/09/sine_cosine_waves.jpg?resize=768%2C432&amp;ssl=1 768w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2025/09/sine_cosine_waves.jpg?resize=1536%2C864&amp;ssl=1 1536w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2025/09/sine_cosine_waves.jpg?w=1920&amp;ssl=1 1920w" sizes="auto, (min-width: 735px) 864px, 96vw" /></figure>



<p>Where do these waves come from? If we think back to the unit circle we talked about earlier, the value of&nbsp;<code>cos()</code>&nbsp;and&nbsp;<code>sin()</code>&nbsp;oscillate between <code>-1</code> and <code>1</code>. In other words, the lengths match when the angle around the unit circle varies. If we graph that oscillation, then we&#8217;ll get our wave and see that they&#8217;re sorta like reflections of each other.</p>



<details class="wp-block-details is-layout-flow wp-block-details-is-layout-flow"><summary>&#x26a0;&#xfe0f; Auto-playing media</summary>
<figure class="wp-block-image size-full"><img data-recalc-dims="1" loading="lazy" decoding="async" width="650" height="390" src="https://i0.wp.com/css-tricks.com/wp-content/uploads/2025/09/circle_cos_sin_k5uu75.gif?resize=650%2C390&#038;ssl=1" alt="Animated sine and cosine waves flowing horizontally on the X axis of a two-axis graph." class="wp-image-389235"/></figure>



<p></p>
</details>



<p>Can we place an element following one of these waves? Absolutely. Let&#8217;s start with the same single row layout of circular items we made earlier. This time, though, the length of that row spans beyond the viewport, causing overflow.</p>



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



<p>We&#8217;ll assign an index position for each item like we did before, but this time we don&#8217;t need to know the total number of items. We had eight items last time, so let&#8217;s bump that up to 10 and pretend like we don&#8217;t know that:</p>



<pre rel="HTML" class="wp-block-csstricks-code-block language-markup" data-line=""><code markup="tt">&lt;ul>
  &lt;li style="--i: 0">&lt;/li>
  &lt;li style="--i: 1">&lt;/li>
  &lt;li style="--i: 2">&lt;/li>
  &lt;li style="--i: 3">&lt;/li>
  &lt;li style="--i: 4">&lt;/li>
  &lt;li style="--i: 5">&lt;/li>
  &lt;li style="--i: 6">&lt;/li>
  &lt;li style="--i: 7">&lt;/li>
  &lt;li style="--i: 8">&lt;/li>
  &lt;li style="--i: 9">&lt;/li>
  &lt;li style="--i: 10">&lt;/li>
&lt;/ul></code></pre>



<p>We want to vary the element&#8217;s vertical position along either a <code>sin()</code> or <code>cos()</code> wave, meaning translating each item&#8217;s position based on its order in the index. We&#8217;ll multiply an item&#8217;s index by a certain angle that is passed into the&nbsp;<code>sin()</code>&nbsp;function, and that will return a ratio that describes how high or low the element should be on the wave. The final thing is to multiply that result by a length value, which I calculated as half  an item&#8217;s total size.</p>



<p>Here&#8217;s the math in CSS-y terms:</p>



<pre rel="CSS" class="wp-block-csstricks-code-block language-css" data-line=""><code markup="tt">li {
  transform: translateY(calc(sin(60deg * var(--i)) * var(--shape-size) / 2));
}</code></pre>



<p>I&#8217;m using a&nbsp;<code>60deg</code>&nbsp;value because the waves it produces are smoother than some other values, but we can vary it as much as we want to get cooler waves. Play around with the toggle in the next demo and watch how the wave&#8217;s intensity changes with the angle:</p>



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



<p>This is a great example to see what we&#8217;re working with, but how would you use it in your work? Imagine we have two of these wavy chains of circles, and we want to intertwine them together, kinda like a DNA strand.</p>



<p>Let&#8217;s say we&#8217;re starting with the HTML structure for two unordered lists nested inside another unordered list. The two nested unordered lists represent the two waves that form the chain pattern:</p>



<pre rel="HTML" class="wp-block-csstricks-code-block language-markup" data-line=""><code markup="tt">&lt;ul class="waves">
  &lt;!-- First wave -->
  &lt;li>
    &lt;ul class="principal">
      &lt;!-- Circles -->
      &lt;li style="--i: 0">&lt;/li>
      &lt;li style="--i: 1">&lt;/li>
      &lt;li style="--i: 2">&lt;/li>
      &lt;li style="--i: 3">&lt;/li>
      &lt;!-- etc.  -->
    &lt;/ul>
  &lt;/li>

  &lt;!-- Second wave -->
  &lt;li>
    &lt;ul class="secondary">
      &lt;!-- Circles -->
      &lt;li style="--i: 0">&lt;/li>
      &lt;li style="--i: 1">&lt;/li>
      &lt;li style="--i: 2">&lt;/li>
      &lt;li style="--i: 3">&lt;/li>
      &lt;!-- etc.  -->
    &lt;/ul>
  &lt;/li>
&lt;/ul></code></pre>



<p>Pretty similar to the examples we&#8217;ve seen so far, right? We&#8217;re still working with an unordered list where the items are indexed with a CSS variable, but now we&#8217;re working with two of those lists&#8230; and they&#8217;re contained inside a third unordered list. We don&#8217;t have to structure this as lists, but I decided to leave them so I can use them as hooks for additional styling later.</p>



<p>To avoid any problems, we&#8217;ll ignore the two direct <code>&lt;li&gt;</code> elements in the outer unordered list that contain the other lists using&nbsp;<code>display: contents</code>.</p>



<pre rel="CSS" class="wp-block-csstricks-code-block language-css" data-line=""><code markup="tt">.waves > li { display: contents; }</code></pre>



<p>Notice how one of the chains is the &#8220;principal&#8221; while the other is the &#8220;secondary.&#8221; The difference is that the &#8220;secondary&#8221; chain is positioned behind the &#8220;principal&#8221; chain. I&#8217;m using slightly different background colors for the items in each chain, so it&#8217;s easier to distinguish one from the other as you scroll through the block-level overflow.</p>



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



<p>We can reorder the chains using a stacking context:</p>



<pre rel="CSS" class="wp-block-csstricks-code-block language-css" data-line=""><code markup="tt">.principal {
  position: relative;
  z-index: 2;
}

.secondary { position: absolute; }</code></pre>



<p>This positions one chain on top of the other. Next, we will adjust each item&#8217;s vertical position with the &#8220;hated&#8221; <code>sin()</code> and <code>cos()</code> functions. Remember, they&#8217;re sorta like reflections of one another, so the variance between the two is what offsets the waves to form two intersecting chains of items:</p>



<pre rel="CSS" class="wp-block-csstricks-code-block language-css" data-line=""><code markup="tt">.principal {
  /* ... */
  li {
    transform: translateY(calc(sin(60deg * var(--i)) * var(--shape-size) / 2));
  }
}

.secondary {
  /* ... */
  li {
    transform: translateY(calc(cos(60deg * var(--i)) * var(--shape-size) / 2));
  }
}</code></pre>



<p>We can accentuate the offset even more by shifting the&nbsp;<code>.secondary</code>&nbsp;wave another&nbsp;<code>60deg</code>:</p>



<pre rel="CSS" class="wp-block-csstricks-code-block language-css" data-line=""><code markup="tt">.secondary {
  /* ... */
  li {
    transform: translateY(calc(cos(60deg * var(--i) + 60deg) * var(--shape-size) / 2));
  }
}</code></pre>



<p>The next demo shows how the waves intersect at an offset angle of <code>60deg</code>. Adjust the slider toggle to see how the waves intersect at different angles:</p>



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



<p>Oh, I told you this could be used in a practical, real-world way. How about adding a little whimsy and flair to a hero banner:</p>



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


<h3 class="wp-block-heading" id="damped-oscillatory-animations">Damped oscillatory animations</h3>


<p>The last example got me thinking: <strong>is there a way to use&nbsp;<code>sin()</code>&nbsp;and&nbsp;<code>cos()</code>&#8216;s back and forth movement for animations?</strong> The first example that came to mind was an animation that also went back and forth, something like a pendulum or a bouncing ball.</p>



<p>This is, of course, trivial since we can do it in a single&nbsp;<a href="https://css-tricks.com/almanac/properties/a/animation/"><code>animation</code></a>&nbsp;declaration:</p>



<pre rel="CSS" class="wp-block-csstricks-code-block language-css" data-line=""><code markup="tt">.element {
  animation: someAnimation 1s infinite alternate;
}</code></pre>



<p>This &#8220;back and forth&#8221; animation is called <em>oscillatory</em> movement. And while&nbsp;<code>cos()</code>&nbsp;or&nbsp;<code>sin()</code>&nbsp;are used to model oscillations in CSS, it would be like reinventing the wheel (albeit a clunkier one).</p>



<p>I&#8217;ve learned that perfect oscillatory movement — like a pendulum that swings back and forth in perpetuity, or a ball that never stops bouncing — doesn&#8217;t really exist. Movement tends to decay over time, like a bouncing spring:</p>



<details class="wp-block-details is-layout-flow wp-block-details-is-layout-flow"><summary>&#x26a0;&#xfe0f; Auto-playing media</summary>
<figure class="wp-block-image size-full"><img data-recalc-dims="1" loading="lazy" decoding="async" width="110" height="359" src="https://i0.wp.com/css-tricks.com/wp-content/uploads/2025/09/damped_spring_nwegdi.gif?resize=110%2C359&#038;ssl=1" alt="An animated coiled spring anchored by an orange box showing natural spring motion." class="wp-image-389231"/></figure>



<p></p>
</details>



<p>There&#8217;s a specific term that describes this: <em>damped</em>&nbsp;oscillatory movement. And guess what? We can model it in CSS with the&nbsp;<code>cos()</code> function! If we graph it over time, then we will see it goes back and forth while getting closer to the resting position<sup id="fn-1"><a href="#footnote-1">1</a></sup>.</p>



<figure class="wp-block-image size-full"><img data-recalc-dims="1" loading="lazy" decoding="async" width="700" height="526" src="https://i0.wp.com/css-tricks.com/wp-content/uploads/2025/09/damped_movement_gfyswm.webp?resize=700%2C526&#038;ssl=1" alt="Damped movement graph. The waves starts steep, then gradually evens out as it goes from left to right." class="wp-image-389171" srcset="https://i0.wp.com/css-tricks.com/wp-content/uploads/2025/09/damped_movement_gfyswm.webp?w=700&amp;ssl=1 700w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2025/09/damped_movement_gfyswm.webp?resize=300%2C225&amp;ssl=1 300w" sizes="auto, (min-width: 735px) 864px, 96vw" /></figure>



<p class="is-style-explanation"><a href="https://en.wikipedia.org/wiki/File:Damped_Spring_Simulation.gif" rel="noopener">Wikipedia has another animated example</a> that nicely demonstrates what damped oscillation&nbsp;looks like.</p>



<p>In general, we can describe damped oscillation over time as a mathematical function:</p>



<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/2025/09/damped_oscillation_formula.jpg?resize=1920%2C1080&#038;ssl=1" alt="Diagram of the damped oscillation formula labeling variables for time, damping, amplitude, frequency and the initial phase." class="wp-image-389173" srcset="https://i0.wp.com/css-tricks.com/wp-content/uploads/2025/09/damped_oscillation_formula.jpg?w=1920&amp;ssl=1 1920w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2025/09/damped_oscillation_formula.jpg?resize=300%2C169&amp;ssl=1 300w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2025/09/damped_oscillation_formula.jpg?resize=1024%2C576&amp;ssl=1 1024w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2025/09/damped_oscillation_formula.jpg?resize=768%2C432&amp;ssl=1 768w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2025/09/damped_oscillation_formula.jpg?resize=1536%2C864&amp;ssl=1 1536w" sizes="auto, (min-width: 735px) 864px, 96vw" /></figure>



<p>It&#8217;s composed of three parts:</p>



<ul class="wp-block-list">
<li><strong>e<sup>−γt</sup>:</strong> Due to the negative exponent, it becomes exponentially smaller as time passes, bringing the movement to a gradual stop. It is multiplied by a damping constant (γ) that specifies how quickly the movement should decay.</li>



<li><strong>a:</strong> This is the initial amplitude of the oscillation, i.e., the element&#8217;s initial position.</li>



<li><strong>cos(ωt−α): </strong>This gives the movement its oscillation as time passes. Time is multiplied by frequency (ω), which determines an element&#8217;s oscillation speed<sup id="fn-2"><a href="#footnote-2">2</a></sup>. We can also subtract from time α, which we can use to offset the initial oscillation of the system.</li>
</ul>



<p>Okay, enough with all the theory! How do we do it in CSS? We&#8217;ll set the stage with a single circle sitting all by itself.</p>



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



<p>We have a few CSS variables we can define that will come in handy since we already know the formula we&#8217;re working with:</p>



<pre rel="CSS" class="wp-block-csstricks-code-block language-css" data-line=""><code markup="tt">:root {
  --circle-size: 60px;

  --amplitude: 200px; /* The amplitude is the distance, so let's write it in pixels*/
  --damping: 0.3;
  --frequency: 0.8;
  --offset: calc(pi/2); /* This is the same as 90deg! (But in radians) */
}</code></pre>



<p>Given these variables, we can peek at what the animation would look like on a graph using a tool like&nbsp;<a href="https://www.geogebra.org/graphing" rel="noopener">GeoGebra</a>:</p>



<figure class="wp-block-image size-full"><img data-recalc-dims="1" loading="lazy" decoding="async" width="1022" height="1090" src="https://i0.wp.com/css-tricks.com/wp-content/uploads/2025/09/damped_graph_w17bzc.webp?resize=1022%2C1090&#038;ssl=1" alt="Damped motion graph. The wave is short and steep, then evens out as it goes from left to right." class="wp-image-389174" srcset="https://i0.wp.com/css-tricks.com/wp-content/uploads/2025/09/damped_graph_w17bzc.webp?w=1022&amp;ssl=1 1022w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2025/09/damped_graph_w17bzc.webp?resize=281%2C300&amp;ssl=1 281w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2025/09/damped_graph_w17bzc.webp?resize=960%2C1024&amp;ssl=1 960w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2025/09/damped_graph_w17bzc.webp?resize=768%2C819&amp;ssl=1 768w" sizes="auto, (min-width: 735px) 864px, 96vw" /></figure>



<p>From the graph, we can see that the animation starts at&nbsp;<code>0px</code>&nbsp;(thanks to our offset), then peaks around&nbsp;<code>140px</code>&nbsp;and dies out around <code>25s</code> in. I, for one, won&#8217;t be waiting 25 seconds for the animation to end, so let&#8217;s create a&nbsp;<code>--progress</code>&nbsp;property that will animate between&nbsp;<code>0</code>&nbsp;to&nbsp;<code>25</code>, and will act as our &#8220;time&#8221; in the function.</p>



<p class="is-style-explanation">Remember that to animate or transition a custom property, we&#8217;ve gotta register it with the&nbsp;<a href="https://css-tricks.com/almanac/rules/p/property/"><code>@property</code></a>&nbsp;at-rule.</p>



<pre rel="CSS" class="wp-block-csstricks-code-block language-css" data-line=""><code markup="tt">@property --progress {
  syntax: "&lt;number>";
  initial-value: 0;
  inherits: true;
}

@keyframes movement {
  from { --progress: 0; }
  to { --progress: 25; }
}</code></pre>



<p>What&#8217;s left is to implement the prior formula for the element&#8217;s movement, which, written in CSS terms, looks like this:</p>



<pre rel="CSS" class="wp-block-csstricks-code-block language-css" data-line=""><code markup="tt">.circle {
  --oscillation: calc(
    (exp(-1 * var(--damping) * var(--progress))) * 
    var(--amplitude) * 
    cos(var(--frequency) * (var(--progress)) - var(--offset))
  );

  transform: translateX(var(--oscillation));
  animation: movement 1s linear infinite;
}</code></pre>



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



<p>This gives a pretty satisfying animation by itself, but the damped motion is only on the x-axis. What would it look like if, instead, we applied the damped motion on both axes? To do this, we can copy the same oscillation formula for x, but replace the&nbsp;<code>cos()</code>&nbsp;with&nbsp;<code>sin()</code>.</p>



<pre rel="CSS" class="wp-block-csstricks-code-block language-css" data-line=""><code markup="tt">.circle {
  --oscillation-x: calc(
    (exp(-1 * var(--damping) * var(--progress))) * 
    var(--amplitude) * 
    cos(var(--frequency) * (var(--progress)) - var(--offset))
  );
  --oscillation-y: calc(
    (exp(-1 * var(--damping) * var(--progress))) * 
    var(--amplitude) * 
    sin(var(--frequency) * (var(--progress)) - var(--offset))
  );

  transform: translateX(var(--oscillation-x)) translateY(var(--oscillation-y));
  animation: movement 1s linear infinite;
}</code></pre>



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



<p>This is even more satisfying! A circular&nbsp;<em>and</em>&nbsp;damped motion, all thanks to&nbsp;<code>cos()</code>&nbsp;and&nbsp;<code>sin()</code>. Besides looking great, how could this be used in a real layout?</p>



<p>We don&#8217;t have to look too hard. Take, for example, this sidebar I recently made where the menu items pop in the viewport with a damped motion:</p>



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



<p>Pretty neat, right?!</p>


<h3 class="wp-block-heading" id="more-trigonometry-to-come-">More trigonometry to come!</h3>


<p>Well, finding uses for the &#8220;most hated CSS feature&#8221; wasn&#8217;t that hard; maybe we should start showing some love to trigonometric functions. But wait. There are still several trigonometric functions in CSS we haven&#8217;t talked about. In the following posts, we&#8217;ll keep exploring what trig functions (like&nbsp;<code>tan()</code>&nbsp;and inverse functions) can do in CSS.</p>



<div class="wp-block-group ticss-ad1a3c1b"><div class="wp-block-group__inner-container is-layout-flow wp-block-group-is-layout-flow"><h4 class="wp-block-heading" id="css-trigonometric-functions-the-most-hated-css-feature">CSS Trigonometric Functions: The “Most Hated” CSS Feature</h4>


<ol class="wp-block-list">
<li><strong><a href="https://css-tricks.com/the-most-hated-css-feature-cos-and-sin/"><code>sin()</code>&nbsp;and&nbsp;<code>cos()</code></a></strong> (<em>You are here!</em>)</li>



<li><strong><code>tan()</code></strong> <em>(Coming soon)</em></li>



<li><strong><code>asin()</code>,&nbsp;<code>acos()</code>,&nbsp;<code>atan()</code>&nbsp;and&nbsp;<code>atan2()</code></strong> <em>(Coming soon)</em></li>
</ol>
</div></div>



<p>Also, before I forget, here is another demo I made using&nbsp;cos()&nbsp;and&nbsp;sin()&nbsp;that didn&#8217;t make the cut in this article, but it is still worth checking out because it dials up the swirly-ness from the last example to show how wacky we can get.</p>



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



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


<h4 class="wp-block-heading" id="footnotes">Footnotes</h4>


<ol class="wp-block-list is-style-almanac-list">
<li id="footnote-1">This kind of damped oscillatory movement, where the back and forth is more visible, is called underdamped oscillation. There are also overdamped and critically damped oscillations, but we won&#8217;t focus on them here. <a href="#fn-1">&#x21aa;&#xfe0f;</a></li>



<li id="footnote-2">In reality, the damped constant and the frequency are closely related. You can read more about&nbsp;damped oscillation in <a href="https://phys.libretexts.org/Bookshelves/University_Physics/University_Physics_(OpenStax)/Book%3A_University_Physics_I_-_Mechanics_Sound_Oscillations_and_Waves_(OpenStax)/15%3A_Oscillations/15.06%3A_Damped_Oscillations" rel="noopener">this paper</a>. <a href="#fn-2">&#x21aa;&#xfe0f;</a></li>
</ol>



<p></p>
<hr />
<p><small><a rel="nofollow" href="https://css-tricks.com/the-most-hated-css-feature-cos-and-sin/">The “Most Hated” CSS Feature: cos() and sin()</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-most-hated-css-feature-cos-and-sin/feed/</wfw:commentRss>
			<slash:comments>10</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">389130</post-id>	</item>
		<item>
		<title>What Can We Actually Do With corner-shape?</title>
		<link>https://css-tricks.com/what-can-we-actually-do-with-corner-shape/</link>
					<comments>https://css-tricks.com/what-can-we-actually-do-with-corner-shape/#comments</comments>
		
		<dc:creator><![CDATA[Daniel Schwarz]]></dc:creator>
		<pubDate>Fri, 12 Sep 2025 14:20:45 +0000</pubDate>
				<category><![CDATA[Articles]]></category>
		<category><![CDATA[css properties]]></category>
		<category><![CDATA[shapes]]></category>
		<guid isPermaLink="false">https://css-tricks.com/?p=389060</guid>

					<description><![CDATA[<p>When I first started messing around with code, rounded corners required five background images or an <a href="https://css-tricks.com/spriting-img/">image sprite</a> likely created in Photoshop, so when <a href="https://css-tricks.com/almanac/properties/b/border-radius/"><code>border-radius</code></a> came onto the scene, I remember everybody thinking that it was the best thing ever. &#8230;</p>
<hr />
<p><small><a rel="nofollow" href="https://css-tricks.com/what-can-we-actually-do-with-corner-shape/">What Can We Actually Do With corner-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>When I first started messing around with code, rounded corners required five background images or an <a href="https://css-tricks.com/spriting-img/">image sprite</a> likely created in Photoshop, so when <a href="https://css-tricks.com/almanac/properties/b/border-radius/"><code>border-radius</code></a> came onto the scene, I remember everybody thinking that it was the best thing ever. Web designs were very square at the time, so to have <code>border-radius</code> was super cool, and it saved us a lot of time, too.</p>



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



<p><a href="https://css-tricks.com/snippets/css/rounded-corners/">Chris’ <code>border-radius</code> article from 2009</a>, which at the time of writing is 16 years old  (wait, how old am <em>I</em>?!), includes vendor prefixes for older web browsers, including “old Konqueror browsers” (<code>-khtml-border-radius</code>). What a time to be alive!</p>



<p>We’re much less excited about rounded corners nowadays. In fact, sharp corners have made a comeback and are just as popular now, as are squircles (square-ish circles or circle-y squares, take your pick), which is exactly what the <code>corner-shape</code> CSS property enables us to create (in addition to many other cool UI effects that I’ll be walking you through today).</p>



<p>At the time of writing, only <a href="https://developer.chrome.com/release-notes/139#corner_shaping_corner-shape_superellipse_squircle" rel="noopener">Chrome 139 and above supports <code>corner-shape</code></a>, which must be used with the <code>border-radius</code> property or/and any of the related individual properties (i.e., <code>border-top-left-radius</code>, <code>border-top-right-radius</code>, <code>border-bottom-right-radius</code>, and <code>border-bottom-left-radius</code>):</p>



<div class="wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper"><iframe id="cp_embed_ByoQgXj" src="//codepen.io/anon/embed/preview/ByoQgXj?height=450&amp;theme-id=1&amp;slug-hash=ByoQgXj&amp;default-tab=result" height="450" scrolling="no" frameborder="0" allowfullscreen allowpaymentrequest name="CodePen Embed ByoQgXj" title="CodePen Embed ByoQgXj" 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="1514" height="698" src="https://i0.wp.com/css-tricks.com/wp-content/uploads/2025/09/s_B9520AAEB8E72143A2990CEB07209D80D9AD692AC1F43B71E4BC9C84D7184887_1757081252810_Screenshot2025-09-05at8.07.10AM.png?resize=1514%2C698&#038;ssl=1" alt="Five vertically-stacked containers in purple comparing the effects of different corner-shape values." class="wp-image-389063" srcset="https://i0.wp.com/css-tricks.com/wp-content/uploads/2025/09/s_B9520AAEB8E72143A2990CEB07209D80D9AD692AC1F43B71E4BC9C84D7184887_1757081252810_Screenshot2025-09-05at8.07.10AM.png?w=1514&amp;ssl=1 1514w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2025/09/s_B9520AAEB8E72143A2990CEB07209D80D9AD692AC1F43B71E4BC9C84D7184887_1757081252810_Screenshot2025-09-05at8.07.10AM.png?resize=300%2C138&amp;ssl=1 300w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2025/09/s_B9520AAEB8E72143A2990CEB07209D80D9AD692AC1F43B71E4BC9C84D7184887_1757081252810_Screenshot2025-09-05at8.07.10AM.png?resize=1024%2C472&amp;ssl=1 1024w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2025/09/s_B9520AAEB8E72143A2990CEB07209D80D9AD692AC1F43B71E4BC9C84D7184887_1757081252810_Screenshot2025-09-05at8.07.10AM.png?resize=768%2C354&amp;ssl=1 768w" sizes="auto, (min-width: 735px) 864px, 96vw" /></figure>


<h3 class="wp-block-heading" id="snipped-corners-using-corner-shape-bevel-">Snipped corners using <code>corner-shape: bevel</code></h3>


<p><a href="https://www.thekernference.com/" rel="noopener">These snipped corners</a> are becoming more and more popular as UI designers embrace <a href="https://css-tricks.com/this-page-is-a-truly-naked-brutalist-html-quine/">brutalist aesthetics</a>.</p>



<figure class="wp-block-image size-full"><img data-recalc-dims="1" loading="lazy" decoding="async" width="712" height="236" src="https://i0.wp.com/css-tricks.com/wp-content/uploads/2025/09/s_B9520AAEB8E72143A2990CEB07209D80D9AD692AC1F43B71E4BC9C84D7184887_1756911883220_Screenshot2025-09-03at9.04.22AM.png?resize=712%2C236&#038;ssl=1" alt="Black button with snipped corners at the upper-left and lower-right that reads ‘Grab your ticket.’" class="wp-image-389064" srcset="https://i0.wp.com/css-tricks.com/wp-content/uploads/2025/09/s_B9520AAEB8E72143A2990CEB07209D80D9AD692AC1F43B71E4BC9C84D7184887_1756911883220_Screenshot2025-09-03at9.04.22AM.png?w=712&amp;ssl=1 712w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2025/09/s_B9520AAEB8E72143A2990CEB07209D80D9AD692AC1F43B71E4BC9C84D7184887_1756911883220_Screenshot2025-09-03at9.04.22AM.png?resize=300%2C99&amp;ssl=1 300w" sizes="auto, (min-width: 735px) 864px, 96vw" /></figure>



<p>In the example above, it’s as easy as using <code>corner-shape: bevel</code> for the snipped corners effect and then <code>border-bottom-right-radius: 16px</code> for the size.</p>



<pre rel="CSS" class="wp-block-csstricks-code-block language-css" data-line=""><code markup="tt">corner-shape: bevel;
border-bottom-right-radius: 16px;</code></pre>



<p>We can do the same thing and it really works with a cyberpunk aesthetic:</p>



<div class="wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper"><iframe id="cp_embed_ByooyQe" src="//codepen.io/anon/embed/preview/ByooyQe?height=450&amp;theme-id=1&amp;slug-hash=ByooyQe&amp;default-tab=result" height="450" scrolling="no" frameborder="0" allowfullscreen allowpaymentrequest name="CodePen Embed ByooyQe" title="CodePen Embed ByooyQe" 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="1766" height="972" src="https://i0.wp.com/css-tricks.com/wp-content/uploads/2025/09/s_B9520AAEB8E72143A2990CEB07209D80D9AD692AC1F43B71E4BC9C84D7184887_1757081374239_Screenshot2025-09-05at8.08.39AM.png?resize=1766%2C972&#038;ssl=1" alt="A rectangular container with a medium bright red border flanked by two tab buttons above it with a beveled bottom-right corner." class="wp-image-389065" srcset="https://i0.wp.com/css-tricks.com/wp-content/uploads/2025/09/s_B9520AAEB8E72143A2990CEB07209D80D9AD692AC1F43B71E4BC9C84D7184887_1757081374239_Screenshot2025-09-05at8.08.39AM.png?w=1766&amp;ssl=1 1766w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2025/09/s_B9520AAEB8E72143A2990CEB07209D80D9AD692AC1F43B71E4BC9C84D7184887_1757081374239_Screenshot2025-09-05at8.08.39AM.png?resize=300%2C165&amp;ssl=1 300w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2025/09/s_B9520AAEB8E72143A2990CEB07209D80D9AD692AC1F43B71E4BC9C84D7184887_1757081374239_Screenshot2025-09-05at8.08.39AM.png?resize=1024%2C564&amp;ssl=1 1024w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2025/09/s_B9520AAEB8E72143A2990CEB07209D80D9AD692AC1F43B71E4BC9C84D7184887_1757081374239_Screenshot2025-09-05at8.08.39AM.png?resize=768%2C423&amp;ssl=1 768w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2025/09/s_B9520AAEB8E72143A2990CEB07209D80D9AD692AC1F43B71E4BC9C84D7184887_1757081374239_Screenshot2025-09-05at8.08.39AM.png?resize=1536%2C845&amp;ssl=1 1536w" sizes="auto, (min-width: 735px) 864px, 96vw" /></figure>


<h3 class="wp-block-heading" id="slanted-sections-using-corner-shape-bevel-">Slanted sections using <code>corner-shape: bevel</code></h3>


<p>Slanted sections is a visual effect that’s even more popular, probably not going anywhere, and again, helps elements to look a lot less like the boxes that they are.</p>



<p>Before we dive in though, it’s important to keep in mind that each border radii has two semi-major axes, a horizontal axis and a vertical axis, with a ‘point’ (to use vector terminology) on each axis. In the example above, both are set to <code>16px</code>, so both points move along their respective axis by that amount, <em>away</em> from their corner of course, and then the beveled line is drawn between them. In the slanted section example below, however, we need to supply a different point value for each axis, like this:</p>



<pre rel="CSS" class="wp-block-csstricks-code-block language-css" data-line=""><code markup="tt">corner-shape: bevel;
border-bottom-right-radius: 100% 50px;</code></pre>



<div class="wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper"><iframe id="cp_embed_EaVVxwV" src="//codepen.io/anon/embed/preview/EaVVxwV?height=450&amp;theme-id=1&amp;slug-hash=EaVVxwV&amp;default-tab=result" height="450" scrolling="no" frameborder="0" allowfullscreen allowpaymentrequest name="CodePen Embed EaVVxwV" title="CodePen Embed EaVVxwV" 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="1310" height="614" src="https://i0.wp.com/css-tricks.com/wp-content/uploads/2025/09/s_B9520AAEB8E72143A2990CEB07209D80D9AD692AC1F43B71E4BC9C84D7184887_1757081541424_Screenshot2025-09-05at8.12.04AM.png?resize=1310%2C614&#038;ssl=1" alt="A large section heading against a solid purple background with white lettering. The container’s bottom-right corner is clipped, giving the container a slanted bottom edge." class="wp-image-389066" srcset="https://i0.wp.com/css-tricks.com/wp-content/uploads/2025/09/s_B9520AAEB8E72143A2990CEB07209D80D9AD692AC1F43B71E4BC9C84D7184887_1757081541424_Screenshot2025-09-05at8.12.04AM.png?w=1310&amp;ssl=1 1310w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2025/09/s_B9520AAEB8E72143A2990CEB07209D80D9AD692AC1F43B71E4BC9C84D7184887_1757081541424_Screenshot2025-09-05at8.12.04AM.png?resize=300%2C141&amp;ssl=1 300w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2025/09/s_B9520AAEB8E72143A2990CEB07209D80D9AD692AC1F43B71E4BC9C84D7184887_1757081541424_Screenshot2025-09-05at8.12.04AM.png?resize=1024%2C480&amp;ssl=1 1024w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2025/09/s_B9520AAEB8E72143A2990CEB07209D80D9AD692AC1F43B71E4BC9C84D7184887_1757081541424_Screenshot2025-09-05at8.12.04AM.png?resize=768%2C360&amp;ssl=1 768w" sizes="auto, (min-width: 735px) 864px, 96vw" /></figure>



<p>The first point moves along <code>100%</code> of the horizontal axis whereas the second point travels <code>50px</code> of the vertical axis, and then the beveled line is drawn between them, creating the slant that you see above.</p>



<p>By the way, having different values for each axis and border radius is exactly how those cool <a href="https://css-tricks.com/css-blob-recipes/#aa-using-border-radius">border radius blobs</a> are made.</p>


<h3 class="wp-block-heading" id="sale-tags-using-corner-shape-round-bevel-bevel-round-">Sale tags using <code>corner-shape: round bevel bevel round</code></h3>


<p>You’ve see those sale tags on almost every e-commerce website, either as images or with rounded corners and not the pointy part (other techniques just aren’t worth the trouble). But now we can carve out the proper shape using two different types of <code>corner-shape</code> at once, as well as a whole set of border radius values:</p>



<div class="wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper"><iframe id="cp_embed_raOONWX" src="//codepen.io/anon/embed/preview/raOONWX?height=450&amp;theme-id=1&amp;slug-hash=raOONWX&amp;default-tab=result" height="450" scrolling="no" frameborder="0" allowfullscreen allowpaymentrequest name="CodePen Embed raOONWX" title="CodePen Embed raOONWX" 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="1048" height="526" src="https://i0.wp.com/css-tricks.com/wp-content/uploads/2025/09/s_B9520AAEB8E72143A2990CEB07209D80D9AD692AC1F43B71E4BC9C84D7184887_1756921169741_Screenshot2025-09-03at11.39.03AM.png?resize=1048%2C526&#038;ssl=1" alt="Red rectangular box with rounded corners on the left and beveled corners on the right forming an arrow shape with the label ‘Sale’ in white." class="wp-image-389067" srcset="https://i0.wp.com/css-tricks.com/wp-content/uploads/2025/09/s_B9520AAEB8E72143A2990CEB07209D80D9AD692AC1F43B71E4BC9C84D7184887_1756921169741_Screenshot2025-09-03at11.39.03AM.png?w=1048&amp;ssl=1 1048w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2025/09/s_B9520AAEB8E72143A2990CEB07209D80D9AD692AC1F43B71E4BC9C84D7184887_1756921169741_Screenshot2025-09-03at11.39.03AM.png?resize=300%2C151&amp;ssl=1 300w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2025/09/s_B9520AAEB8E72143A2990CEB07209D80D9AD692AC1F43B71E4BC9C84D7184887_1756921169741_Screenshot2025-09-03at11.39.03AM.png?resize=1024%2C514&amp;ssl=1 1024w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2025/09/s_B9520AAEB8E72143A2990CEB07209D80D9AD692AC1F43B71E4BC9C84D7184887_1756921169741_Screenshot2025-09-03at11.39.03AM.png?resize=768%2C385&amp;ssl=1 768w" sizes="auto, (min-width: 735px) 864px, 96vw" /></figure>



<p>You’ll need <code>corner-shape: round bevel bevel round</code> to start off. The order flows clockwise, starting from the top-left, as follows:</p>



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



<li>top-right</li>



<li>bottom-right</li>



<li>bottom-left</li>
</ul>



<p>Just like with <code>border-radius</code>. You <em>can</em> omit some values, causing them to be inferred from other values, but both the inference logic and resulting value syntax lack clarity, so I’d just avoid this, especially since we’re about to explore a more complex <code>border-radius</code>:</p>



<pre rel="CSS" class="wp-block-csstricks-code-block language-css" data-line=""><code markup="tt">corner-shape: round bevel bevel round;
border-radius: 16px 48px 48px 16px / 16px 50% 50% 16px;</code></pre>



<p>Left of the forward slash (<code>/</code>) we have the horizontal-axis values of each corner in the order mentioned above, and on the right of the <code>/</code>, the vertical-axis values. So, to be clear, the first and fifth values correspond to the same corner, as do the second and sixth, and so on. You can unpack the shorthand if it’s easier to read:</p>



<pre rel="CSS" class="wp-block-csstricks-code-block language-css" data-line=""><code markup="tt">border-top-left-radius: 16px;
border-top-right-radius: 48px 50%;
border-bottom-right-radius: 48px 50%;
border-bottom-left-radius: 16px;</code></pre>



<p>Up until now, we’ve not really needed to fully understand the border radius syntax. But now that we have <code>corner-shape</code>, it’s definitely worth doing so.</p>



<p>As for the actual values, <code>16px</code> corresponds to the <code>round</code> corners (this one’s easy to understand) while the <code>48px 50%</code> values are for the <code>bevel</code> ones, meaning that the corners are ‘drawn’ from <code>48px</code> horizontally to <code>50%</code> vertically, which is why and how they head into a point.</p>



<p>Regarding borders — yes, the pointy parts would look nicer if they were <em>slightly</em> rounded, but using <code>border</code>s and <code>outline</code>s on these elements yields unpredictable (but I suspect intended) results due to how browsers draw the corners, which <em>sucks</em>.</p>


<h3 class="wp-block-heading" id="arrow-crumbs-using-the-same-method">Arrow crumbs using the same method</h3>


<p>Yep, same thing.</p>



<div class="wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper"><iframe id="cp_embed_GgpgQNN" src="//codepen.io/anon/embed/preview/GgpgQNN?height=450&amp;theme-id=1&amp;slug-hash=GgpgQNN&amp;default-tab=result" height="450" scrolling="no" frameborder="0" allowfullscreen allowpaymentrequest name="CodePen Embed GgpgQNN" title="CodePen Embed GgpgQNN" 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="1446" height="390" src="https://i0.wp.com/css-tricks.com/wp-content/uploads/2025/09/s_B9520AAEB8E72143A2990CEB07209D80D9AD692AC1F43B71E4BC9C84D7184887_1756921412654_Screenshot2025-09-03at11.43.19AM.png?resize=1446%2C390&#038;ssl=1" alt="A rounded rectangular box in three purple arrow-shaped segments pointing towards the right. Each segment is a breadcrumb, labeled Step 1, Step 2, and Step 3 in white. The first segment is a darker shade of purple." class="wp-image-389068" srcset="https://i0.wp.com/css-tricks.com/wp-content/uploads/2025/09/s_B9520AAEB8E72143A2990CEB07209D80D9AD692AC1F43B71E4BC9C84D7184887_1756921412654_Screenshot2025-09-03at11.43.19AM.png?w=1446&amp;ssl=1 1446w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2025/09/s_B9520AAEB8E72143A2990CEB07209D80D9AD692AC1F43B71E4BC9C84D7184887_1756921412654_Screenshot2025-09-03at11.43.19AM.png?resize=300%2C81&amp;ssl=1 300w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2025/09/s_B9520AAEB8E72143A2990CEB07209D80D9AD692AC1F43B71E4BC9C84D7184887_1756921412654_Screenshot2025-09-03at11.43.19AM.png?resize=1024%2C276&amp;ssl=1 1024w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2025/09/s_B9520AAEB8E72143A2990CEB07209D80D9AD692AC1F43B71E4BC9C84D7184887_1756921412654_Screenshot2025-09-03at11.43.19AM.png?resize=768%2C207&amp;ssl=1 768w" sizes="auto, (min-width: 735px) 864px, 96vw" /></figure>



<p>We essentially have a grid row with negative margins, but because we can’t create ‘inset’ arrows or use borders/outlines, we have to create an effect where the fake borders of certain arrows bleed into the next. This is done by nesting the exact same shape in the arrows and then applying something to the effect of <code>padding-right: 3px</code>, where <code>3px</code> is the value of the would-be border. The code comments below should explain it in more detail (the complete code in <a href="https://codepen.io/mrdanielschwarz/pen/GgpgQNN" rel="noopener">the Pen</a> is quite interesting, though):</p>



<pre rel="HTML" class="wp-block-csstricks-code-block language-markup" data-line=""><code markup="tt">&lt;nav>
  &lt;ol>
    &lt;li>
      &lt;a>Step 1&lt;/a>
    &lt;/li>
    &lt;li>
      &lt;a>Step 2&lt;/a>
    &lt;/li>
    &lt;li>
      &lt;a>Step 3&lt;/a>
    &lt;/li>
  &lt;/ol>
&lt;/nav></code></pre>



<pre rel="CSS" class="wp-block-csstricks-code-block language-css" data-line=""><code markup="tt">ol {
  /* Clip n’ round */
  overflow: clip;
  border-radius: 16px;

  li {
    /* Arrow color */
    background: hsl(270 100% 30%);

    /* Reverses the z-indexes, making the arrows stack */
    /* Result: 2, 1, 0, ... (sibling-x requires Chrome 138+) */
    z-index: calc((sibling-index() * -1) + sibling-count());

    &amp;:not(:last-child) {
      /* Arrow width */
      padding-right: 3px;

      /* Arrow shape */
      corner-shape: bevel;
      border-radius: 0 32px 32px 0 / 0 50% 50% 0;

      /* Pull the next one into this one */
      margin-right: -32px;

    }

    a {
      /* Same shape */
      corner-shape: inherit;
      border-radius: inherit;

      /* Overlay background */
      background: hsl(270 100% 50%);
    }
  }
}</code></pre>


<h3 class="wp-block-heading" id="tooltips-using-corner-shape-scoop-">Tooltips using <code>corner-shape: scoop</code></h3>


<div class="wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper"><iframe id="cp_embed_LEpmMWg" src="//codepen.io/anon/embed/preview/LEpmMWg?height=450&amp;theme-id=1&amp;slug-hash=LEpmMWg&amp;default-tab=result" height="450" scrolling="no" frameborder="0" allowfullscreen allowpaymentrequest name="CodePen Embed LEpmMWg" title="CodePen Embed LEpmMWg" 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="1420" height="574" src="https://i0.wp.com/css-tricks.com/wp-content/uploads/2025/09/s_B9520AAEB8E72143A2990CEB07209D80D9AD692AC1F43B71E4BC9C84D7184887_1756921702744_Screenshot2025-09-03at11.47.59AM.png?resize=1420%2C574&#038;ssl=1" alt="Small purple button with white text and a red outline next to a red tooltip with white text floated to the right and a styled caret tip on the left side making it connected to the button." class="wp-image-389069" srcset="https://i0.wp.com/css-tricks.com/wp-content/uploads/2025/09/s_B9520AAEB8E72143A2990CEB07209D80D9AD692AC1F43B71E4BC9C84D7184887_1756921702744_Screenshot2025-09-03at11.47.59AM.png?w=1420&amp;ssl=1 1420w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2025/09/s_B9520AAEB8E72143A2990CEB07209D80D9AD692AC1F43B71E4BC9C84D7184887_1756921702744_Screenshot2025-09-03at11.47.59AM.png?resize=300%2C121&amp;ssl=1 300w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2025/09/s_B9520AAEB8E72143A2990CEB07209D80D9AD692AC1F43B71E4BC9C84D7184887_1756921702744_Screenshot2025-09-03at11.47.59AM.png?resize=1024%2C414&amp;ssl=1 1024w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2025/09/s_B9520AAEB8E72143A2990CEB07209D80D9AD692AC1F43B71E4BC9C84D7184887_1756921702744_Screenshot2025-09-03at11.47.59AM.png?resize=768%2C310&amp;ssl=1 768w" sizes="auto, (min-width: 735px) 864px, 96vw" /></figure>



<p>To create this tooltip style, I’ve used a <a href="https://css-tricks.com/clarifying-the-relationship-between-popovers-and-dialogs/">popover</a>, <a href="https://css-tricks.com/css-anchor-positioning-guide/">anchor positioning</a> (to position the caret relative to the tooltip), and <code>corner-shape: scoop</code>. The caret shape is the same as the arrow shape used in the examples above, so feel free to switch <code>scoop</code> to <code>bevel</code> if you prefer the classic triangle tooltips.</p>



<p>A quick walkthrough:</p>



<pre rel="HTML" class="wp-block-csstricks-code-block language-markup" data-line=""><code markup="tt">&lt;!-- Connect button to tooltip -->
&lt;button popovertarget="tooltip" id="button">Click for tip&lt;/button>

&lt;!-- Anchor tooltip to button -->
&lt;div anchor="button" id="tooltip" popover>Don’t eat yellow snow&lt;/div></code></pre>



<pre rel="CSS" class="wp-block-csstricks-code-block language-css" data-line=""><code markup="tt">#tooltip {
  /* Define anchor */
  anchor-name: --tooltip;

  /* Necessary reset */
  margin: 0;

  /* Center vertically */
  align-self: anchor-center;

  /* Pin to right side + 15 */
  left: calc(anchor(right) + 15px);

  &amp;::after {
    /* Create caret */
    content: "";
    width: 5px;
    height: 10px;
    corner-shape: scoop;
    border-top-left-radius: 100% 50%;
    border-bottom-left-radius: 100% 50%;

    /* Anchor to tooltip */
    position-anchor: --tooltip;

    /* Center vertically */
    align-self: anchor-center;

    /* Pin to left side */
    right: anchor(left);

    /* Popovers have this already (required otherwise) */
    position: fixed;
  }
}</code></pre>



<p>If you’d rather these were hover-triggered, the upcoming <a href="https://css-tricks.com/a-first-look-at-the-interest-invoker-api-for-hover-triggered-popovers/">Interest Invoker API</a> is what you’re looking for.</p>


<h3 class="wp-block-heading" id="realistic-highlighting-using-corner-shape-squircle-bevel-">Realistic highlighting using <code>corner-shape: squircle bevel</code></h3>


<p>The <code>&lt;mark&gt;</code> element, used for semantic highlighting, defaults with a yellow background, but it doesn’t exactly create a highlighter effect. By adding the following two lines of CSS, which admittedly I discovered by experimenting with completely random values, we can make it look more like a hand-waved highlight:</p>



<pre rel="CSS" class="wp-block-csstricks-code-block language-css" data-line=""><code markup="tt">mark {
  /* A...squevel? */
  corner-shape: squircle bevel;
  border-radius: 50% / 1.1rem 0.5rem 0.9rem 0.7rem;

  /* Prevents background-break when wrapping */
  box-decoration-break: clone;
}</code></pre>



<div class="wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper"><iframe id="cp_embed_raOVOXj" src="//codepen.io/anon/embed/preview/raOVOXj?height=450&amp;theme-id=1&amp;slug-hash=raOVOXj&amp;default-tab=result" height="450" scrolling="no" frameborder="0" allowfullscreen allowpaymentrequest name="CodePen Embed raOVOXj" title="CodePen Embed raOVOXj" 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="1222" height="474" src="https://i0.wp.com/css-tricks.com/wp-content/uploads/2025/09/s_B9520AAEB8E72143A2990CEB07209D80D9AD692AC1F43B71E4BC9C84D7184887_1756921874103_Screenshot2025-09-03at11.50.58AM.png?resize=1222%2C474&#038;ssl=1" alt="Text reading ‘Highlighted text’ in black against a yellow background containing no sharp edges." class="wp-image-389071" srcset="https://i0.wp.com/css-tricks.com/wp-content/uploads/2025/09/s_B9520AAEB8E72143A2990CEB07209D80D9AD692AC1F43B71E4BC9C84D7184887_1756921874103_Screenshot2025-09-03at11.50.58AM.png?w=1222&amp;ssl=1 1222w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2025/09/s_B9520AAEB8E72143A2990CEB07209D80D9AD692AC1F43B71E4BC9C84D7184887_1756921874103_Screenshot2025-09-03at11.50.58AM.png?resize=300%2C116&amp;ssl=1 300w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2025/09/s_B9520AAEB8E72143A2990CEB07209D80D9AD692AC1F43B71E4BC9C84D7184887_1756921874103_Screenshot2025-09-03at11.50.58AM.png?resize=1024%2C397&amp;ssl=1 1024w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2025/09/s_B9520AAEB8E72143A2990CEB07209D80D9AD692AC1F43B71E4BC9C84D7184887_1756921874103_Screenshot2025-09-03at11.50.58AM.png?resize=768%2C298&amp;ssl=1 768w" sizes="auto, (min-width: 735px) 864px, 96vw" /></figure>



<p>We can also use <code>squircle</code> by itself to create those fancy-rounded app icons, or use them on buttons/cards/form controls/etc. if you think the ‘old’ border radius is starting to look a bit stale:</p>



<div class="wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper"><iframe id="cp_embed_gbaYELq" src="//codepen.io/anon/embed/preview/gbaYELq?height=450&amp;theme-id=1&amp;slug-hash=gbaYELq&amp;default-tab=result" height="450" scrolling="no" frameborder="0" allowfullscreen allowpaymentrequest name="CodePen Embed gbaYELq" title="CodePen Embed gbaYELq" 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="1404" height="586" src="https://i0.wp.com/css-tricks.com/wp-content/uploads/2025/09/s_B9520AAEB8E72143A2990CEB07209D80D9AD692AC1F43B71E4BC9C84D7184887_1756921975678_Screenshot2025-09-03at11.52.40AM.png?resize=1404%2C586&#038;ssl=1" alt="Squircle shaped box filled with a linear gradient that goes from orange to blue with white text on top that says ‘CSS’." class="wp-image-389072" srcset="https://i0.wp.com/css-tricks.com/wp-content/uploads/2025/09/s_B9520AAEB8E72143A2990CEB07209D80D9AD692AC1F43B71E4BC9C84D7184887_1756921975678_Screenshot2025-09-03at11.52.40AM.png?w=1404&amp;ssl=1 1404w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2025/09/s_B9520AAEB8E72143A2990CEB07209D80D9AD692AC1F43B71E4BC9C84D7184887_1756921975678_Screenshot2025-09-03at11.52.40AM.png?resize=300%2C125&amp;ssl=1 300w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2025/09/s_B9520AAEB8E72143A2990CEB07209D80D9AD692AC1F43B71E4BC9C84D7184887_1756921975678_Screenshot2025-09-03at11.52.40AM.png?resize=1024%2C427&amp;ssl=1 1024w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2025/09/s_B9520AAEB8E72143A2990CEB07209D80D9AD692AC1F43B71E4BC9C84D7184887_1756921975678_Screenshot2025-09-03at11.52.40AM.png?resize=768%2C321&amp;ssl=1 768w" sizes="auto, (min-width: 735px) 864px, 96vw" /></figure>



<div class="wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper"><iframe id="cp_embed_zxvRaGY" src="//codepen.io/anon/embed/preview/zxvRaGY?height=450&amp;theme-id=1&amp;slug-hash=zxvRaGY&amp;default-tab=result" height="450" scrolling="no" frameborder="0" allowfullscreen allowpaymentrequest name="CodePen Embed zxvRaGY" title="CodePen Embed zxvRaGY" 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="1110" height="428" src="https://i0.wp.com/css-tricks.com/wp-content/uploads/2025/09/s_B9520AAEB8E72143A2990CEB07209D80D9AD692AC1F43B71E4BC9C84D7184887_1756922082526_Screenshot2025-09-03at11.54.28AM.png?resize=1110%2C428&#038;ssl=1" alt="Squircle-shaped purple button with a white label that says ‘Button.’" class="wp-image-389073" srcset="https://i0.wp.com/css-tricks.com/wp-content/uploads/2025/09/s_B9520AAEB8E72143A2990CEB07209D80D9AD692AC1F43B71E4BC9C84D7184887_1756922082526_Screenshot2025-09-03at11.54.28AM.png?w=1110&amp;ssl=1 1110w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2025/09/s_B9520AAEB8E72143A2990CEB07209D80D9AD692AC1F43B71E4BC9C84D7184887_1756922082526_Screenshot2025-09-03at11.54.28AM.png?resize=300%2C116&amp;ssl=1 300w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2025/09/s_B9520AAEB8E72143A2990CEB07209D80D9AD692AC1F43B71E4BC9C84D7184887_1756922082526_Screenshot2025-09-03at11.54.28AM.png?resize=1024%2C395&amp;ssl=1 1024w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2025/09/s_B9520AAEB8E72143A2990CEB07209D80D9AD692AC1F43B71E4BC9C84D7184887_1756922082526_Screenshot2025-09-03at11.54.28AM.png?resize=768%2C296&amp;ssl=1 768w" sizes="auto, (min-width: 735px) 864px, 96vw" /></figure>


<h3 class="wp-block-heading" id="hand-drawn-boxes-using-the-same-method">Hand-drawn boxes using the same method</h3>


<p>Same thing, only larger. Kind of looks like a hand-drawn box?</p>



<div class="wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper"><iframe id="cp_embed_azvzXjo" src="//codepen.io/anon/embed/preview/azvzXjo?height=450&amp;theme-id=1&amp;slug-hash=azvzXjo&amp;default-tab=result" height="450" scrolling="no" frameborder="0" allowfullscreen allowpaymentrequest name="CodePen Embed azvzXjo" title="CodePen Embed azvzXjo" 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="1794" height="768" src="https://i0.wp.com/css-tricks.com/wp-content/uploads/2025/09/s_B9520AAEB8E72143A2990CEB07209D80D9AD692AC1F43B71E4BC9C84D7184887_1756922141714_Screenshot2025-09-03at11.55.29AM.png?resize=1794%2C768&#038;ssl=1" alt="Solid white rectangular box with thick, black borders that look hand-drawn." class="wp-image-389075" srcset="https://i0.wp.com/css-tricks.com/wp-content/uploads/2025/09/s_B9520AAEB8E72143A2990CEB07209D80D9AD692AC1F43B71E4BC9C84D7184887_1756922141714_Screenshot2025-09-03at11.55.29AM.png?w=1794&amp;ssl=1 1794w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2025/09/s_B9520AAEB8E72143A2990CEB07209D80D9AD692AC1F43B71E4BC9C84D7184887_1756922141714_Screenshot2025-09-03at11.55.29AM.png?resize=300%2C128&amp;ssl=1 300w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2025/09/s_B9520AAEB8E72143A2990CEB07209D80D9AD692AC1F43B71E4BC9C84D7184887_1756922141714_Screenshot2025-09-03at11.55.29AM.png?resize=1024%2C438&amp;ssl=1 1024w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2025/09/s_B9520AAEB8E72143A2990CEB07209D80D9AD692AC1F43B71E4BC9C84D7184887_1756922141714_Screenshot2025-09-03at11.55.29AM.png?resize=768%2C329&amp;ssl=1 768w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2025/09/s_B9520AAEB8E72143A2990CEB07209D80D9AD692AC1F43B71E4BC9C84D7184887_1756922141714_Screenshot2025-09-03at11.55.29AM.png?resize=1536%2C658&amp;ssl=1 1536w" sizes="auto, (min-width: 735px) 864px, 96vw" /></figure>



<p>Admittedly, this effect doesn’t look as awesome on a larger scale, so if you’re really looking to wow and create something more akin to the <a href="https://www.rockstargames.com/reddeadredemption2" rel="noopener">Red Dead Redemption aesthetic</a>, <a href="https://css-tricks.com/revisiting-css-border-image/">this <code>border-image</code> approach</a> would be better.</p>


<h3 class="wp-block-heading" id="clip-a-background-with-corner-shape-notch-">Clip a background with <code>corner-shape: notch</code></h3>


<p>Notched border radii are ugly and I won’t hear otherwise. I don’t think you’ll want to use them to create a visual effect, but I’ve learned that they’re useful for background clipping if you set the irrelevant axis to <code>50%</code> and the axis of the side that you want to clip by the amount that you want to clip it by. So if you wanted to clip <code>30px</code> off the background from the left for example, you’d choose <code>30px</code> for the horizontal axes and <code>50%</code> for the vertical axes (for the <code>-left-radius</code> properties only, of course).</p>



<pre rel="CSS" class="wp-block-csstricks-code-block language-css" data-line=""><code markup="tt">corner-shape: notch;
border-top-left-radius: 30px 50%;
border-bottom-left-radius: 30px 50%;</code></pre>



<div class="wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper"><iframe id="cp_embed_jEbbONp" src="//codepen.io/anon/embed/preview/jEbbONp?height=450&amp;theme-id=1&amp;slug-hash=jEbbONp&amp;default-tab=result" height="450" scrolling="no" frameborder="0" allowfullscreen allowpaymentrequest name="CodePen Embed jEbbONp" title="CodePen Embed jEbbONp" 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="1746" height="474" src="https://i0.wp.com/css-tricks.com/wp-content/uploads/2025/09/s_B9520AAEB8E72143A2990CEB07209D80D9AD692AC1F43B71E4BC9C84D7184887_1756922213211_Screenshot2025-09-03at11.56.33AM.png?resize=1746%2C474&#038;ssl=1" alt="The words ‘Clipped background’ in bold black letters with a thinly-bordered rectangle that nearly covers the text." class="wp-image-389077" srcset="https://i0.wp.com/css-tricks.com/wp-content/uploads/2025/09/s_B9520AAEB8E72143A2990CEB07209D80D9AD692AC1F43B71E4BC9C84D7184887_1756922213211_Screenshot2025-09-03at11.56.33AM.png?w=1746&amp;ssl=1 1746w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2025/09/s_B9520AAEB8E72143A2990CEB07209D80D9AD692AC1F43B71E4BC9C84D7184887_1756922213211_Screenshot2025-09-03at11.56.33AM.png?resize=300%2C81&amp;ssl=1 300w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2025/09/s_B9520AAEB8E72143A2990CEB07209D80D9AD692AC1F43B71E4BC9C84D7184887_1756922213211_Screenshot2025-09-03at11.56.33AM.png?resize=1024%2C278&amp;ssl=1 1024w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2025/09/s_B9520AAEB8E72143A2990CEB07209D80D9AD692AC1F43B71E4BC9C84D7184887_1756922213211_Screenshot2025-09-03at11.56.33AM.png?resize=768%2C208&amp;ssl=1 768w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2025/09/s_B9520AAEB8E72143A2990CEB07209D80D9AD692AC1F43B71E4BC9C84D7184887_1756922213211_Screenshot2025-09-03at11.56.33AM.png?resize=1536%2C417&amp;ssl=1 1536w" sizes="auto, (min-width: 735px) 864px, 96vw" /></figure>


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


<p>So, <code>corner-shape</code> is actually a helluva lot of fun. It certainly has more uses than I expected, and no doubt with some experimentation you’ll come up with some more. With that in mind, I’ll leave it to you CSS-Tricksters to mess around with (remember though, you’ll need to be using Chrome 139 or higher).</p>



<p>As a parting gift, I leave you with this very cool but completely useless CSS Tie Fighter, made with <code>corner-shape</code> and anchor positioning:</p>



<div class="wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper"><iframe id="cp_embed_wBKKweg" src="//codepen.io/anon/embed/preview/wBKKweg?height=450&amp;theme-id=1&amp;slug-hash=wBKKweg&amp;default-tab=result" height="450" scrolling="no" frameborder="0" allowfullscreen allowpaymentrequest name="CodePen Embed wBKKweg" title="CodePen Embed wBKKweg" 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="1474" height="796" src="https://i0.wp.com/css-tricks.com/wp-content/uploads/2025/09/s_B9520AAEB8E72143A2990CEB07209D80D9AD692AC1F43B71E4BC9C84D7184887_1756922381535_Screenshot2025-09-03at11.59.17AM.png?resize=1474%2C796&#038;ssl=1" alt="Hexagon shape with six black segments forming the shape, separated by gaps of gray space. The negative space in the middle forms another hexagon." class="wp-image-389079" srcset="https://i0.wp.com/css-tricks.com/wp-content/uploads/2025/09/s_B9520AAEB8E72143A2990CEB07209D80D9AD692AC1F43B71E4BC9C84D7184887_1756922381535_Screenshot2025-09-03at11.59.17AM.png?w=1474&amp;ssl=1 1474w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2025/09/s_B9520AAEB8E72143A2990CEB07209D80D9AD692AC1F43B71E4BC9C84D7184887_1756922381535_Screenshot2025-09-03at11.59.17AM.png?resize=300%2C162&amp;ssl=1 300w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2025/09/s_B9520AAEB8E72143A2990CEB07209D80D9AD692AC1F43B71E4BC9C84D7184887_1756922381535_Screenshot2025-09-03at11.59.17AM.png?resize=1024%2C553&amp;ssl=1 1024w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2025/09/s_B9520AAEB8E72143A2990CEB07209D80D9AD692AC1F43B71E4BC9C84D7184887_1756922381535_Screenshot2025-09-03at11.59.17AM.png?resize=768%2C415&amp;ssl=1 768w" sizes="auto, (min-width: 735px) 864px, 96vw" /></figure>
<hr />
<p><small><a rel="nofollow" href="https://css-tricks.com/what-can-we-actually-do-with-corner-shape/">What Can We Actually Do With corner-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/what-can-we-actually-do-with-corner-shape/feed/</wfw:commentRss>
			<slash:comments>9</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">389060</post-id>	</item>
		<item>
		<title>Compiling Multiple CSS Files into One</title>
		<link>https://css-tricks.com/compiling-multiple-css-files-into-one/</link>
					<comments>https://css-tricks.com/compiling-multiple-css-files-into-one/#comments</comments>
		
		<dc:creator><![CDATA[Geoff Graham]]></dc:creator>
		<pubDate>Thu, 11 Sep 2025 15:16:34 +0000</pubDate>
				<category><![CDATA[Links]]></category>
		<category><![CDATA[css preprocessors]]></category>
		<category><![CDATA[node]]></category>
		<category><![CDATA[postcss]]></category>
		<category><![CDATA[Sass]]></category>
		<guid isPermaLink="false">https://css-tricks.com/?p=389117</guid>

					<description><![CDATA[<p>Stu Robson outlines two ways to compile multiple CSS files when you aren't relying on Sass for it.</p>
<hr />
<p><small><a rel="nofollow" href="https://css-tricks.com/compiling-multiple-css-files-into-one/">Compiling Multiple CSS Files into One</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://www.alwaystwisted.com" rel="noopener">Stu Robson</a> is on a mission to <a href="https://www.alwaystwisted.com/articles/UnSassing-my-CSS" rel="noopener">&#8220;un-Sass&#8221; his CSS</a>. I see articles like this pop up every year, and for good reason as CSS has grown so many new legs in recent years. So much so that much of the core features that may have prompted you to reach for Sass in the past are now baked directly into CSS. In fact, we have <a href="https://jeffbridgforth.com" rel="noopener">Jeff Bridgforth</a> on tap with a related article next week.</p>



<p>What I like about Stu&#8217;s stab at this is that it&#8217;s an ongoing journey rather than a wholesale switch. In fact, he&#8217;s out with a new post that <a href="https://www.alwaystwisted.com/articles/UnSassing-my-CSS-CSS-imports" rel="noopener">pokes specifically at compiling multiple CSS files into a single file</a>. Splitting and organizing styles into separate files is definitely the reason I continue to Sass-ify my work. I love being able to find exactly what I need in a specific file and updating it without having to dig through a monolith of style rules.</p>



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



<p>But is that a real reason to keep using Sass? I&#8217;ve honestly never questioned it, perhaps due to a lizard brain that doesn&#8217;t care as long as something continues to work. <em>Oh, I want partialized style files? Always done that with a Sass-y toolchain that hasn&#8217;t let me down yet.</em> I know, not the most proactive path.</p>



<p>Stu outlines two ways to compile multiple CSS files when you aren&#8217;t relying on Sass for it:</p>


<h3 class="wp-block-heading" id="using-postcss">Using PostCSS</h3>


<p>Ah, that&#8217;s right, we can use <a href="https://postcss.org" rel="noopener">PostCSS</a> both with <em>and</em> without Sass. It&#8217;s easy to forget that PostCSS and Sass are compatible, but not dependent on one another.</p>



<pre rel="Terminal" class="wp-block-csstricks-code-block language-none" data-line=""><code markup="tt">postcss main.css -o output.css</code></pre>



<p>Stu explains why this could be a nice way to toe-dip into un-Sass&#8217;ing your work:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p>PostCSS can seamlessly integrate with popular build tools like webpack, Gulp, and Rollup, allowing you to incorporate CSS compilation into your existing development workflow without potential, additional configuration headaches.</p>
</blockquote>


<h3 class="wp-block-heading" id="custom-script-for-compilation">Custom Script for Compilation</h3>


<p>The ultimate thing would be eliminating the need for any dependencies. Stu has a custom Node.js script for that:</p>



<pre rel="JavaScript" class="wp-block-csstricks-code-block language-javascript" data-line=""><code markup="tt">const fs = require('fs');
const path = require('path');
// Function to read and compile CSS
function compileCSS(inputFile, outputFile) {
    const cssContent = fs.readFileSync(inputFile, 'utf-8');
    const imports = cssContent.match(/@import\s+['"]([^'"]+)['"]/g) || [];
    let compiledCSS = '';
    // Read and append each imported CSS file
    imports.forEach(importStatement => {
        const filePath = importStatement.match(/['"]([^'"]+)['"]/)[1];
        const fullPath = path.resolve(path.dirname(inputFile), filePath);
        compiledCSS += fs.readFileSync(fullPath, 'utf-8') + '\n';
    });
    // Write the compiled CSS to the output file
    fs.writeFileSync(outputFile, compiledCSS.trim());
    console.log(`Compiled CSS written to ${outputFile}`);
}
// Usage
const inputCSSFile = 'index.css'; // Your main CSS file
const outputCSSFile = 'output.css'; // Output file
compileCSS(inputCSSFile, outputCSSFile);</code></pre>



<p>Not 100% free of dependencies, but geez, what a nice way to reduce the overhead and still combine files:</p>



<pre rel="Terminal" class="wp-block-csstricks-code-block language-none" data-line=""><code markup="tt">node compile-css.js</code></pre>



<p>This approach is designed for a flat file directory. If you&#8217;re like me and prefer nested subfolders:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p>With the flat file structure and single-level import strategy I employ, nested imports (you can do with&nbsp;<code>postcss-import</code>&nbsp;aren&#8217;t necessary for my project setup, simplifying the compilation process while maintaining clean organisation.</p>
</blockquote>



<p>Very cool, thanks Stu! And check out the <a href="https://www.alwaystwisted.com/articles/UnSassing-my-CSS" rel="noopener">full post</a> because there&#8217;s a lot of helpful context behind this, particularly with the custom script.</p>
<hr />
<p><small><a rel="nofollow" href="https://css-tricks.com/compiling-multiple-css-files-into-one/">Compiling Multiple CSS Files into One</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/compiling-multiple-css-files-into-one/feed/</wfw:commentRss>
			<slash:comments>9</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">389117</post-id>	</item>
		<item>
		<title>What&#8217;re Your Top 4 CSS Properties?</title>
		<link>https://css-tricks.com/whatre-your-top-4-css-properties/</link>
					<comments>https://css-tricks.com/whatre-your-top-4-css-properties/#comments</comments>
		
		<dc:creator><![CDATA[Geoff Graham]]></dc:creator>
		<pubDate>Wed, 10 Sep 2025 13:13:41 +0000</pubDate>
				<category><![CDATA[Articles]]></category>
		<category><![CDATA[css properties]]></category>
		<category><![CDATA[opinion]]></category>
		<guid isPermaLink="false">https://css-tricks.com/?p=388800</guid>

					<description><![CDATA[<p>Everyone has a different opinion which is great because it demonstrates the messy, non-linear craft that is thinking like a front-end developer.</p>
<hr />
<p><small><a rel="nofollow" href="https://css-tricks.com/whatre-your-top-4-css-properties/">What&#8217;re Your Top 4 CSS Properties?</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>That&#8217;s what <a href="https://blog.damato.design/posts/my-top-4/" rel="noopener">Donnie D&#8217;Amato asks in a recent post</a>:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p>You are asked to build a website but you can use only 4 CSS properties, what are those?</p>
</blockquote>



<p>This really got the CSS-Tricks team talking. It&#8217;s the nerdy version of &#8220;if you could only take one album with you on a remote island&#8230;&#8221; And everyone had a different opinion which is great because it demonstrates the messy, non-linear craft that is <a href="https://css-tricks.com/how-to-think-like-a-front-end-developer/">thinking like a front-end developer</a>.</p>



<p>Seems like a pretty straightforward thing to answer, right? But like Donnie says, this takes some strategy. Like, say spacing is high on your priority list. Are you going to use <code>margin</code>? <code>padding?</code> Perhaps you&#8217;re leaning into layout and go with <code>gap</code> as part of a flexbox direction&#8230; but then you&#8217;re committing to <code>display</code> as one of your options. That can quickly eat up your choices!</p>



<p>Our answers are pretty consistent, but converged even more as the discussion wore on and all of us were coming at it with different priorities. I&#8217;ll share each person&#8217;s &#8220;gut&#8221; reaction because I like how raw it is. I think you&#8217;ll see that there&#8217;s always a compromise in the mix, but those compromises really reveal a person&#8217;s cards as far as what they think is most important in a situation with overly tight constraints.</p>



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



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


<h3 class="wp-block-heading" id="juan-diego-rodriguez">Juan Diego Rodriguez</h3>


<p>Juan and I came out pretty close to the same choices, as we&#8217;ll see in a bit:</p>



<ul class="wp-block-list is-style-almanac-list">
<li><strong><code>font</code>:</strong> Typography is a priority and we get a lot of constituent properties with this single shorthand.</li>



<li><strong><code>padding</code>:</strong> A little padding makes things breath and helps with visual separation.</li>



<li><strong><code>background</code>:</strong> Another shorthand with lots of styling possibilities in a tiny package.</li>



<li><strong><code>color</code>:</strong> More visual hierarchy.</li>
</ul>



<p>But he was debating with himself a bit in the process:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p>Thinking about switching&nbsp;<code>color</code>&nbsp;with&nbsp;<code>place-items</code>, since it works in block elements. <code>grid</code>&nbsp;would need&nbsp;<code>display</code>, though).</p>
</blockquote>


<h3 class="wp-block-heading" id="ryan-trimble">Ryan Trimble</h3>


<p>Ryan&#8217;s all about that <s>bass</s> structure:</p>



<ul class="wp-block-list is-style-almanac-list">
<li><strong><code>display</code>:</strong> This opens up a world of layouts, but most importantly <code>flex</code>.</li>



<li><strong><code>flex-direction</code>:</strong> It&#8217;s a good idea to consider multi-directional layouts that are easily adjustable with media queries.</li>



<li><strong><code>width</code>:</strong> This helps constrain elements and text, as well as divide up flex containers.</li>



<li><strong><code>margin</code>:</strong> This is for spacing that&#8217;s bit more versatile than <code>gap</code>, while also allowing us to center elements easily.</li>
</ul>



<p>And Ryan couldn&#8217;t resist reaching a little out of bounds:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p>For automatic color theme support, and no extra CSS properties required: <code>&lt;meta name="color-scheme" content="dark light"&gt;</code>&nbsp;</p>
</blockquote>


<h3 class="wp-block-heading" id="danny-schwarz">Danny Schwarz</h3>


<p>Every team needs a wild card:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p>On the contrary I think I&#8217;d choose&nbsp;<code>font</code>,&nbsp;<code>padding</code>, and&nbsp;<code>color</code>. I wouldn&#8217;t even choose a 4th.</p>
</blockquote>



<ul class="wp-block-list is-style-almanac-list">
<li><strong><code>font</code>:</strong> This isn&#8217;t a big surprise if you&#8217;re familiar with <a href="https://css-tricks.com/author/danielschwarz/">Danny&#8217;s writing</a>.</li>



<li><strong><code>padding</code>:</strong> So far, Ryan&#8217;s the only one to eschew <code>padding</code> as a core choice!</li>



<li><strong><code>color</code>:</strong> Too bad this isn&#8217;t baked right into <code>font</code>!</li>
</ul>



<p>I&#8217;ll also point out that Danny soon questioned his decision to use all four choices:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p>I supposed we&#8217;d need&nbsp;<code>width</code>&nbsp;to achieve a good line length.</p>
</blockquote>


<h3 class="wp-block-heading" id="sunkanmi-fafowora">Sunkanmi Fafowora</h3>


<p>This is the first list to lean squarely into <a href="https://css-tricks.com/snippets/css/complete-guide-grid/">CSS Grid</a>, allowing the <code>grid</code> shorthand to take up a choice in favor of having a complete layout system:</p>



<ul class="wp-block-list is-style-almanac-list">
<li><strong><code>font</code>:</strong> This is a popular one, right?</li>



<li><strong><code>display</code>:</strong> Makes <code>grid</code> available</li>



<li><strong><code>grid</code>:</strong> Required for this <code>display</code> approach</li>



<li><strong><code>color</code>:</strong> For sprinkling in text color where it might help</li>
</ul>



<p>I love that Ryan and Sunkanmi are thinking in terms of structure, albeit in very different ways for different reasons!</p>


<h3 class="wp-block-heading" id="zell-liew">Zell Liew</h3>


<p>In Zell&#8217;s own words: <em>&#8220;Really really plain and simple site here.&#8221;</em></p>



<ul class="wp-block-list is-style-almanac-list">
<li><strong><code>font</code>:</strong> Content is still the most important piece of information.</li>



<li><strong><code>max-width</code>:</strong> Ensures type measure is ok.</li>



<li><strong><code>margin</code>:</strong> Lets me play around with spacing.</li>



<li><strong><code>color</code>:</strong> This ensures there&#8217;s no pure black/white contrast that hurts the eyes. I&#8217;d love for background as well, but we only have four choices.</li>
</ul>



<p>But there&#8217;s a little bit of nuance in those choices, as he explains: <em>&#8220;But I&#8217;d switch up <code>color</code> for <code>background</code> on sites with more complex info that requires proper sectioning. In that case I&#8217;d also switch <code>margin</code> with <code>padding</code>.&#8221;</em></p>


<h3 class="wp-block-heading" id="amit-sheen">Amit Sheen</h3>


<p>Getting straight to Amit&#8217;s selections:</p>



<ul class="wp-block-list is-style-almanac-list">
<li><strong><code>font</code></strong></li>



<li><strong><code>color</code></strong></li>



<li><strong><code>background</code></strong></li>



<li><code><strong>color-scheme</strong></code></li>
</ul>



<p>The choices are largely driven by wanting to combat default user agent styles:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p>The thing is, if we only have four properties, we end up relying heavily on the user agents, and the only thing I&#8217;d really want to change is the fonts. But while we are at it, let&#8217;s add some color control. I’m not sure how much I’d actually use them, but it would be good to have them available.</p>
</blockquote>


<h3 class="wp-block-heading" id="geoff-graham">Geoff Graham</h3>


<p>Alright, I&#8217;m not quite as exciting now that you&#8217;ve seen everyone else&#8217;s choices. You&#8217;ll see a lot of overlap here:</p>



<ul class="wp-block-list is-style-almanac-list">
<li><strong><code>font</code>:</strong> A shorthand for a whopping SEVEN properties for massaging text styles.</li>



<li><strong><code>color</code>:</strong> Seems like this would come in super handy for establishing a visual hierarchy and distinguishing one element from another.</li>



<li><strong><code>padding</code>:</strong> I can&#8217;t live without a little breathing room between an element&#8217;s content box and its inner edge.</li>



<li><strong><code>color-scheme</code>:</strong> Good minimal theming that&#8217;ll work nicely alongside <code>color</code> and support the <code>light-dark()</code> function.</li>
</ul>



<p>Clearly, I&#8217;m all in on typography. That could be a very good thing or it could really constrain me when it comes to laying things out. I really had to fight the urge to use <code>display</code> because I always find it incredibly useful for laying things out side-by-side that wouldn&#8217;t otherwise be possible with block-level elements.</p>



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


<h3 class="wp-block-heading" id="your-turn">Your turn!</h3>


<p>Curious minds want to know! <a href="#respond">Which four properties</a> would you take with you on a desert island?</p>



<p></p>
<hr />
<p><small><a rel="nofollow" href="https://css-tricks.com/whatre-your-top-4-css-properties/">What&#8217;re Your Top 4 CSS Properties?</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/whatre-your-top-4-css-properties/feed/</wfw:commentRss>
			<slash:comments>16</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">388800</post-id>	</item>
		<item>
		<title>Composition in CSS</title>
		<link>https://css-tricks.com/composition-in-css/</link>
					<comments>https://css-tricks.com/composition-in-css/#comments</comments>
		
		<dc:creator><![CDATA[Zell Liew]]></dc:creator>
		<pubDate>Mon, 08 Sep 2025 13:55:26 +0000</pubDate>
				<category><![CDATA[Articles]]></category>
		<category><![CDATA[cascade]]></category>
		<category><![CDATA[CSS]]></category>
		<category><![CDATA[specificity]]></category>
		<guid isPermaLink="false">https://css-tricks.com/?p=388424</guid>

					<description><![CDATA[<p>CSS is a composable language by nature. This composition nature is already built into the cascade. We simply don't talk about composition as a Big Thing because it's the nature of the language.</p>
<hr />
<p><small><a rel="nofollow" href="https://css-tricks.com/composition-in-css/">Composition 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>Tailwind and other utility libraries have been huge proponents of composition. But, to me, their version of composition has always carried a heavy sense of naïveté.</p>



<p>I mean, utility composition is basically adding CSS values to the element, one at a time&#8230;</p>



<pre rel="HTML" class="wp-block-csstricks-code-block language-markup" data-line=""><code markup="tt">&lt;div class="p-4 border-2 border-blue-500"> ... &lt;/div></code></pre>



<p>If we&#8217;re honest for a minute, how is this composition different from adding CSS rules directly into a class?</p>



<pre rel="CSS" class="wp-block-csstricks-code-block language-css" data-line=""><code markup="tt">/* This is composition too! */
.card {
  padding: 1rem; 
  border: 2px solid var(--color-blue-500)
}</code></pre>



<p>That said, I can&#8217;t deny the fact that I&#8217;ve been thinking a lot more about composition ever since I began using Tailwind. So, here are a couple of notes that I&#8217;ve gathered together about CSS composition.</p>



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


<h3 class="wp-block-heading" id="it-s-not-a-new-concept">It&#8217;s not a new concept</h3>


<p><strong>CSS is a composable language by nature.</strong> This composition nature is already built into the cascade. Let&#8217;s say you&#8217;ve decided to style a button with a few properties:</p>



<pre rel="CSS" class="wp-block-csstricks-code-block language-css" data-line=""><code markup="tt">.button {
  display: inline-flex;
  padding: 0.75em 1.5em; 
  /* other styles... */
}</code></pre>



<p>You can always tag on other classes to modify the button&#8217;s appearance:</p>



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



<pre rel="CSS" class="wp-block-csstricks-code-block language-css" data-line=""><code markup="tt">.primary { background: orange; }
.secondary { background: pink; }</code></pre>



<p>You can even change the appearance of other elements to a button by adding the <code>.button</code> class:</p>



<pre rel="HTML" class="wp-block-csstricks-code-block language-markup" data-line=""><code markup="tt">&lt;a href="#" class="button"> ... &lt;/a></code></pre>



<p>Composition is happening in both cases:</p>



<ol class="wp-block-list">
<li>We composed <code>.button</code> onto <code>a</code></li>



<li>We composed <code>.red</code> onto <code>.button</code></li>
</ol>



<p>So, CSS composition has been in existence since forever. We simply don&#8217;t talk about composition as a Big Thing because it&#8217;s the nature of the language.</p>


<h3 class="wp-block-heading" id="developers-take-a-pretty-narrow-view-of-composition">Developers take a pretty narrow view of composition</h3>


<p>When developers talk about composition in CSS, they always seem to always restrict the definition of composition to the addition of classes in the HTML.</p>



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



<p>What&#8217;s interesting is that few people, if any, speak about composition within CSS files — from the angle of using <a href="https://sass-lang.com/documentation/at-rules/mixin/" rel="noopener">Sass mixins</a> or <a href="https://css-tricks.com/using-css-cascade-layers-with-tailwind-utilities/">advanced Tailwind utilities</a>.</p>



<p>In these cases, we are also composing styles&#8230; just not directly in the HTML!</p>



<pre rel="CSS" class="wp-block-csstricks-code-block language-css" data-line=""><code markup="tt">@mixin button () {
  display: inline-flex;
  padding: 0.75em 1.5em; 
  /* other styles ... */
}

.button {
  @include button; 
}</code></pre>


<h2 class="wp-block-heading" id="what-is-composition-">What is composition?</h2>


<p>Composition comes from two possible words:</p>



<ul class="wp-block-list">
<li><strong>Compose:</strong> Put together</li>



<li><strong>Composite:</strong> Made up of distinct parts or elements</li>
</ul>



<p>Both words come from the same Latin root <dfn>componere</dfn>, which means to arrange or direct.</p>



<p>In other words&#8230; all work is put together in some way, so all work is composed. This makes me wonder why composition is used in such a limited context. &#x1f914;</p>



<p>Moving on&#8230;</p>


<h3 class="wp-block-heading" id="composition-doesn-t-reduce-bloat">Composition doesn&#8217;t reduce bloat</h3>


<p>Class composition reduces CSS bloat only if you&#8217;re using utility classes. However, class composition with utility classes is likely to create HTML bloat.</p>



<pre rel="HTML" class="wp-block-csstricks-code-block language-markup" data-line=""><code markup="tt">&lt;div class="utility composition">...&lt;/div>
&lt;div class="one utility at a time">...&lt;/div>
&lt;div class="may create html bloat">...&lt;/div></code></pre>



<p>On the other hand, class composition with selectors might not reduce CSS bloat. But they definitely introduce lesser HTML bloat.</p>



<pre rel="HTML" class="wp-block-csstricks-code-block language-markup" data-line=""><code markup="tt">&lt;div class="class composition">...&lt;/div>
&lt;div class="card primary">...&lt;/div>
&lt;div class="may override properties">...&lt;/div>
&lt;div class="less html bloat"> ... &lt;/div></code></pre>



<p>Which is better? ¯\_(ツ)_/¯</p>


<h3 class="wp-block-heading" id="html-bloat-and-css-bloat-are-probably-the-least-of-your-concerns">HTML bloat and CSS bloat are probably the least of your concerns</h3>


<p>We know this:</p>



<ul class="wp-block-list">
<li>HTML can contain a huge amount of things and it doesn&#8217;t affect performance much.</li>



<li>CSS, too.</li>



<li>500 lines of CSS is approx 12kb to 15kb (according to Claude).</li>



<li>An image typically weighs 150kb or perhaps even more.</li>
</ul>



<p>For most projects, optimizing your use of images is going to net you better weight reduction than agonizing over utility vs. selector composition.</p>



<p>Refactoring your codebase to decrease CSS bloat is not likely to increase performance much. Maybe a 2ms decrease in load times?</p>



<p>But refactoring your codebase to improve developer recognition and make it easier to style? Much more worth it.</p>



<p>So, I&#8217;d say:</p>



<ul class="wp-block-list">
<li>HTML and CSS bloat are pretty inconsequential.</li>



<li>It&#8217;s worthwhile to focus on architecture, structure, and clarity instead.</li>
</ul>


<h3 class="wp-block-heading" id="advanced-compositions">Advanced compositions</h3>


<p>If we zoom out, we can see that all styles we write fall into four categories:</p>



<ol class="wp-block-list">
<li><strong>Layouts:</strong> Affects how we place things on the page</li>



<li><strong>Typography:</strong> Everything font related</li>



<li><strong>Theming:</strong> Everything color related</li>



<li><strong>Effects:</strong> Nice good to have stuff like gradients, shadows, etc.</li>
</ol>



<p>Styles from each of these four categories don&#8217;t intersect with each other. For example:</p>



<ul class="wp-block-list">
<li><code>font-weight</code> belongs exclusively to the <em>Typography</em> category</li>



<li><code>colour</code> belongs exclusively to the <em>Theming</em> category</li>
</ul>



<p>It makes sense to create composable classes per category — when that&#8217;s done, you can mix-and-match these classes together to create the final output. Very much like Lego, for the lack of a better example. (Alright, maybe Duplo for the kids?)</p>



<p>So your HTML might end up looking like this, assuming you do class composition for these four categories:</p>



<pre rel="HTML" class="wp-block-csstricks-code-block language-markup" data-line=""><code markup="tt">&lt;!-- These are all pseudo classes. Use your imagination for now! -->
&lt;div class="layout-1 layout-2 effects-1">
  &lt;h2 class="typography-1 theming-1"> ... &lt;/div>
  &lt;div class="typography-2"> ... &lt;/div>
&lt;/div></code></pre>



<p>A real example of this would be the following, if we used classes from <a href="https://splendidlabz.com/docs/styles/" rel="noopener">Splendid Styles</a> and <a href="https://splendidlabz.com/docs/layouts/" rel="noopener">Splendid Layouts</a>:</p>



<pre rel="HTML" class="wp-block-csstricks-code-block language-markup" data-line=""><code markup="tt">&lt;div class="card vertical elevation-3">
  &lt;h2 class="inter-title"> ... &lt;/h2>
  &lt;div class="prose"> ... &lt;/div>
&lt;/div></code></pre>



<p>I&#8217;m writing more about this four-category system and how I&#8217;m creating composable classes in my latest work: <a href="https://magicaldevschool.com/courses/unorthodox-tailwind/" rel="noopener">Unorthodox Tailwind</a>. Give it a check if you&#8217;re interested!</p>


<h3 class="wp-block-heading" id="wrapping-up">Wrapping up</h3>


<p>To sum up:</p>



<ol class="wp-block-list">
<li>CSS is composable by nature.</li>



<li>Developers seem to be quite narrow-minded about what composition means in CSS.</li>



<li>You can do composition in the HTML or in the CSS.</li>



<li>Styles we write can be divided into four categories — layouts, typography, theming, and effects.</li>
</ol>



<p>And finally: <a href="https://splendidlabz.com/solutions/styles/" rel="noopener">Splendid Styles</a> contains classes that can aid composition in each of these four categories. <a href="https://splendidlabz.com/solutions/layouts/" rel="noopener">Splendid Layouts</a> handles the layout portion. And I&#8217;m writing more about how I create composable classes in my course <a href="https://magicaldevschool.com/courses/unorthodox-tailwind/" rel="noopener">Unorthodox Tailwind</a>.</p>
<hr />
<p><small><a rel="nofollow" href="https://css-tricks.com/composition-in-css/">Composition 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/composition-in-css/feed/</wfw:commentRss>
			<slash:comments>3</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">388424</post-id>	</item>
	</channel>
</rss>

<!-- plugin=object-cache-pro client=phpredis metric#hits=5472 metric#misses=8 metric#hit-ratio=99.9 metric#bytes=5902513 metric#prefetches=118 metric#store-reads=100 metric#store-writes=2 metric#store-hits=316 metric#store-misses=4 metric#sql-queries=5 metric#ms-total=237.18 metric#ms-cache=19.23 metric#ms-cache-avg=0.1903 metric#ms-cache-ratio=8.1 -->
