> All in One 586: April 2026

Ads

Thursday, April 30, 2026

Partly Cloudy today!



With a high of F and a low of 32F. Currently, it's 40F and Partly Cloudy outside.

Current wind speeds: 5 from the South

Pollen: 0

Sunrise: April 30, 2026 at 05:54PM

Sunset: May 1, 2026 at 07:44AM

UV index: 0

Humidity: 92%

via https://ift.tt/AuB2kgE

May 1, 2026 at 10:02AM

Tuesday, April 28, 2026

Mostly Cloudy today!



With a high of F and a low of 34F. Currently, it's 44F and Partly Cloudy outside.

Current wind speeds: 7 from the Northeast

Pollen: 0

Sunrise: April 28, 2026 at 05:56PM

Sunset: April 29, 2026 at 07:42AM

UV index: 0

Humidity: 61%

via https://ift.tt/xjPGDE3

April 29, 2026 at 10:02AM

Monday, April 27, 2026

Rain Early today!



With a high of F and a low of 30F. Currently, it's 44F and Showers in the Vicinity outside.

Current wind speeds: 16 from the Northeast

Pollen: 0

Sunrise: April 27, 2026 at 05:57PM

Sunset: April 28, 2026 at 07:41AM

UV index: 0

Humidity: 63%

via https://ift.tt/GSlh9kC

April 28, 2026 at 10:02AM

Sunday, April 26, 2026

Isolated Thunderstorms today!



With a high of F and a low of 36F. Currently, it's 41F and Mostly Cloudy outside.

Current wind speeds: 7 from the Northeast

Pollen: 0

Sunrise: April 26, 2026 at 05:59PM

Sunset: April 27, 2026 at 07:40AM

UV index: 0

Humidity: 100%

via https://ift.tt/XtcDYJG

April 27, 2026 at 10:02AM

Saturday, April 25, 2026

Cloudy today!



With a high of F and a low of 32F. Currently, it's 40F and Cloudy outside.

Current wind speeds: 14 from the Northeast

Pollen: 0

Sunrise: April 25, 2026 at 06:00PM

Sunset: April 26, 2026 at 07:39AM

UV index: 0

Humidity: 77%

via https://ift.tt/kVlaegv

April 26, 2026 at 10:02AM

Friday, April 24, 2026

Mostly Cloudy/Wind today!



With a high of F and a low of 31F. Currently, it's 62F and Clear/Wind outside.

Current wind speeds: 24 from the South

Pollen: 0

Sunrise: April 24, 2026 at 06:01PM

Sunset: April 25, 2026 at 07:38AM

UV index: 0

Humidity: 22%

via https://ift.tt/oRjiGL2

April 25, 2026 at 10:02AM

Thursday, April 23, 2026

Mostly Clear today!



With a high of F and a low of 31F. Currently, it's 44F and Clear outside.

Current wind speeds: 7 from the Northeast

Pollen: 0

Sunrise: April 23, 2026 at 06:03PM

Sunset: April 24, 2026 at 07:37AM

UV index: 0

Humidity: 33%

via https://ift.tt/nu91sFt

April 24, 2026 at 10:02AM

Recreating Apple’s Vision Pro Animation in CSS

Apple’s product animations, particularly the scrolly teardowns (technical term), have always been inspiring. But these bleeding-edge animations have always used JavaScript and other technologies. Plus, they aren’t always responsive (or, at least, Apple switches to a static image at a certain width).

I’ve been wowed by CSS’s more recent scrolling animation capabilities and wondered if I could rebuild one of these animations in just CSS and make it responsive. (In fact, CSS sure has come a long way since the last attempt in this publication.) The one I’ll be attempting is from the Vision Pro site and to see it you’ll need to scroll down until you hit a black background, a little more than halfway down the page. If you’re too lazy errr… efficient to go look yourself, and/or they decide to change the animation after this article goes live, you can watch this video:

Note: While Apple’s version works in all major browsers, the CSS-only version, at the time of this writing, will not work in Firefox.

Apple’s Animation

The first thing we have to do is identify what’s going on in the original animation. There are two major stages.

Stage 1: “Exploding” Hardware

Three electronic components rise in sequence from the Vision Pro device at the bottom of the page. Each of the three components is a set of two images that go both in front of and behind other components like a sub roll around a hot dog bun around a bread stick. (Yes, that’s a weird analogy, but you get it, don’t you?)

The first, outermost component (the sub roll) comprises the frontmost and the hindmost images allowing it to appear as if it’s both in front of and behind the other components.

The next component (the hot dog bun) wraps the third component (the bread stick) similarly. This provides depth, visual interest, and a 3D effect, as transparent areas in each image allow the images behind it to show through.

Stage 2: Flip-Up to Eyepieces

The final piece of the Vision Pro animation flips the device up in a smooth motion to show the eyepieces. Apple does this portion with a video, using JavaScript to advance the video as the user scrolls.

Let’s recreate these, one stage at a time.

“Exploding” Hardware

Since Apple already created the six images for the components, we can borrow them. Initially, I started with a stack of img tags in a div and used position: fixed to keep the images at the bottom of the page and position: absolute to have them overlap each other. However, when I did this, I ran into two issues: (1) It wasn’t responsive — shrinking the width of the viewport made the images go off screen, and (2) the Vision Pro couldn’t scroll into view or scroll out of view as it does on the Apple site.

After banging my head against this for a bit, I went back and looked at how Apple constructed it. They had made each image a background image that was at background-position: bottom center, and used background-size: cover to keep it a consistent aspect ratio. I still needed them to be able to overlap though, but I didn’t want to pull them out of flow the way position: absolute does so I set display: grid on their parent element and assigned them all to the same grid area.

.visionpro { /* the overarching div that holds all the images */
  display: grid;
  grid-template-columns: 1fr;
  grid-template-rows: 1fr;
}
.part { /* each of the images has a part class */
  grid-area: 1 / 1 / 2 / 2;
}

As my logic professor used to say in the early aughts, “Now we’re cooking with gas!” (I don’t really know how that applies here, but it seemed appropriate. Somewhat illogical, I know.)

I then began animating the components. I started with a scroll timeline that would have allowed me to pin the animation timeline to scrolling the entire html element, but realized that if the Vision Pro (meaning the elements holding all of the images) was going to scroll both into and out of the viewport, then I should switch to a view timeline so that scrolling the element into view would start the animation rather than trying to estimate a keyframe percentage to start on where the elements would be in view (a rather brittle and non-responsive way to handle it).

Scrolling the Vision Pro into view, pausing while it’s animating, and then scrolling it out of view is a textbook use of position: sticky. So I created a container div that fully encapsulated the Vision Pro div and set it to position: relative. I pushed the container div down past the viewport with a top margin, and set top on the vision pro div to 0. You could then scroll up till the position: sticky held the vision pro in place, the animation executed and then, when the container had been entirely scrolled through, it would carry the Vision Pro div up and out of the viewport.

