Skip to main content

Three Layers of UI Interaction

Drew Powers wrote about how to polish UI and described this process as the 3 layers of UI interaction:

  1. Render area: where does this element appear?
  2. Hit area: what is the shape/size/placement of the invisible interactive area?
  3. Focus area: when Tab-ing, what is the focus ring shape/placement?

What happens when you don’t care for these things? Well, I reckon everything about a UI then feels uncertain and we tend to navigate around with a real lack of confidence. But there’s been many times where I’ve forgotten about that third one!

Time-based CSS animations

This post by Chuan about time-based CSS animations has a ton of bonkers CSS tricks that are worth checking out:

After years of wait, CSS now has enough Math functions supported, particularly mod(), round(), and trigonometric functions. It's time to revisit the time-based way of animation, hope it'll be more useful this time.

Chuan uses these functions to set up a custom variable via the CSS Houdini API and then track the time in milliseconds. So beware, this is pretty wild stuff:

@property --t {
	syntax: "<integer>";
	initial-value: 0;
	inherits: true;
}

@keyframes tick {
	from {
		--t: 0;
	}
	to {
		--t: 86400000;
	}
}

:root {
	animation: tick 86400000ms linear infinite;
}

Chaun also works on css-doodle which is a web component for drawing cool and elaborate patterns. All of this stuff is very much worth checking out!

Unleash the power of scroll-driven animations

Here’s a great video course about the power of scroll driven animations by Bramus and he takes what we already know about CSS animations but gets into the nitty gritty new-ness of binding these fancy animations to the reader’s scrolling behavior.

Speaking of which, here’s Bramus writing about them in his ever-so-excellent demo page from a while back:

Scroll-driven animations are a common UX pattern on the web. These are animations that are linked to the scroll position of a scroll container. This means that as you scroll up or down, the linked animation scrubs forward or backward in direct response. Think of interesting effects such as parallax background images or reading indicators which move as you scroll.

What clicked for me in Bramus’s video series is that with CSS alone we can now detect if an element is visible in its “scrollport” and then animate it in or out or upside down. So it’s not just about adding big, fancy (and often annoying!) animations to the page. Instead, I think subtle examples like this stacking card effect is where scroll-driven animations are going to really shine. But that’s just my hunch!

Either way, I’m extremely excited about all this stuff because I think it’ll lead to more performant scrolling animations but it also unlocks untold power for us web designers to make subtle visual changes to the reading experience.

Web Components Demystified

Scott Jehl just kickstarted his course on web components and it just sounds great:

Fortunately, we now have a standard component model built right into our browsers, and using its various technologies can help us cut delivery weight and reduce complexity. Many of the largest companies in the world have already begun transitioning their sites and design systems to use web components, and major JavaScript libraries like React have greatly improved their compatibility to work with them as well. And while web components are indeed ready for production today, in the coming months several specifications are shaping up to make them even better. This course aims to cover all of that.

Can’t wait.

Applying P3 colors

