Modern CSS for dynamic component-based architecture | Stephanie Eckles

A lovely overview of much of the new CSS that has landed (or is landing) in browsers recently, and how that relates to component-led architecture:

Thoughtful usage of container queries, :has(), and other modern CSS features makes your components and layouts for more resilient across unknown conditions.

On CSS resets:

  • Scope "default" styles using the :not selector e.g.
a:not([class]) {
    text-decoration-thickness: max(0.08em, 1px);
    text-underline-offset: 0.15em;
  • Also love the use of max() here to prevent the size dipping below a single pixel, and em units to scale with text size.
  • Useful to set global padding on the :target and :focus selectors, so that any anchor links used to jump around a page will leave some gap above and below them (e.g. sticky headers):
:target {
    scroll-padding-block-start: 2rem;

:focus {
    scroll-padding-block-end: 8vh;

On the state of focus-visible:

  • Now the default in all evergreen browsers 🥳
  • Using outline is preferable and lets us use things like outline-offset for easy styling.

On when we can start using CSS native nesting:

  • Use tools like Lightning CSS to let you use native nesting today, and that will monitor browser coverage and convert your styles as needed.

On cascade layers:

  • Layer priority works similarly to styles: those that come last override those before them (so top-to-bottom in a stylesheet is lowest-to-highest priority).
  • Un-layered styles take top priority.
  • That default ordering can be overridden using a layer definition rule at the top of a stylesheet (the example given is what Steph is using):
@layer reset, theme, global, layout, components, utilities, states
  • Annoyingly, it seems that nesting layers works inversely, so the if you have a layer nested within another layer, the child layer will have lower priority than the parent. That feels counterintuitive to me, where I'd expect nested styles to be more specific and therefore higher priority. Will have to wait and see 😬

On a clever grid utility class:

.layout-grid {
    --layout-grid-min: 30ch;
    --layout-grid-gap: 3vmax;    

    display: grid;
    grid-template-columns: repeat(
        minmax(min(100%, var(--layout-grid-min)), 1fr)
    gap: var(--layout-grid-gap);
  • Contains custom property overrides for customisation of grid spacing and column sizes;
  • Automatically covers a fully responsive, multi-column layout.

Or the equivalent in flexbox (very similar what I'm using in places on this site, but clever use of nesting and custom properties):

.flex-layout-grid {
    --flex-grid-min: 30ch;
    --flex-grid-gap: 3vmax;

    display: flex;
    flex-wrap: wrap;
    gap: var(--flex-grid-gap);

    > * {
        flex: 1 1 var(--flex-grid-min);

On using custom property fallbacks to define component APIs and default states:

  • The following will look for an as-yet undefined property, button-color, then, if it is not provided, will fallback to the theme-level primary colour:
.button {
    color: var(--button-color, var(--primary));

On focus outlines and forced colour mode:

  • Users with forced colour mode enabled will often lose additional borders or outlines on non-interactive elements, so if you are ever disabling a browser style using outline, it is best to leave it in place and instead set the colour to transparent. Force colour mode will convert this to a visible outline:
a:focus-visible {
    outline-color: transparent;

On using :has as a quantity query:

  • Example given is a pagination component, where each page is listed as a link (e.g. 1, 2, 3, 4 etc.). But if you have 100+ pages, you probably don't want all of those links. We can use :has and :nth-child (or other pseudo-selectors) to query the number of specific children within a component, and then apply styles:
.pagination:has(li:nth-child(11)) { // 11 here means that after 10 pages, apply the following
    ... // hide some of your pages, or revert to an Page x of y style labelling instead
  • If you combine this with containers and style queries, you can get some very clever logic directly in the CSS. For example, if you have a navigation menu that you want to show a dropdown on smaller screens and if there are more than 6 menu items only show them if there is enough space, you could do this:
.nav:has(:nth-child(6)) {
    --show-menu: true;

@container menu (50ch <= inline-size <= 80ch) {
    @container not style(--show-menu: true) {
        .nav .menu {
            display: none;

        .nav ul {
            display: flex;
  • The issue with the above is that localisation may cause your words to change length and therefore the fixed container sizes could be altered but I think we're also getting the ability to use custom properties in container query statements, so that could mean a very small amount of JS would be able to cascade across an entire site 🤯

Explore Other Notes


Rethinking categorisation

I always enjoy hearing others' thoughts on taxonomies, and Lea's ideas are well thought through and come with some interesting challenges (and findings) around using hackable URLs, folksonomies, and […]


The LLMentalist effect

A brilliant deep-dive into the subtle psychological manipulation that occurs when interacting with LLMs and other so-called "AI" tools and the parallels inherent with con-artist tricks such as […]
  • A&nbsp;lovely overview of much of the new CSS that has landed (or is landing) in browsers recently, and how that relates to component-led [&#8230;]
  • Murray Adcock.
Journal permalink

Made By Me, But Made Possible By:


Build: Gatsby

Deployment: GitHub

Hosting: Netlify

Connect With Me:

Twitter Twitter

Instagram Instragram

500px 500px

GitHub GitHub

Keep Up To Date:

All Posts RSS feed.

Articles RSS feed.

Journal RSS feed.

Notes RSS feed.