Now, to tackle the component moves. When I first used a translate to move the images up, I had hoped to use the natural order of the elements to keep everything nicely stacked in my bread-based turducken. Alas, the browser’s sneaky optimization engine placed my sub roll entirely on top of my hot dog bun, which was entirely on top of my breadstick. Luckily, using z-index allowed me to separate the layers and get the overlap that is part of why Apple’s version looks so awesome.

Another problem I ran into was that, at sizes smaller than the 960-pixel width of the images, I couldn’t reliably and responsively move the components up. They needed to be far enough away that they didn’t interfere with Stage 2, but not so far away that they went fully out of the viewport. (Where’s a bear family and a blonde girl when you need them?) Thankfully, as it so often does, algebra saved my tuchus. Since I have the dimensions of the full-size image (960px by 608px), and the full width of the image is equal to the width of the viewport, I could write an equation like below to get the height and use that in my translation calculations for how far to move each component.

--stage2-height: calc(min(100vw, 960px) * 608 / 960);

However, this calculation breaks down when the viewport is shorter than 608px and wider than 960px because the width of the image is no longer equal to 100vw. I initially wrote a similar equation to calculate the width:

--stage2-width: calc(min(100vh, 608px) * 960 / 608);

But it also only works if the height is 608px or less, and they both won’t work while the other one applies. This would be a simple fix using an “if” statement. While CSS does have an if() function as I’m writing this, it doesn’t work in Safari. While I know this whole thing won’t work in Firefox, I didn’t want to knock out a whole other browser if I could help it. So, I fixed it with a media query:

:root {
  --stage2-height: calc(min(100vw, 960px) * 608 / 960);
  --stage2-width: calc(min(100vh, 608px) * 960 / 608);
}

@media screen and (max-height: 608px) {
  :root {
    --stage2-height: calc(var(--vid-width) * 608 / 960);
  }
}

I patted myself on the back for my mathematical genius and problem-solving skills until I realized (as you smarty pants people have probably already figured out) that if the height is less than 608px, then it’s equal to 100vh. (Yes, vh is a complicated unit, particularly on iOS, but for this proof of concept I’m ignoring its downsides).

So, really all I needed was:

:root {
  --stage2-height: calc(min(100vw, 960px) * 608 / 960);
}

@media screen and (max-height: 608px) {
  :root {
    --stage2-height: 100vh;
  }
}

But whatever my mathematical tangents (Ha! Terrible math pun!), this allowed me to base my vertical translations on the height of the Stage 2 graphics, e.g.:

translate: 0 calc(var(--stage2-height) * -1 - 25vh);

…and thus get them out of the way for the Stage 2 animation. That said, it wasn’t perfect, and at viewports narrower than 410px, I still had to make an adjustment to the heights using a media query.

Flip-Up to Eyepieces

Unfortunately, there’s no way to either start a video with just CSS or modify the frame rate with just CSS. However, we can create a set of keyframes that changes the background image over time, such as:

/* ... */

50% {
  background-image: url(imgs/video/00037.jpg);
  z-index: -1;
}

51% {
  background-image: url(imgs/video/00039.jpg);
  z-index: -1;
}

52% {
  background-image: url(imgs/video/00041.jpg);
  z-index: -1;
}

/* ... */

(Since there’s, like, 60-some images involved in this one, I’m not giving you the full set of keyframes, but you can go look at the cssvideo keyframes in the complete CodePen for the full Monty.)

The downside of this, however, is that instead of one video file, we’re downloading 60+ files for the same effect. You’ll notice that the file numbers skip a number between each iteration. This was me halving the number of frames so that we didn’t have 120+ images to download. (You might be able to speed things up with a sprite, but since this is more proof-of-concept than a production-ready solution, I didn’t have the patience to stitch 60+ images together).

The animation was a bit choppy on the initial scroll, even when running the demo locally.

So I added:

<link rel="preload" as="image" href="imgs/video/00011.jpg">

…for every image, including the component images. That helped a lot because the server didn’t have to parse the CSS before downloading all the images.

Using the same view timeline as we do for Stage 1, we run an animation moving it into place and the cssvideo animation and the eyepieces appear to “flip up.”

animation: vpsf-move forwards, cssvideo forwards;
animation-timeline: --apple-vp, --apple-vp;

Fine Tuning

While a view timeline was great, the animation didn’t always begin or end exactly when I wanted it to. Enter animation-range. While there’s a lot of options what I used on all of the .parts was

animation-range: contain cover;

This made sure that the Vision Pro element was inside the viewport before it started (contain) and that it didn’t fully finish the animation until it was out of view (cover). This worked well for the parts because I wanted them fully in view before the components started rising and since their endpoint isn’t important they can keep moving until they’re off screen.

However, for Stage 2, I wanted to be sure the flip up animation had ended before it went off screen so for this one I used:

animation-range: cover 10% contain;

Both cover and 10% refer to the start of the animation, using the cover keyword, but pushing its start 10% later. The contain ensures that the animation ends before it starts going off screen.

Here’s everything together:

And here’s a video in case your browser doesn’t support it yet:

Conclusion

CSS sure has come a long way and while I definitely used some cutting edge features there were also a lot of relatively recent additions that made this possible too.

With scroll timelines, we can attach an animation to the scroll either of an entire element or just when an element is in view. The animation-range property let us fine-tune when the animation happened. position: sticky lets us easily hold something on screen while we animate it even as its scrolling. Grid layout allowed overlap elements without pulling them out of flow. Even calc(), viewport units, custom properties, and media queries all had their roles in making this possible. And that doesn’t even count the HTML innovations like preload. Incredible!

Maybe we should add a W to WWW: The World Wide Wondrous Web. Okay, okay you can stop groaning, but I’m not wrong…


Recreating Apple’s Vision Pro Animation in CSS originally handwritten and published with love on CSS-Tricks. You should really get the newsletter as well.



from CSS-Tricks https://ift.tt/u6Xh9YL
via IFTTT

Wednesday, April 22, 2026

Partly Cloudy/Wind today!



With a high of F and a low of 41F. Currently, it's 66F and Fair outside.

Current wind speeds: 14 from the Southwest

Pollen: 0

Sunrise: April 22, 2026 at 06:04PM

Sunset: April 23, 2026 at 07:36AM

UV index: 0

Humidity: 10%

via https://ift.tt/SXsmr89

April 23, 2026 at 10:02AM

Enhancing Astro With a Markdown Component

There are two ways to enhance Markdown in an Astro project:

  1. Through MDX
  2. Through a Markdown Component

This article focuses on the Markdown Component.

Why Use a Markdown Component

I use a Markdown Component for two main reasons:

  1. It reduces the amount of markup I need to write.
  2. It converts typographic symbols like ' to opening or closing quotes (' or ').

So, I can skip several HTML tags — like <p>, <strong>, <em>, <ul>, <ol>, <li>, and <a>. I can also skip heading tags if I don’t need to add classes to them.

