Harry Roberts on what CSS containment actually is, what each contain value does, and how to use it confidently on real projects without creating hard-to-debug side effects.
Note - Posted on
Last week, I built a visual explainer of the CSS Cascade.
I used anchor positioning with chained anchors to stack the cascade steps. It worked, but Safari 26.4 and earlier had a bug with chained anchors, so I had to exclude Safari entirely via an
@supportshack.I wasn’t entirely happy with that, so I refactored the layout to use a subgrid based approach with
grid-template-areas. Since subgrid became Baseline Widely Available recently, the timing also worked out.While I was at it, I also restructured the HTML such that each cascade step is now a list item inside an ordered list, which gives better semantics.
Note - Posted on
I built a visual explainer of the CSS Cascade, the algorithm that determines the “winning value” from a list of competing declarations.
After Web Day Out 2026, I was checking out Manuel Matuzović’s UA+ stylesheet. Manuel mentioned that he wraps all rules in an anonymous layer to avoid specificity issues. I realised I didn’t actually understand how anonymous layers worked, so I went back and re-read Miriam Suzanne’s cascade layers guide on CSS-Tricks. I really liked the way Miriam presented the cascade and specifically the order of precedence within each step.
Recently, while learning
@scope, I had come across a diagram of the cascade in Bramus’ article on@scope. I had seen it before in his CSS Day 2022 talk as well but this time it stuck with me. The layout just made the cascade click visually.I wondered if I could combine Bramus’ cascade diagram layout with the order of precedence information from Miriam’s article. The early CodePen prototype turned out well enough that I decided to polish and publish it.
There are still some things to be done. The website currently fails WCAG Success Criterion 1.4.4: Resize Text (Level AA) under certain conditions. I haven’t landed on a fix that works for the design yet. Hit me up if you have any ideas.
Writing code is cheap now - Agentic Engineering Patterns - Simon Willison's Weblog
Simon Willison on what they mean by “good code”:
- The code works. It does what it’s meant to do, without bugs.
- We know the code works. We’ve taken steps to confirm to ourselves and to others that the code is fit for purpose.
- It solves the right problem.
- It handles error cases gracefully and predictably: it doesn’t just consider the happy path. Errors should provide enough information to help future maintainers understand what went wrong.
- It’s simple and minimal - it does only what’s needed, in a way that both humans and machines can understand now and maintain in the future.
- It’s protected by tests. The tests show that it works now and act as a regression suite to avoid it quietly breaking in the future.
- It’s documented at an appropriate level, and that documentation reflects the current state of the system - if the code changes an existing behavior the existing documentation needs to be updated to match.
- The design affords future changes. It’s important to maintain YAGNI - code with added complexity to anticipate future changes that may never come is often bad code - but it’s also important not to write code that makes future changes much harder than they should be.
- All of the other relevant “ilities” - accessibility, testability, reliability, security, maintainability, observability, scalability, usability - the non-functional quality measures that are appropriate for the particular class of software being developed.
Adactio: Journal—Magic
Libraries are bits of other people’s code that I call from my code. Frameworks are other people’s code that call bits of my code.
What is agentic engineering? - Agentic Engineering Patterns - Simon Willison's Weblog
Writing code has never been the sole activity of a software engineer. The craft has always been figuring out what code to write. Any given software problem has dozens of potential solutions, each with their own tradeoffs. Our job is to navigate those options and find the ones that are the best fit for our unique set of circumstances and requirements.
You Might Debate It — If You Could See It - Jim Nielsen’s Blog
When you offload your thinking, you might be on-loading someone else’s you’d never agree to — personally or collectively.
Note - Posted on
I couldn’t make it to Web Day Out 2026. Thankfully, Jeremy wrote about it and linked to a bunch of online talk about it. Special shout out to Josh Tumath who live-blogged each talk.
By all accounts, it was an excellent event. The regret at missing out (RAMO) is real. I remotely attended State of the Browser 2026 and I’d love it if more events offered a remote option.
Many of the speakers were kind enough to share a link to their slides. I haven’t checked out all of them yet, but I liked Manuel Matuzović’s talk ‘Breaking with habits’. So much so that I feel the urge to redesign my portfolio based on the approach he showed. For now, I’ll dive deep into his UA+ stylesheet and oli.css project to get a deeper understanding.
Inherited properties leak through the donut scope
Today I learned about a potential pitfall with
@scope.Via MDN [emphasis mine]:
It is important to understand that, while
@scopeallows you to isolate the application of selectors to specific DOM subtrees, it does not completely isolate the applied styles to within those subtrees. This is most noticeable with inheritance — properties that are inherited by children (for examplecolororfont-family) will still be inherited, beyond any set scope limit.In other words,
@scopeisolates your selectors, not your styles. Here’s what that looks like in practice.<article class="card"> <p>In scope</p> <div class="content"> <p>Outside the scope limit</p> </div> </article>@scope (.card) to (.content) { :scope { color: red; font-family: Georgia, serif; } p { padding: 1lh; outline: 1px solid; } }The second
<p>, the one inside.content, won’t get thepaddingoroutlinebecause those are non-inherited properties in the scope limit. That part works exactly as you’d expect.But it will still turn red and render in Georgia because
colorandfont-familyare inherited properties set on.card(via:scope). And as we just learned from MDN, inheritance flows through the DOM regardless of where@scopesets the scope limit.As Miriam mentioned in the Winging It episode on CSS Scope & Mixins, it has to work this way because if
@scopeblocked inheritance at the scope limit, every element beyond the scope limit would receive the initial value for each inherited property.initialapplies the value as defined in the CSS spec. And that would be far more destructive than letting inheritance flow through.If you’re coming from JavaScript, you might expect custom properties to work like scoped variables work in JavaScript. But in CSS, custom properties are inherited properties just like
colororfont-family.@scope (.card) to (.content) { :scope { --text-color: red; font-family: Georgia, serif; } p { color: var(--text-color); padding: 1lh; outline: 1px solid; } } /* This rule exists to show that the inherited value is available beyond the scope limit */ .content p { color: var(--text-color); }With the same HTML, the second
<p>again loses thepaddingandoutline. But--text-colorinherits down from.cardinto.content p. So the second<p>also turns red because itscolorproperty referencesvar(--text-color)which resolves tored.For custom properties, if inheritance were blocked at the scope limit and no ancestor outside the scope had also defined it, any
var(--text-color)beyond the scope limit would be undefined and that would just trigger the fallback in thevar()function, or if there’s no fallback, the property using it would behave asunset.There’s more to what happens when a custom property is undefined, involving the guaranteed-invalid value and invalid at computed-value time (IACVT). I wrote a note on what happens when the CSS function
var()references an undefined custom property.Note - Posted on
What happens when the
var()CSS function references a custom property that is undefined or explicitly set toinitial?In both cases the custom property’s value is the guaranteed-invalid value. That’s the initial value of every custom property as defined in the spec. When
var()encounters this value during substitution, here’s what happens:- If a fallback was provided, the fallback value is used.
- If no fallback was provided, the referencing property becomes invalid at computed-value time (IACVT). The property then behaves as if its value had been specified as the
unsetkeyword.
Note - Posted on
I just read the latest issue of Chris’ Corner on CodePen. I then read the following articles, all of which Chris links to:
- Chris’ article on The Big Gotcha of Anchor Positioning
- Temani Afif’s article on Why is Anchor Positioning not working?
- James Stuckey Weber’s article on CSS Anchor Positioning in Practice
Oh boy! My mind is completely fried. I had no idea anchor positioning was this complicated. I would be afraid to touch CSS anchor positioning if not for this recommendation from James to make it work reliably.
- Make the anchor and the positioned element siblings.
- Put the anchor first in the DOM.
I also saw the Winging It episode on ‘Debugging CSS Anchor Positioning‘. It really helped me develop a mental model of how anchor positioning works and why the gotchas exist.
Also, I totally agree with Tab Atkins-Bittner that dev tools really need a way to show the containing block for elements, especially absPos/fixedPos elements.
Note - Posted on
I liked Kevin Powell’s video on the slide-in nav. In the video, Kevin demonstrates how to create a sticky header that initially scrolls away (when the user scrolls the page), then slides back into view as a sticky top bar once the user has scrolled past a certain threshold.
He achieves the slide-in effect using scroll state queries and a negative
inset-block-starton the sticky positioned element.He also recreates the effect using scroll driven animations. But it’s not an ideal solution, since as he puts it speaking about the header, “if I stop at the wrong spot, it’s like halfway there”. That is definitely a usability issue.
I remembered Bramus sharing his post on scroll triggered animations. As Bramus mentions, scroll triggered animations “trigger when crossing a specific scroll offset”. This is exactly what’s required to avoid the problem Kevin mentioned above.
Here’s my CodePen demo achieving the slide-in effect using scroll triggered animations.
I tested it on Chrome Canary v147. No other browser seems to support it yet, not even Chrome v145 even though Bramus’ article says it ships with v145. On browsers that don’t support scroll triggered animations, there is no slide-in effect but the header remains sticky.
Underlining Links With CSS | Always Twisted
Today I learned about the CSS property
text-underline-position. As Stuart mentions,the
undervalue forces the underline to sit below all the descenders, giving you a consistent baseline.a { text-underline-position: under; }Modular: The Claude C Compiler: What It Reveals About the Future of Software
- Good software depends on judgment, communication, and clear abstraction. AI has amplified this.
- AI coding is automation of implementation, so design and stewardship become more important.
Note - Posted on
I keep forgetting how each of the following CSS values rolls back a declaration to a different point in the cascade. It was about time I jotted it down for my future self before it slips my mind again for the 1000th time.
initial- Applies the initial value as defined in the CSS spec
unset- Inherits or falls back to the initial value
revert- Reverts to the user agent's default value
revert-layer- Rolls back to the value in a previous cascade layer
Note - Posted on
I’ll be attending State of the Browser for the first time, remotely! 💃🏻
Really looking forward to Bramus’ talk on CSS Anchor Positioning and Zach’s talk on reducing the JS footprint.
zachleat’s Twitter Archive—№ 20,184
Zach Leatherman on frontend architecture and building for longevity amid framework churn:
- 👏 Hire someone that’s good at HTML and CSS to build components independent of JS frameworks 👏
- Plug components into a JS framework and layer on behavior later
- Pay HTML/CSS devs what they deserve for giving part of your codebase longer shelf life than unpasteurized milk
Note - Posted on
Today I learned that setting
type="reset"on a<button>element in HTML creates a reset button that, when activated, immediately clears all form data, resetting it to its initial state.This could have been useful in the context of a search form. But
<input type="search">already provides a native clear button for that single field.Saying “No” In an Age of Abundance - Jim Nielsen’s Blog
It’s never been a good idea to ship everything you think of. Every addition accretes complexity and comes with a cognitive cost.
Maybe we need to reframe the concept of scarcity from us, the makers of software, to them, the users of software. Their resources are what matter most:
- Attention (too many features and they can’t all be used, or even tried)
- Stability (too much frequent change is an impediment to learning a product)
- Clarity (too many options creates confusion and paralysis)
- Coherence (too many plots and subplots cannot tell a unified story)
Singing the gospel of collective efficacy (Interconnected)
Similarly we all love when the swifts visit (beautiful birds), so somebody started a group to get swift nest boxes made and installed collectively, then applied for subsidy funding, then got everyone to chip in such that people who couldn’t afford it could have their boxes paid for, and now suddenly we’re all writing to MPs and following the legislation to include swift nesting sites in new build houses. Etc.
It’s called collective efficacy, the belief that you can make a difference by acting together.