Andy Bell set out to convert all the hex colors (like #FFFFFF for white) into p3 colors and wrote up his process:

I’ll be honest. I am not smart enough to explain the new colour systems that CSS has in its ever-expanding toolset. What I can do though, is show you what a major impact they can have with not much effort.

Andy also links to hexp3.com which lets ya pass in any hex value and it’ll do its best to transform it into a p3 color.

Hints and Suggestions

Miriam Suzanne gave a fantastic talk the other day during 11ty’s International Symposium on Making Web Sites Real Good and I couldn’t possibly recommend it more. The highlight for me was when Miriam talks about the “CSS is awesome” meme:

This is the reason that the default overflow is visible: if we get cocky and make a box too small for our text, browsers will try to bail us out. Not because it’s the best looking solution but because the web will try to protect content whenever it can. Browsers are helping us out here.

So to me, this meme, this “CSS is awesome” box-breaking meme, it actually perfectly captures what is awesome about CSS and how much can go wrong when we try to control things that we shouldn’t necessarily control. When we add too many constraints at once.

I love that meme for the wrong reasons.

Miriam argues that this is a core part of CSS’s design but it’s also a political choice: to protect the content and protect the user we shouldn’t treat the CSS we write as a series of strict rules. Instead, we must think of them as hints and suggestions to the browser. Or, as Miriam says: guidance with a light touch.

What would HTML do?

Smart take here from Jeremy about composable design systems:

Colours, spacing, type; these are all building blocks that a designer can compose with. But it gets murkier after that. Pre-made form fields? Sure. Pre-made forms? No thank you!

It’s like there’s a line where a design system crosses over from being a useful toolkit into being a bureaucratic hurdle to overcome. When you hear a designer complaining that a design system is stifling their creativity, I bet it’s because that line has been crossed.

Jeremy argues that design systems should have many small pieces that can be easily reconfigured—and this happens to tie in nicely from another post of his about HTML web components:

...what if my web component needs to do two things?

I make two web components.

The beauty of custom elements is that they can be used just like regular HTML elements. And the beauty of HTML is that it’s composable.

Yes! This is precisely why I started ranting a while back about how, whenever I confront a design system problem, I ask myself this one question that guides the way: “What would HTML do?”

HTML is the ultimate composable language. With just a few elements shuffled together you can create wildly different interfaces. And that’s really where all the power from HTML comes from: everything has one job, does it really well (ideally), which makes the possible options almost infinite.

Design systems should hope for the same.

An alternative proposal for CSS masonry

Rachel Andrew:

The Chrome team believes that masonry should be a separate layout method, defined using display: masonry (or another keyword should a better name be decided upon). Later in this post, you can see some examples of what that could look like in code.

There are two related reasons why we feel that masonry is better defined outside of grid layout—the potential of layout performance issues, and the fact that both masonry and grid have features that make sense in one layout method but not the other.

The most compelling argument for me is the potentially very confusing overlaps and underlaps between grid and masonry where sometimes a grid property just won’t apply to a masonry-style layout. However, this example that Rachel shares in her post certainly feels icky to me:

.masonry {
	display: masonry;
	masonry-template-tracks: repeat(auto-fill, auto);
}

.placed {
	masonry-track: 2 / 5;
}

At a quick glance, and without understanding too closely what’s going on under the hood, I’d question why the heck this looks so much like a grid-but-not-a-grid syntax. Maybe I just gotta sit with it some more though!

Printing Music with CSS Grid

Cool cool cool:

Too often have I witnessed the improvising musician sweaty-handedly attempting to pinch-zoom an A4 pdf on a tiny mobile screen at the climax of a gig. We need fluid and responsive music rendering for the web!

Music notation should be as accessible and as fluid as text is, on the web; that it is not, yet, is something of an afront to my sensibilities. Let us fix this pressing problem.

Maybe we need display: music , too? Okay that was a mean joke and I did not mean it, everyone just calm down. Jeez.

Popover API

Una Kravets has the scoop on the Popover API being fully supported in browsers:

It's happening! One of the features I am most hyped about has just landed across all modern browsers and is officially a part of Baseline 2024. And this feature is the Popover API. Popover provides so many awesome primitives and developer affordances for building layered interfaces like tooltips, menus, teaching UIs, and more.

“Baseline” is a way of measuring browser support that I had somehow managed to skip right over when it was announced last year. But anyway, back to Una’s post: Popover is real neat because you don’t have to worry about z-index or focus management, and using the API is as simple as writing some good ol’ HTML:

<button popovertarget="mypopover">Toggle the popover</button>
<div id="mypopover" popover>Popover content</div>

I like this! This syntax feels very much like invokers and ya know that I love those. But one question I had when reading Una’s post was this: when and how should I use the Popover API?

Thankfully, Hidde de Vries has collected a bunch of examples from menus and popover lists to teaching UIs and mega-navs that’s worth looking at in his post about Popover semantics. Hidde also reminds me that the title attribute exists which makes things even more complicated. And it looks like there’s rumblings about being able to customize the styles of that in the future which I would LOVE.

Another confounding question here: when should I use [popover] and when should I reach for <dialog>? MDN recommends that this decision should be based on whether you need the content of the page to “inert” whilst this thing is active:

Popovers created using the Popover API are always non-modal. If you want to create a modal popover, a <dialog> element is the right way to go. There is significant overlap between the two — you might for example want to create a popover that persists, but control it using declarative HTML. You can turn a <dialog> element into a popover (<dialog popover> is perfectly valid) if you want to combine popover control with dialog semantics.

Huh! So you can write something like this:

<dialog popover id="mypopover">This is some content</dialog>
<button popovertarget="mypopover">Open</button>

That’s handy. But wait, okay. Now I’m really, extremely confused—when should I use title versus popover versus dialog again? Back to Hidde’s post, he notes:

My advice would be that whenever tooltips contain more than just plain text, a non-modal dialog would be more appropriate...

So I think the logic goes something like this:

  1. Use title if the content in my popover is just text.
  2. Use the popover attribute if the content is plain text or a menu of options.
  3. Use <dialog> if you need to force the user to make a decision or block all other interactions on the page.

Update: Sara Soueidan replied with a great note that we shouldn’t reach for the title attribute at all and linked to this equally great post about it:

It is primarily displayed as a native tooltip in desktop browsers, and revealed when a user mouse hovers over markup elements the title is set to. Because of this, it has been a universal usability challenge since its inception, as not all users have been consistently able to interact with it.

Can you detect overflow with CSS?

The other day I made a daft and careless mistake whilst writing about cool queries and specifically this bit of code:

.parent {
	max-width: 300px;
}

.child {
	width: 500px;

	@media (overflow-inline) {
		background: yellow;
	}
}

After misreading the spec and making a quick demo in the browser, I thought I had cracked the problem of being able to detect scrollable containers with just CSS. I assumed what was happening here was that the overflow-inline media query above would detect when the child element was exceeding the bounds of the parent element and then I could change its background to yellow. The demo appeared to work, I had solved the three body problem in CSS, wonderful.

Except, well. It doesn’t work!

Later that same day, Kilian Valkhof wrote up a very good post about why this isn’t the case at all:

Media features say something about the device, and the overflow media feature says something about how the device handles overflowing. It doesn’t say anything about if the page currently is overflowing, just how it would handle overflowing.

Overflow can be used to check how the “device”, or rather the medium in this case, handles overflowing content. On a screen, in most browsers, overflow is going to be “scroll”: If the content overflows, the device deals with that by letting you scroll. But when your medium is print overflow-block is going to evaluate to “paged”. Paper doesn’t scroll, it will continue the content on the next printed page instead.

As Kilian notes, media queries like the one I scribbled above are about the medium, not the page. So this is where I made my fatal and embarrassing mistake that we shall all now politely forget.

Thankfully though, instead of spreading CSS rumors on the internet like I had, back in 2023 Bramus had shown an honest way to detect overflow with—of course!—scroll driven animations. The code isn’t quite as succinct as have a nice little query do the work for us but this does make a bunch of sense to me, even at a quick glance:

@keyframes detect-scroll {
	from,
	to {
		--can-scroll: ;
	}
}

.container {
	animation: detect-scroll linear;
	animation-timeline: scroll(self);

	--bg-if-can-scroll: var(--can-scroll) lime;
	--bg-if-cant-scroll: red;
	background: var(--bg-if-can-scroll, var(--bg-if-cant-scroll));
}

Bramus writes:

For elements that have scrollable overflow, the animation will be active, so the computed value of --can-scroll will be 1. For elements without scrollable overflow, the value will be 0

Fantastic! The whole post walks through this block of code in much more detail and so it’s very much worth reading but this made me sigh a great sigh of relief. I made a little demo where you can add more words to a sentence and see the moment these styles are enabled, too:

See the Pen Detect scroll with CSS by Robin Rendle (@robinrendle) on CodePen.

It’s also worth pointing to Bramus’s excellent page of demos for scroll-driven animations. I feel like it’s about time that I dig my heels in and really explore what’s possible since up until now I’ve been ignoring this CSS superpower.

Design Token to CSS

The other day I mentioned Saneef Ansari’s excellent postcss-design-token-utils that converts a bunch of JSON into custom CSS properties and utility classes. At the end I aggressively rambled about how much I’d love to see this as a dedicated website and, well, Saneef built the darn thing!

It’s called Design Token to CSS and it’s very much worth checking out. I’ve fallen out of the loop a little bit when it comes to syncing design tokens like this between Figma and your front-end codebase but I imagine this is extremely helpful for keeping designers and engineers on the same page there.

Also! This tool is part of Saneef’s collection of “nano-sized tools for web designers” called nanools which I had never seen before but holy heck is color × color worth looking at as well. It lets you build full blown color systems—but I especially love how Saneef goes about explaining luminance, chroma, and hue.

CSS shapes

Making shapes with CSS will never fail to impress me and I just spotted this great resource by Temani Afif that has a ton of examples. You’ve got the basics, like squares, triangles, and circles but then the more wild examples like ribbons and grid lines. Great stuff.

Also, completely unrelated to all this, but I had a lot of fun scrolling through Temani’s work on Codepen. Especially this trippy, 3d parallax effect and this, the ultimate CSS shape of them all: a CSS-only Goku.

Cool queries

There’s lots of query-related things that CSS can do that’s pretty neat. First up you’ve got your bog standard media queries where you can activate some styles when the size of the viewport reaches a certain value:

@media (min-width: 700px) {
	.my-element {
		background: yellow;
	}
}

But now that nesting has pretty good support then it’s just a whole lot nicer to organize them more like this:

.my-element {
	@media (min-width: 500px) {
	}
	@media (min-width: 700px) {
	}
	@media (min-width: 800px) {
	}
}

Ahhhh that is very nice and finally matches my mental model of how these queries are applied by the browser. But as I was spelunking through the Media Queries Level 5 spec, I noticed that you can also write these media queries like this:

.my-element {
	@media (width >= 500px) {
		background: yellow;
	}
}

That’s even nicerer! I’m not sure why I’ve never heard of this syntax before but it sure looks way tidier to me. You can even do more complex things with this syntax like add a more complex range to the mix:

.my-element {
	@media (1000px <= width <= 1500px) {
		background: yellow;
	}
}

This tells the browser to make the background yellow between 1000px and 1500px. That’s really, really nice. I also had no idea that was possible until just a second ago!

Then there’s of course other pretty popular things that media queries can do, such as enable styles based on the orientation of the device. We’ve likely used these to fix some weird mobile bug:

.my-element {
	@media (orientation: portrait) {
	}
	@media (orientation: landscape) {
	}
}

Also, as I mentioned yesterday, you can detect JavaScript support with a media query now:

.my-element {
	@media (scripting: enabled) {
	}
}

Another real shocker found deep in the spec is that it’s possible to use a media query to style an element based on whether it’s overflowing the parent, like this:

.parent {
	max-width: 300px;
}

.child {
	width: 500px;

	@media (overflow-inline) {
		background: yellow;
	}
}

What! That seems fantastically useful! You can play with a demo but it was surprising to me that this bad boy seems to have great browser support already? I must’ve missed the memo.

Update: Kilian Valkhof replied to this and explained that what’s going on here isn’t what I originally assumed! @media queries only apply to the user’s device, not to anything on the page itself. Which is an extreme bummer! It seems that being able to detect whether an element is overflowing with CSS alone isn’t quite possible yet unfortunately.

Okay then there’s the really cool stuff, like container queries. Ahmad Shadeed’s primer is required reading but I’ve always been confused by the syntax without nesting. With nesting, container queries are so much easier to read:

.parent {
	container-name: parent;
	container-type: inline-size;
}

.child {
	@container parent (width >= 1000px) {
		background: yellow;
	}
}

It’s pretty neat that you can add in the width shorthand there and it all works together. I love it when multiple things overlap in this way to improve the whole “ecosystem” of CSS.

Next up: Una Kravets’s post on style queries was a real what the heck moment when I first read it because you can now check to see if an element has certain styles applied to it. Like this:

.parent {
	--background: red;
}

.child {
	@container style(--background: red) {
		background: green;
	}
}

The child element here will only have a green background if the variable in a parent element is set to red.

Right now it looks like browsers only support checking CSS variables like this but in the future they’ll expand to do all sorts of bonkers things. I still feel like I’m wrapping my head around the implications of these style queries but this will be nice if you have a component with a mode or a toggle from one visual state to another. Say, a navigation component that can switch orientations and the child elements change their style based on one CSS value.