<div class="card">
  <!-- prettier-ignore -->
  <Markdown>
    ## Card Title
    This is a paragraph with **strong** and *italic* text.
    This is the second paragraph with a [link](https://link-somewhere.com)

    - List
    - Of
    - Items
  </Markdown>
</div>

Notice the prettier-ignore comment? It tells prettier not to format the contents within the <Markdown> block so Prettier won’t mess up my Markdown content.

The HTML output will be as follows:

<div class="card">
  <h2> Card Title </h2>
  <p>This is a paragraph with <strong>strong</strong> and <em>italic</em> text.</p>
  <p>This is the second paragraph with a <a href="https://link-somewhere.com">link</a></p>

  <ul>
    <li> List </li>
    <li> Of </li>
    <li> Items </li>
  </ul>
</div>

Installing the Markdown Component

Fun Fact: Astro came with a <Markdown> component in its early release, but this <Markdown> component was migrated to a separate plugin in Version 1, and completely removed in version 3.

I was upset about it. But I decided to build a Markdown component for myself since I liked using one. You can the documentation here.

Using the Markdown component is simple: Just import and use it in the way I showed you above.

---
import { Markdown } from '@splendidlabz/astro'
---

<Markdown>
  ...
</Markdown>

Respects Indentation Automatically

You can write your Markdown naturally, as if you’re writing content normally. This Markdown component detects the indentation and outputs the correct values (without wrapping them in <pre> and <code> tags).

<div>
  <div>
    <!-- prettier-ignore -->
    <Markdown>
      This is a paragraph

      This is a second paragraph
    </Markdown>
  </div>
</div>

Here’s the output:

<div>
  <div>
    <p>This is a paragraph</p>
    <p>This is a second paragraph</p>
  </div>
</div>

Inline Option

There’s an inline option that tells the <Markdown> component not to generate paragraph tags.

<h2 class="max-w-[12em]">
  <Markdown inline> Ain't this cool? </Markdown>
</h2>

Here’s the output:

<h2 class="max-w-[12em]">
  Ain't this cool?
</h2>

Gotchas and Caveats

Prettier messes up the <!-- prettier-ignore --> block if you have unicode characters like emojis and em dashes anywhere before the block.

Here’s the original code:

<!-- prettier-ignore -->
<Markdown>
  Markdown block that contains Unicode characters 🤗
</Markdown>

<!-- prettier-ignore -->
<Markdown>
  Second Markdown block.
</Markdown>

Here’s what it looks like after saving:

<!-- prettier-ignore -->
<Markdown>
  Markdown block that contains unicode characters 🤗
</Markdown>

<!-- prettier-ignore -->
<Markdown>
  Second Markdown block.
</Markdown>

Unfortunately, we can’t do much about emojis because the issue stems from Prettier’s formatter.

But, we can use en and em dashes by writing -- and ---, respectively.

Content Workaround

You can prevent Prettier from breaking all those <!-- prettier-ignore --> comments by not using them!

To do this, you just put your content inside a content property. No need to worry about whitespace as well — that’s taken care of for you.

<Markdown content=`
  This is a paragraph

  This is another paragraph
`/>

Personally, I think it doesn’t look at nice as slot version above…

But it lets you use markdown directly with any JS or json content you load!

---
const content = `
  This is a paragraph

  This is another paragraph
`
---

<Markdown {content} />

Taking it Further

I’ve been building with Astro for 3+ years, and I kept running into the same friction points on content-heavy sites: blog pages, tag pages, pagination, and folder structures that get messy over time.

So I built Practical Astro: Content Systems — 7 ready-to-use solutions for Astro content workflows (MDX is just one of them). You get both the code and the thinking behind it.

If you want a cleaner, calmer content workflow, check it out.

I also write about Astro Patterns and Using Tailwind + CSS together on my blog. Come by and say hi!


Enhancing Astro With a Markdown Component originally handwritten and published with love on CSS-Tricks. You should really get the newsletter as well.



from CSS-Tricks https://ift.tt/IqQ17ZV
via IFTTT

Tuesday, April 21, 2026

Clear today!



With a high of F and a low of 50F. Currently, it's 65F and Clear outside.

Current wind speeds: 16 from the South

Pollen: 0

Sunrise: April 21, 2026 at 06:05PM

Sunset: April 22, 2026 at 07:35AM

UV index: 0

Humidity: 23%

via https://ift.tt/gh812O3

April 22, 2026 at 10:02AM

Monday, April 20, 2026

Mostly Clear today!



With a high of F and a low of 41F. Currently, it's 57F and Clear outside.

Current wind speeds: 4 from the Southeast

Pollen: 0

Sunrise: April 20, 2026 at 06:07PM

Sunset: April 21, 2026 at 07:34AM

UV index: 0

Humidity: 15%

via https://ift.tt/8nJpqYP

April 21, 2026 at 10:02AM

Markdown + Astro = ❤️

Markdown is a great invention that lets us write less markup. It also handles typographical matters like converting straight apostrophes (') to opening or closing quotes (' or ') for us.

Although Astro has built-in support for Markdown via .md files, I’d argue that your Markdown experience can be enhanced in two ways:

  1. MDX
  2. Markdown Component

I’ve cover these in depth in Practical Astro: Content Systems.

We’re going to focus on MDX today.

MDX

MDX is a superset of Markdown. It lets you use components in Markdown and simple JSX in addition to all other Markdown features.

For Astro, you can also use components from any frontend framework that you have installed. So you can do something like:

---
# Frontmatter...
---

import AstroComp from '@/components/AstroComp.astro'
import SvelteComp from '@/components/AstroComp.astro'

<AstroComp> ... </AstroComp>
<SvelteComp> ... </SvelteComp>

It can be a great substitute for content-heavy stuff because it lets you write markup like the following.

<div class="card">
  ### Card Title

  Content goes here

  - List
  - Of
  - Items

  Second paragraph
</div>

Astro will convert the MDX into the following HTML:

<div class="card">
  <h2>Card Title</h2>

  <p>Content goes here </p>

  <ul>
    <li> List </li>
    <li> Of </li>
    <li> Items </li>
  </ul>

  <p>Second paragraph</p>
</div>

Notice what I did above:

  • I used ## instead of a full h2 tag.
  • I used - instead of <ul> and <li> to denote lists.
  • I didn’t need any paragraph tags.

Writing the whole thing in HTML directly would have been somewhat of a pain.

Installing MDX

Astro folks have built an integration for MDX so it’s easy-peasy to add it to your project. Just follow these instructions.

Three Main Ways to Use MDX

These methods also work with standard Markdown files.

  1. Import it directly into an Astro file
  2. Through content collections
  3. Through a layout

Import it Directly

The first way is simply to import your MDX file and use it directly as a component.

---
import MDXComp from '../components/MDXComp.mdx'
---

<MDXComp />

Because of this, MDX can kinda function like a partial.

Through Content Collections

First, you feed your MDX into a content collection. Note that you have to add the mdx pattern to your glob here.

Import it directly

The first way is simply to import your MDX file and use it directly as a component.

// src/content.config.js
import { defineCollection } from 'astro:content';
import { glob } from 'astro/loaders';

const blog = defineCollection({
  loader: glob({ pattern: "**/*.{md,mdx}", base: "./src/blog" }),
});

export const collections = { blog };

Then you retrieve the MDX file from the content collection.

---
import { getEntry, render } from 'astro:content'
const { slug } = Astro.props
const post = await getEntry('blog', slug)
const { Content } = await render(post)
---

<Content />

As you’re doing this, you can pass components into the MDX files so you don’t have to import them individually in every file.

For example, here’s how I would pass the Image component from Splendid Labz into each of my MDX files.

---
import { Image } from '@splendidlabz/astro'
// ...
const { Content } = await render(post)
const components = { Image }
---

<Content {components} />

In my MDX files, I can now use Image without importing it.

<Image src="..." alt="..." />

Use a Layout

Finally, you can add a layout frontmatter in the MDX file.

---
title: Blog Post Title
layout: @/layouts/MDX.astro
---

This layout frontmatter should point to an Astro file.

In that file:

  • You can extract frontmatter properties from Astro.props.content.
  • The MDX content can be rendered with <slot>.
---
import Base from './Base.astro'
const props = Astro.props.content
const { title } = props
---

<Base>
  <h1>{title}</h1>
  <slot />
</Base>

Caveats

Formatting and Linting Fails

ESLint and Prettier don’t format MDX files well, so you’ll end up manually indenting most of your markup.

This is fine for small amounts of markup. But if you have lots of them… then the Markdown Component will be a much better choice.

More on that in another upcoming post.

RSS Issues

The Astro RSS integration doesn’t support MDX files out of the box.

Thankfully, this can be handled easily with Astro containers. I’ll show you how to do this in Practical Astro.

Taking it Further

I’ve been building with Astro for 3+ years, and I kept running into the same friction points on content-heavy sites: blog pages, tag pages, pagination, and folder structures that get messy over time.

So I built Practical Astro: Content Systems, 7 ready-to-use solutions for Astro content workflows (MDX is just one of them). You get both the code and the thinking behind it.

If you want a cleaner, calmer content workflow, check it out.

I also write about Astro Patterns and Using Tailwind + CSS together on my blog. Come by and say hi!


Markdown + Astro = ❤️ originally handwritten and published with love on CSS-Tricks. You should really get the newsletter as well.



from CSS-Tricks https://ift.tt/WOTlj0d
via IFTTT

Sunday, April 19, 2026

Clear today!



With a high of F and a low of 30F. Currently, it's 48F and Clear outside.

Current wind speeds: 8 from the Southeast

Pollen: 0

Sunrise: April 19, 2026 at 06:08PM

Sunset: April 20, 2026 at 07:33AM

UV index: 0

Humidity: 16%

via https://ift.tt/Qu6d3yK

April 20, 2026 at 10:02AM

Saturday, April 18, 2026

Mostly Clear today!



With a high of F and a low of 27F. Currently, it's 37F and Clear outside.

Current wind speeds: 3 from the Southwest

Pollen: 0

Sunrise: April 18, 2026 at 06:09PM

Sunset: April 19, 2026 at 07:32AM

UV index: 0

Humidity: 23%

via https://ift.tt/WQ6g0rq

April 19, 2026 at 10:02AM

Friday, April 17, 2026

Mostly Clear today!



With a high of F and a low of 20F. Currently, it's 31F and Fair outside.

Current wind speeds: 7 from the Northwest

Pollen: 0

Sunrise: April 17, 2026 at 06:11PM

Sunset: April 18, 2026 at 07:31AM

UV index: 0

Humidity: 72%

via https://ift.tt/k4oiOTB

April 18, 2026 at 10:02AM

What’s !important #9: clip-path Jigsaws, View Transitions Toolkit, Name-only Containers, and More

This issue of What’s !important brings you clip-path jigsaws, a view transitions toolkit, name-only containers, the usual roundup of new, notable web platform features, and more.

Creating a jigsaw puzzle using clip-path

Amit Sheen demonstrated how to create a full jigsaw puzzle using clip-path. While I doubt that you’ll need to create a jigsaw puzzle anytime soon, Amit’s walkthrough offers a fantastic way to acquaint yourself with this evolving CSS property that’s becoming more and more popular every day.

For example, Chrome Canary shipped rounded clip-path polygons only last week:

I and Jason are currently working on implementing the CSS `polygon() round` keyword in Chrome. This is one of my favorite CSS features! Thanks to @lea.verou.me for bringing it to CSS. Enable the `enable-experimental-web-platform-features` flag in Chrome Canary codepen.io/yisi/pen/NPR…

[image or embed]

— yisibl.bsky.social (@yisibl.bsky.social) Apr 9, 2026 at 7:25

And there’s also talk of implementing other corner-shape keywords such as bevel, too.

Finally, since we’re on the topic, and because I somehow completely missed it for What’s !important #8, here’s Karl Koch demonstrating some really neat clip-path animations.

Get clippin’!

View transitions toolkit

The Chrome DevRel team created a view transitions toolkit, a collection of utilities that make working with view transitions a bit easier.

Here’s my favorite demo from the site:

Chrome shipped element-scoped view transitions only last month, so there’s no better time to dive into this toolkit.

How name-only containers can be used for scoping

Chris Coyier discussed the use of name-only containers for scoping, and how they compare to class names and @scope. Personally, I prefer @scope because it tends to result in cleaner HTML, and it seems that Chris has updated his stance to be more @scope-aligned too, but it really comes down to personal preference. What’s your take on it?

Hey, remember subgrid?

At one point, subgrid was one of the most highly-anticipated CSS features, but it’s been two and half years since it became Baseline Newly Available, and it’s barely made a dent in the CSS landscape. This is a shame, because subgrid can help us to break out of grids properly and avoid the ‘ol Michael Scofield/nested wrappers/negative margins extravaganza.

But don’t worry, David Bushell’s very simple explanation of subgrid has you covered.

A subgrid-powered web layout featuring Lorem Ipsum placeholder text and some images. Red vertical alignment markers depict the grid columns.
Source: David Bushell (although the red grid lines were added by me).

You Might Not Need…JavaScript?

Remember You Might Not Need jQuery? Pavel Laptev’s The Great CSS Expansion has a similar vibe, noting CSS alternatives to JavaScript libraries (and JavaScript in general) that are smaller and more performant.

A screenshot of a technical article featuring the Anchor Positioning heading, a comparison table of JavaScript libraries for anchor positioning, and a CSS code example.

Missed hits

It’s becoming increasingly difficult to keep up with all of these new CSS features. I attempted way too many rounds of Keith Cirkel’s new CSS or BS? quiz, and my best score was only 18/20. Sad times. Let me know your score in the comments (unless it’s higher than mine…).

A screenshot from an online quiz titled CSS or BS? showing the CSS property font-synthesis in a speech bubble, with buttons to select whether the property is real or fake.

What’s !important #9: clip-path Jigsaws, View Transitions Toolkit, Name-only Containers, and More originally handwritten and published with love on CSS-Tricks. You should really get the newsletter as well.



from CSS-Tricks https://ift.tt/gvCQm1a
via IFTTT

Thursday, April 16, 2026

Partly Cloudy today!



With a high of F and a low of 34F. Currently, it's 62F and Clear outside.

Current wind speeds: 15 from the Southwest

Pollen: 0

Sunrise: April 16, 2026 at 06:12PM

Sunset: April 17, 2026 at 07:30AM

UV index: 0

Humidity: 14%

via https://ift.tt/Wo48aH2

April 17, 2026 at 10:02AM

A Well-Designed JavaScript Module System is Your First Architecture Decision

Writing large programs in JavaScript without modules would be pretty difficult. Imagine you only have the global scope to work with. This was the situation in JavaScript before modules. Scripts attached to the DOM were prone to overwriting each other and variable name conflicts.

With JavaScript modules, you have the ability to create private scopes for your code, and also explicitly state which parts of your code should be globally accessible.

JavaScript modules are not just a way of splitting code across files, but mainly a way to design boundaries between parts of your system.

Behind every technology, there should be a guide for its use. While JavaScript modules make it easier to write “big” programs, if there are no principles or systems for using them, things could easily become difficult to maintain.

How ESM Traded Flexibility For “Analyzability”

The two module systems in JavaScript are CommonJS (CJS) and ECMAScript Modules (ESM).

The CommonJS module system was the first JavaScript module system. It was created to be compatible with server-side JavaScript, and as such, its syntax (require(), module.exports, etc.) was not natively supported by browsers.

The import mechanism for CommonJS relies on the require() function, and being a function, it is not restricted to being called at the top of a module; it can also be called in an if statement or even a loop.

// CommonJS — require() is a function call, can appear anywhere
const module = require('./module')

// this is valid CommonJS — the dependency is conditional and unknowable until runtime
if (process.env.NODE_ENV === 'production') {
  const logger = require('./productionLogger')
}

// the path itself can be dynamic — no static tool can resolve this
const plugin = require(`./plugins/${pluginName}`)

The same cannot be said for ESM: the import statement has to be at the top. Anything else is regarded as an invalid syntax.

// ESM — import is a declaration, not a function call
import { formatDate } from './formatters'

// invalid ESM — imports must be at the top level, not conditional
if (process.env.NODE_ENV === 'production') {
  import { logger } from './productionLogger' // SyntaxError
}

// the path must be a static string — no dynamic resolution
import { plugin } from `./plugins/${pluginName}` // SyntaxError: : template literals are dynamic paths

You can see that CommonJS gives you more flexibility than ESM. But if ESM was created after CommonJS, why wasn’t this flexibility implemented in ESM too, and how does it affect your code?

The answer comes down to static analysis and tree-shaking. With CommonJS, static tools cannot determine which modules are needed for your program to run in order to remove the ones that aren’t needed. And when a bundler is not sure whether a module is needed or not, it includes it by default. The way CommonJS is defined, modules that depend on each other can only be known at runtime.

ESM was designed to fix this. By making sure the position of import statements is restricted to the top of the file and that paths are static string literals, static tools can better understand the structure of the dependencies in the code and eliminate the modules that aren’t needed, which in turn, makes bundle sizes smaller.

Why Modules Are An Architectural Decision

Whether you are aware of it or not, every time you create, import, or export modules, you are shaping the structure of your application. This is because modules are the basic building blocks of a project architecture, and the interaction between these modules is what makes an application functional and useful.

The organization of modules defines boundaries, shapes the flow of your dependencies, and even mirrors your team’s organizational structure. The way you manage the modules in your project can either make or break your project.

The Dependency Rule For Clean Architecture

There are so many ways to structure a project, and there is no one-size-fits-all method to organize every project.

Clean architecture is a controversial methodology and not every team should adopt it. It might even be over-engineering, especially smaller projects. However, if you don’t have a strict option for structuring a project, then the clean architecture approach could be a good place to start.

According to Robert Martin’s dependency rule:

“Nothing in an inner circle can know anything at all about something in an outer circle.”

Robert C. Martin

Based on this rule, an application should be structured in different layers, where the business logic is the application’s core and the technologies for building the application are positioned at the outermost layer. The interface adapters and business rules come in between.

A javascript module linear flow diagram going from frameworks to interface adapters, to use cases to entities.
A simplified representation of the clean architecture concentric circles diagram

From the diagram, the first block represents the outer circle and the last block represents the inner circle. The arrows show which layer depends on the other, and the direction of dependencies flow towards the inner circle. This means that the framework and drivers can depend on the interface adapters, and the interface adapters can depend on the use cases layer, and the use cases layer can depend on the entities. Dependencies must point inward and not outward.

So, based on this rule, the business logic layer should not know anything at all about the technologies used in building the application — which is a good thing because technologies are more volatile than business logic, and you don’t want your business logic to be affected every time you have to update your tech stack. You should build your project around your business logic and not around your tech stack.

Without a proper rule, you are probably freely importing modules from anywhere in your project, and as your project grows, it becomes increasingly difficult to make changes. You’ll eventually have to refactor your code in order to properly maintain your project in the future.

What Your Module Graph Means Architecturally

One tool that can help you maintain good project architecture is the module graph. A module graph is a type of dependency flow that shows how different modules in a project rely on each other. Each time you make imports, you are shaping the dependency graph of your project.

A healthy dependency graph could look like this:

Diagram of a javascript module clean architecture based on express.js demonstrating dependencies that flow in a single direction.
Generated with Madge and Graphviz.

From the graph, you can see dependencies flowing in one direction (following the dependency rule), where high-level modules depend on low-level ones, and never the other way around.

Conversely, this is what an unhealthy one might look like:

A more complex javascript module flow diagram showing how smaller dependencies only rely on larger dependencies, all the way to the end of the flow at which the smallest items circle back to the largest dependency.
I couldn’t find a project with an unhealthy dependency graph, so I had to modify the Express.js dependency graph above to make it look unhealthy for this example.

From the above graph above, you can see that utils.js is no longer a dependency of response.js and application.js as we would find in a healthy graph, but is also dependent on request.js and view.js. This level of dependence on utils.js increases the blast radius if anything goes wrong with it. And it also makes it harder to run tests on the module.

Yet another issue we can point out with utils.js is how it depends on request.js this goes against the ideal flow for dependencies. High-level modules should depend on low-level ones, and never the reverse.

So, how can we solve these issues? The first step is to identify what’s causing the problem. All of the issues with utils.js are related to the fact that it is doing too much. That’s where the Single Responsibility Principle comes into play. Using this principle, utils.js can be inspected to identify everything it does, then each cohesive functionality identified from utils.js can be extracted into its own focused module. This way, we won’t have so many modules that are dependent on utils.js, leading to a more stable application.

Moving on from utils.js​, we can see from the graph that there are now two circular dependencies:

  • express.jsapplication.jsview.jsexpress.js
  • response.jsutils.jsview.jsresponse.js

Circular dependencies occur when two or more modules directly or indirectly depend on each other. This is bad because it makes it hard to reuse a module, and any change made to one module in the circular dependency is likely to affect the rest of the modules.

For example, in the first circular dependency (express.jsapplication.jsview.jsexpress.js), if view.js breaks, application.js will also break because it depends on view.js — and express.js will also break because it depends on application.js.

You can begin checking and managing your module graphs with tools such as Madge and Dependency Cruiser. Madge allows you to visualize module dependencies, while Dependency Cruiser goes further by allowing you to set rules on which layers of your application are allowed to import from which other layers.

Understanding the module graph can help you optimize build times and fix architectural issues such as circular dependency and high coupling.

The Barrel File Problem

One common way the JavaScript module system is being used is through barrel files. A barrel file is a file (usually named something like index.js/index.ts) that re-exports components from other files. Barrel files provide a cleaner way to handle a project’s imports and exports.

Suppose we have the following files:

// auth/login.ts
export function login(email: string, password: string) {
  return `Logging in ${email}`;
}

// auth/register.ts
export function register(email: string, password: string) {
  return `Registering ${email}`;
}

Without barrel files, this is how the imports look:

// somewhere else in the app
import { login } from '@/features/auth/login';
import { register } from '@/features/auth/register';

Notice how the more modules we need in a file, the more import lines we’re going to have in that file.

Using barrel files, we can make our imports look like this:

// somewhere else in the app
import { login, register } from '@/features/auth';

And the barrel file handling the exports will look like this:

// auth/index.ts
export * from './login';
export * from './register';

​​Barrel files provide a cleaner way to handle imports and exports. They improve code readability and make it easier to refactor code by reducing the lines of imports you have to manage. However, the benefits they provide come at the expense of performance (by prolonging build times) and less effective tree shaking, which, of course, results in larger JavaScript bundles. Atlassian, for instance, reported to have achieved 75% faster builds, and a slight reduction in their JavaScript bundle size after removing barrel files from their Jira application’s front-end.

For small projects, barrel files are great. But for larger projects, I’d say they improve code readability at the expense of performance. You can also read about the effects barrel files had on the MSW library project.

The Coupling Issue

Coupling describes how the components of your system rely on each other. In practice, you cannot get rid of coupling, as different parts of your project need to interact for them to function well. However, there are two types of coupling you should avoid: (1) tight coupling and (2) implicit coupling.

Tight coupling occurs when there is a high degree of interdependence between two or more modules in a project such that the dependent module relies on some of the implementation details of the dependency module. This makes it hard (if not impossible) to update the dependency module without touching the dependent module, and, depending on how tightly coupled your project is, updating one module may require updating several other modules — a phenomenon known as change amplification.

Implicit coupling occurs when one module in your project secretly depends on another. Patterns like global singletons, shared mutable state, and side effects can cause implicit coupling. Implicit coupling can reduce inaccurate tree shaking, unexpected behavior in your code, and other issues that are difficult to trace.

While coupling cannot be removed from a system, it is important that:

  • You are not exposing the implementation details of a module for another to depend on.
  • You are not exposing the implementation details of a module for another to depend on.
  • The dependence of one module on another is explicit.
  • Patterns such as shared mutable states and global singletons are used carefully.

Module Boundaries Are Team Boundaries

When building large scale applications, different modules of the application are usually assigned to different teams. Depending on who owns the modules, boundaries are created, and these boundaries can be characterized as one of the following:

  • Weak: Where others are allowed to make changes to code that wasn’t assigned to them, and the ones responsible for the code monitor the changes made by others while also maintaining the code.
  • Strong: Where ownership is assigned to different people, and no one is allowed to make contributions to code that is not assigned to them. If anyone needs a change in another person’s module, they’ll have to contact the owner of that module, so the owners can make that change.
  • Collective: Where no one owns anything and anyone can make changes to any part of the project.

There must be some form of communication regardless of the type of collaboration. With Conway’s Law, we can better infer how different levels of communication coupled with the different types of ownership can affect software architecture.

According to Conway’s Law:

Any organization that designs a system (defined broadly) will produce a design whose structure is a copy of the organization’s communication structure.

Based on this, here are some assumptions we can make:

Good Communication Poor Communication
Weak Code Ownership Architecture may still emerge, but boundaries remain unclear Fragmented, inconsistent architecture
Strong Code Ownership Clear, cohesive architecture aligned with ownership boundaries Disconnected modules; integration mismatches
Collective Code Ownership Highly collaborative, integrated architecture Blurred boundaries; architectural drift

Here’s something to keep in mind whenever you define module boundaries: Modules that frequently change together should share the same boundary, since shared evolution is a strong signal that they represent a single cohesive unit.

Conclusion

Structuring a large project goes beyond organizing files and folders. It involves creating boundaries through modules and coupling them together to form a functional system. By being deliberate about your project architecture, you save yourself from the hassle that comes with refactoring, and you make your project easier to scale and maintain.

If you have existing projects you’d like to manage and you don’t know where to start, you can begin by installing Madge or Dependency Cruiser. Point Madge at your project, and see what the graph actually looks like. Check for circular dependencies and modules with arrows coming in from everywhere. Ask yourself if what you see is what you planned your project to look like.

Then, you can proceed by enforcing boundaries, breaking circular chains, moving modules and extracting utilities. You don’t need to refactor everything at once — you can make changes as you go. Also, if you don’t have an organized system for using modules, you need to start implementing one.

Are you letting your module structure happen to you, or are you designing it?

Further Reading


A Well-Designed JavaScript Module System is Your First Architecture Decision originally handwritten and published with love on CSS-Tricks. You should really get the newsletter as well.



from CSS-Tricks https://ift.tt/CTE7lU5
via IFTTT

Wednesday, April 15, 2026

Clear today!



With a high of F and a low of 36F. Currently, it's 50F and Clear outside.

Current wind speeds: 8 from the South

Pollen: 0

Sunrise: April 15, 2026 at 06:14PM

Sunset: April 16, 2026 at 07:29AM

UV index: 0

Humidity: 21%

via https://ift.tt/QuvMA3I

April 16, 2026 at 10:02AM

Tuesday, April 14, 2026

Clear/Wind today!



With a high of F and a low of 33F. Currently, it's 48F and Clear outside.

Current wind speeds: 19 from the Northwest

Pollen: 0

Sunrise: April 14, 2026 at 06:15PM

Sunset: April 15, 2026 at 07:28AM

UV index: 0

Humidity: 61%

via https://ift.tt/04mWGke

April 15, 2026 at 10:02AM

The Radio State Machine

Managing state in CSS is not exactly the most obvious thing in the world, and to be honest, it is not always the best choice either. If an interaction carries business logic, needs persistence, depends on data, or has to coordinate multiple moving parts, JavaScript is usually the right tool for the job.

That said, not every kind of state deserves a trip through JavaScript.

Sometimes we are dealing with purely visual UI state: whether a panel is open, an icon changed its appearance, a card is flipped, or whether a decorative part of the interface should move from one visual mode to another.

In cases like these, keeping the logic in CSS can be not just possible, but preferable. It keeps the behavior close to the presentation layer, reduces JavaScript overhead, and often leads to surprisingly elegant solutions.

The Boolean solution

One of the best-known examples of CSS state management is the checkbox hack.

If you have spent enough time around CSS, you have probably seen it used for all kinds of clever UI tricks. It can be used to restyle the checkbox itself, toggle menus, control inner visuals of components, reveal hidden sections, and even switch an entire theme. It is one of those techniques that feels slightly mischievous the first time you see it, and then immediately becomes useful.

If you have never used it before, the checkbox hack concept is very simple:

  1. We place a hidden checkbox at the top of the document.
<input type="checkbox" id="state-toggle" hidden>
  1. We connect a label to it, so the user can toggle it from anywhere we want.
<label for="state-toggle" class="state-button">
  Toggle state
</label>
  1. In CSS, we use the :checked state and sibling combinators to style other parts of the page based on whether that checkbox is checked.
#state-toggle:checked ~ .element {
  /* styles when the checkbox is checked */
}

.element {
  /* default styles */
}

In other words, the checkbox becomes a little piece of built-in UI state that CSS can react to. Here is a simple example of how it can be used to switch between light and dark themes:

We have :has()

Note that I’ve placed the checkbox at the top of the document, before the rest of the content. This was important in the days before the :has() pseudo-class, because CSS only allowed us to select elements that come after the checkbox in the DOM. Placing the checkbox at the top was a way to ensure that we could target any element in the page with our selectors, regardless of the label position in the DOM.

But now that :has() is widely supported, we can place the checkbox anywhere in the document, and still target elements that come before it. This gives us much more flexibility in how we structure our HTML. For example, we can place the checkbox right next to the label, and still control the entire page with it.

Here is a classic example of the checkbox hack theme selector, with the checkbox placed next to the label, and using :has() to control the page styles:

<div class="content">
  <!-- content -->
</div>

<label class="theme-button">
  <input type="checkbox" id="theme-toggle" hidden>
  Toggle theme
</label>
body {
  /* other styles */

  /* default to dark mode */
  color-scheme: dark;

  /* when the checkbox is checked, switch to light mode */
  &:has(#theme-toggle:checked) {
    color-scheme: light;
  }
}

/* use the color `light-dark()` on the content */
.content {
  background-color: light-dark(#111, #eee);
  color: light-dark(#fff, #000);
}

Note: I’m using the ID selector (#) in the CSS as it is already part of the checkbox hack convention, and it is a simple way to target the checkbox. If you worry about CSS selectors performance, don’t.

Hidden, not disabled (and not so accessible)

Note I’ve been using the HTML hidden global attribute to hide the checkbox from view. This is a common practice in the checkbox hack, as it keeps the input in the DOM and allows it to maintain its state, while removing it from the visual flow of the page.

Sadly, the hidden attribute also hides the element from assistive technologies, and the label that controls it does not have any interactive behavior on its own, which means that screen readers and other assistive devices will not be able to interact with the checkbox.

This is a significant accessibility concern, and to fix this, we need a different approach: instead of wrapping the checkbox in a label and hiding it with hidden, we can turn the checkbox into the button itself.

<input type="checkbox" class="theme-button" aria-label="Toggle theme">

No hidden, no label, just a fully accessible checkbox. And to style it like a button, we can use the appearance property to remove the default checkbox styling and apply our own styles.

.theme-button {
  appearance: none;
  cursor: pointer;
  font: inherit;
  color: inherit;
  /* other styles */
  
  /* Add text using a simple pseudo-element */
  &::after {
    content: "Toggle theme";
  }
}

This way, we get a fully accessible toggle button that still controls the state of the page through CSS, without relying on hidden inputs or labels. And we’re going to use this approach in all the following examples as well.

Getting more states

So, the checkbox hack is a great way to manage simple binary state in CSS, but it also has a very clear limitation. A checkbox gives us two states: checked and not checked. On and off. That is great when the UI only needs a binary choice, but it is not always enough.

What if we want a component to be in one of three, four, or seven modes? What if a visual system needs a proper set of mutually exclusive states instead of a simple toggle?

That is where the Radio State Machine comes in.

Simple three-state example

The core idea is very similar to the checkbox hack, but instead of a single checkbox, we use a bunch of radio buttons. Each radio button represents a different state, and because radios let us choose one option out of many, they give us a surprisingly flexible way to build multi-state visual systems directly in CSS.

Let’s break down how this works:

<div class="state-button">
  <input type="radio" name="state" data-state="one" aria-label="state one" checked>
  <input type="radio" name="state" data-state="two" aria-label="state two">
  <input type="radio" name="state" data-state="three" aria-label="state three">
</div>

We created a group of radio buttons. Note that they all share the same name attribute (state in this case). This ensures that only one radio can be selected at a time, giving us mutually exclusive states.

We gave each radio button a unique data-state that we can target in CSS to apply different styles based on which state is selected, and the checked attribute to set the default state (in this case, one is the default).

Style the buttons

The style for the radio buttons themselves is similar to the checkbox button we created earlier. We use appearance: none to remove the default styling, and then apply our own styles to make them look like buttons.

input[name="state"] {
  appearance: none;
  padding: 1em;
  border: 1px solid;
  font: inherit;
  color: inherit;
  cursor: pointer;
  user-select: none;

  /* Add text using a pseudo-element */
  &::after {
    content: "Toggle State";
  }

  &:hover {
    background-color: #fff3;
  }
}

The main difference is that we have multiple radio buttons, each representing a different state, and we only need to show the one for the next state in the sequence, while hiding the others. We can’t use display: none on the radio buttons themselves, because that would make them inaccessible, but we can achieve this by adding a few properties as a default, and overriding them for the radio button we want to show.

  1. position: fixed; to take the radio buttons out of the normal flow of the page.
  2. pointer-events: none; to make sure the radio buttons themselves are not clickable.
  3. opacity: 0; to make the radio buttons invisible.

That will hide all the radio buttons by default, while keeping them in the DOM and accessible.

Then we can show the next radio button in the sequence by targeting it with the adjacent sibling combinator (+) when the current radio button is checked. This way, only one radio button is visible at a time, and users can click on it to move to the next state.

input[name="state"] {
  /* other styles */

  position: fixed;
  pointer-events: none;
  opacity: 0;

  &:checked + & {
    position: relative;
    pointer-events: all;
    opacity: 1;
  }
}

And to make the flow circular, we can also add a rule to show the first radio button when the last one is checked. This is, of course, optional, and we’ll talk about linear and bi-directional flows later.

&:first-child:has(~ :last-child:checked) {}

One last touch is to add an outline to the radio buttons container. As we are always hiding the checked radio buttons, we are also hiding its outline. By adding an outline to the container, we can ensure that users can still see where they are when they navigate through the states using the keyboard.

.state-button:has(:focus-visible) {
  outline: 2px solid red;
}

Style the rest

Now we can add styles for each state using the :checked selector to target the selected radio button. Each state will have its own unique styles, and we can use the data-state attribute to differentiate between them.

body {
  /* other styles */
  
  &:has([data-state="one"]:checked) .element {
    /* styles when the first radio button is checked */
  }

  &:has([data-state="two"]:checked) .element {
    /* styles when the second radio button is checked */
  }

  &:has([data-state="three"]:checked) .element {
    /* styles when the third radio button is checked */
  }
}

.element {
  /* default styles */
}

And, of course, this pattern can be used for far more than a simple three-state toggle. The same idea can power steppers, view switchers, card variations, visual filters, layout modes, small interactive demos, and even more elaborate CSS-only toys. Some of these use cases are mostly practical, some are more playful, and we are going to explore a few of them later in this article.

Utilize custom properties

Now that we are back to keeping all the state inputs in one place, and we are already leaning on :has(), we get another very practical advantage: custom properties.

In previous examples, we often set the final properties directly per state, which meant targeting the element itself each time. That works, but it can get noisy fast, especially as the selectors become more specific and the component grows.

A cleaner pattern is to assign state values to variables at a higher level, take advantage of how custom properties naturally cascade down, and then consume those variables wherever needed inside the component.

For example, we can define --left and --top per state:

body {
  /* ... */
  &:has([data-state="one"]:checked) {
    --left: 48%;
    --top: 48%;
  }
  &:has([data-state="two"]:checked) {
    --left: 73%;
    --top: 81%;
  }
  /* other states... */
}

Then we simply consume those values on the element itself:

.map::after {
  content: '';
  position: absolute;
  left: var(--left, 50%);
  top: var(--top, 50%);
  /* ... */
}

This keeps state styling centralized, reduces selector repetition, and makes each component class easier to read because it only consumes variables instead of re-implementing state logic.

Use math, not just states

Once we move state into variables, we can also treat state as a number and start doing calculations.

Instead of assigning full visual values for every state, we can define a single numeric variable:

body {
  /* ... */
  &:has([data-state="one"]:checked) { --state: 1; }
  &:has([data-state="two"]:checked) { --state: 2; }
  &:has([data-state="three"]:checked) { --state: 3; }
  &:has([data-state="four"]:checked) { --state: 4; }
  &:has([data-state="five"]:checked) { --state: 5; }
}

Now we can take that value and use it in calculations on any element we want. For example, we can drive the background color directly from the active state:

.card {
  background-color: hsl(calc(var(--state) * 60) 50% 50%);
}

And if we define an index variable like --i per item (at least until sibling-index() is more widely available), we can calculate each item’s style, like position and opacity, relative to the active state and its place in the sequence.

.card {
  position: absolute;
  transform:
    translateX(calc((var(--i) - var(--state)) * 110%))
    scale(calc(1 - (abs(var(--i) - var(--state)) * 0.3)));
  opacity: calc(1 - (abs(var(--i) - var(--state)) * 0.4));
}

This is where the pattern becomes really fun: one --state variable drives an entire visual system. You are no longer writing separate style blocks for every card in every state. You define a rule once, give each item its own index (--i), and let CSS do the rest.

Not every state flow should loop

You may have noticed that unlike the earlier demos, the last example was not circular. Once you reach the last state, you get stuck there. This is because I removed the rule that shows the first radio button when the last one is checked, and instead added a disabled radio button as a placeholder that appears when the last state is active.

<input type="radio" name="state" disabled>

This pattern is useful for progressive flows like onboarding steps, checkout progress, or multi-step setup forms where the final step is a real endpoint. That said, the states are still accessible through keyboard navigation, and that is a good thing, unless you don’t want it to be.

In that case, you can replace the position, pointer-events, and opacity properties with display: none as a default, and display: block (or inline-block, etc.) for the one that should be visible and interactive. This way, the hidden states will not be focusable or reachable by keyboard users, and the flow will be truly linear.

Bi-directional flows

Of course, interaction should not only move forward. Sometimes users need to go back too, so we can add a “Previous” button by also showing the radio button that points to the previous state in the sequence.

To update the CSS so each state reveals not one, but two radio buttons, we need to expand the selectors to target both the next and previous buttons for each state. We select the next button like before, using the adjacent sibling combinator (+), and the previous button using :has() to look for the checked state on the next button (:has(+ :checked)).

input[name="state"] {
  position: fixed;
  pointer-events: none;
  opacity: 0;
  /* other styles */
  
  &:has(+ :checked),
  &:checked + &  {
    position: relative;
    pointer-events: all;
    opacity: 1;
  }

  /* Set text to "Next" as a default */
  &::after {
    content: "Next";
  }

  /* Change text to "Previous" when the next state is checked */
  &:has(+ :checked)::after {
    content: "Previous";
  }
}

This way, users can navigate in either direction through the states.

This is a simple extension of the previous logic, but it gives us much more control over the flow of the state machine, and allows us to create more complex interactions while still keeping the state management in CSS.

Accessibility notes

Before wrapping up, one important reminder: this pattern should stay visual in responsibility, but accessible in behavior. Because the markup is built on real form controls, we already get a strong baseline, but we need to be deliberate about accessibility details:

  • Make the radio buttons clearly interactive (cursor, size, spacing) and keep their wording explicit.
  • Keep visible focus styles so keyboard users can always track where they are.
  • If a step is not available, communicate that state clearly in the UI, not only by color.
  • Respect reduced motion preferences when state changes animate layout or opacity.
  • If state changes carry business meaning (validation, persistence, async data), hand that part to JavaScript and use CSS state as the visual layer.

In short: the radio state machine works best when it enhances interaction, not when it replaces semantics or application logic.

Closing thoughts

The radio state machine is one of those CSS ideas that feels small at first, and then suddenly opens a lot of creative doors.

With a few well-placed inputs, and a couple of smart selectors, we can build interactions that feel alive, expressive, and surprisingly robust, all while keeping visual state close to the layer that actually renders it.

But it is still just that: an idea.

Use it when the state is mostly visual, local, and interaction-driven. Skip it when the flow depends on business rules, external data, persistence, or complex orchestration.

Believe me, if there were a prize for forcing complex state management into CSS just because we technically can, I would have won it long ago. The real win is not proving CSS can do everything, but learning exactly where it shines.

So here is the challenge: pick one tiny UI in your project, rebuild it as a mini state machine, and see what happens. If it becomes cleaner, keep it. If it gets awkward, roll it back with zero guilt. And don’t forget to share your experiments.


The Radio State Machine originally handwritten and published with love on CSS-Tricks. You should really get the newsletter as well.



from CSS-Tricks https://ift.tt/bXq5d4V
via IFTTT

Mostly Clear today!

With a high of F and a low of 49F. Currently, it's 57F and Clear outside. Current wind speeds: 9 from the Northeast Pollen: 4 Su...