Css Tricks

Syndicate content CSS-Tricks
Tips, Tricks, and Techniques on using Cascading Style Sheets.
Updated: 17 hours 26 min ago

Digging Deeper Into Container Style Queries

Thu, 12/01/2022 - 3:59am

I wrote up some early thoughts on container style queries a little while back. It’s still early days. They’re already defined in the CSS Containment Module Level 1 specification (currently in Editor’s Draft status) but there’s still a couple of outstanding discussions taking place.

The basic idea is that we can define a container and then apply styles conditionally to its descendants based on its computed styling.

@container <name>? <conditions> { /* conditional styles */ }

The best example I’ve seen so far is removing italics from something like <em>, <i>, and <q> when they are used in a context where content is already italicized:

em, i, q { font-style: italic; /* default UA behavior */ } /* When the container's font-style is italic, remove italics from these elements. */ @container style(font-style: italic) { em, i, q { font-style: normal; } }

That’s the general idea. But if you didn’t know it, Miriam Suzanne, who is an editor of the spec, keeps an ongoing and thorough set of personal notes on container style queries that is publicly available. It was updated the other day and I spent some time in there trying to wrap my head around more nuanced aspects of style queries. It’s unofficial stuff, but I thought I’d jot down some things that stood out to me. Who knows? Maybe it’s stuff we can eventually look forward to!

Every element is a style container

We don’t even need to explictly assign a container-name or container-type to define a style container because everything is a style container by default.

So, you see that example above that removes italics? Notice it doesn’t identify a container. It jumps right to the query using the style() function. So, what container is being queried? It’s going to be the direct parent of the elements receiving the applied styles. And if not that, then it’s the next nearest relative container that takes precedence.

I like that. It’s very CSS-y for the query to search up for a match, then continue to bubble up until it finds a matching condition.

It was hard for my little brain to understand why we can get away with an implicit container based on styles but not so much when we’re dealing with dimensional queries, like size and inline-size. Miriam explains it nicely:

Dimensional queries require css containment on the size, layout, and style of the container in order to prevent layout loops. Containment is an invasive thing to apply broadly, so it was important that authors have careful control over what elements are (or are not) size containers.

Style-based queries don’t have the same limitation. There is already no way in CSS for descendant styles to have an impact on the computed styles of an ancestor. So no containment is required, and there are no invasive or unexpected side-effects in establishing an element as a style query container.

(Emphasis mine)

It all comes down to consequences — of which there are none as far as everything being a style query container right out of the box.

  • If a container is found: conditions are resolved against that container.
  • If multiple containers match: the nearest relative container takes precedence.
  • If no matches are found: unknown returned.

That’s the same “forgiving” spirit as the rest of CSS.

A container can support both dimensional and style queries

Let’s say we want define a style query without an explicit container-name:

@container style(font-style: italic) { em { font-style: normal; } }

This works because all elements are style containers, no matter the container-type. That’s what allows us to implicitly query styles and rely on the nearest match. And this is totally fine since, again, there are no adverse side effects when establishing style containers.

We have to use an explicit container-type for dimensional queries, but not so much for style queries since every element is a style query. That also means this container is both a style and dimensional query:

.card-container { container: card / inline-size; /* implictly a style query container as well */ } Excluding a container from being queried

Perhaps we don’t want a container to participate in the matching process. That’s where it might be possible to set container-type: none on an element.

.some-element { container-type: none; } Explicit style query containers offer more control of what gets queried

If, say, we were to write a style query for padding , there is no reliable way to determine the best matching container, regardless of whether we’re working with an explicitly named container or the nearest direct parent. That’s because padding is not an inherited property.

So, in those instances, we ought to use container-name to explictly inform the browser which containers they can pull from. We can even give a container multiple explicit names to make it match more conditions:

.card { container-name: card layout theme; }

Oh, and container-name accepts any number of optional and reusable names for a container! That’s even more flexibility when it comes to helping the browser make a choice when searching for matches.

.theme { container-name: theme; } .grid { container-name: layout; } .card { container-name: card layout theme; }

I sort of wonder if that might also be considered a “fallback” in the event that one container is passed over.

Style queries can be combined

The or and and operators allow us to combine wueries to keep things DRY:

@container bubble style(--arrow-position: start start) or style(--arrow-position: end start) { .bubble::after { border-block-end-color: inherit; inset-block-end: 100%; } } /* is the same as... */ @container bubble style(--arrow-position: start start) { /* etc. */ } @container bubble style(--arrow-position: end start) { /* etc. */ } Toggling styles

There’s a little overlap between container style queries and work being done to define a toggle() function. For example, we can cycle through two font-style values, say italic and normal:

em, i, q { font-style: italic; } @container style(font-style: italic) { em, i, q { font-style: normal; } }

Cool. But the proposal for CSS Toggles suggests that the toggle() function would be a simpler approach:

em, i, q { font-style: toggle(italic, normal); }

But anything beyond this sort of binary use case is where toggle() is less suitable. Style queries, though, are good to go. Miriam identifies three instances where style queries are more suitable than a toggle():

/* When font-style is italic, apply background color. */ /* Toggles can only handle one property at a time. */ @container style(font-style: italic) { em, i, q { background: lightpink; } } /* When font-style is italic and --color-mode equals light */ /* Toggles can only evaluate one condition at a time */ @container style((font-style: italic) and (--color-mode: light)) { em, i, q { background: lightpink; } } /* Apply the same query condition to multiple properties */ /* Toggles have to set each one individually as separate toggles */ @container style(font-style: italic) { em, i, q { /* clipped gradient text */ background: var(--feature-gradient); background-clip: text; box-decoration-break: clone; color: transparent; text-shadow: none; } } Style queries solve the “Custom Property Toggle Hack”

Notice that style queries are a formal solution for the “CSS custom property toggle trick”. In there, we set an empty custom property (--foo: ;) and use the comma-separated fallback method to “toggle” properties on and off when then custom property is set to a real value.

button { --is-raised: ; /* off by default */ border: 1px solid var(--is-raised, rgb(0 0 0 / 0.1)); box-shadow: var( --is-raised, 0 1px hsl(0 0% 100% / 0.8) inset, 0 0.1em 0.1em -0.1em rgb(0 0 0 / 0.2) ); text-shadow: var(--is-raised, 0 -1px 1px rgb(0 0 0 / 0.3)); } button:active { box-shadow: var(--is-raised, 0 1px 0.2em black inset); } #foo { --is-raised: initial; /* turned on, all fallbacks take effect. */ }

That’s super cool, also a lot of work that style container queries makes trivial.

Style queries and CSS generated content

For generated content produced by the content property of ::before and ::after pseudo-elements, the matching container is the element on which the content is generated.

.bubble { --arrow-position: end end; container: bubble; border: medium solid green; position: relative; } .bubble::after { content: ""; border: 1em solid transparent; position: absolute; } @container bubble style(--arrow-position: end end) { .bubble::after { border-block-start-color: inherit; inset-block-start: 100%; inset-inline-end: 1em; } } Style queries and web components

We can define a web component as a container and query it by style. First, we have the <template> of the component:

<template id="media-host"> <article> <div part="img"> <slot name="img">…</slot> </div> <div part="content"> <slot name="title">…</slot> <slot name="content">…</slot> </div> </article> </template>

Then we use the :host pseudo-element as a container to set a container-name, a container-type, and some high-level attributes on it:

:host { container: media-host / inline-size; --media-location: before; --media-style: square; --theme: light; }

Elements inside the <media-host> can query the parameters of the <media-host> element:

@container media-host style(--media-style: round) { [part='img'] { border-radius: 100%; } } What’s next?

Again, all the stuff I’ve jotted down here is based on Miriam’s notes, and those notes are not a substitute for the official spec. But they are an indication of what’s being discussed and where things could land in the future. I appreciate Miriam linked up a handful of outstanding discussions still taking place that we can follow to stay on top of things:

Digging Deeper Into Container Style Queries originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

Using The New Constrained Layout In WordPress Block Themes

Wed, 11/30/2022 - 4:11am

One of the main goals of the WordPress Site Editor (and, yes, that is now the “official” name) is to move basic block styling from CSS to structured JSON. JSON files are machine-readable, which makes it consumable by the JavaScript-based Site Editor for configuring a theme’s global styles directly in WordPress.

It’s not all the way there yet! If we look at the Twenty Twenty-Two (TT2) default theme, there were two main unresolved issues: styling interactions (like :hover, :active, :focus), and the margins and padding of layout containers. You can see how those were temporarily fixed in the TT2 style.css file rather than making it into the theme.json file.

WordPress 6.1 fixed those issues and what I want to do is look specifically at the latter. Now that we have JSON-ified styles for the margins and padding of layout containers, that opens us up to more flexible and robust ways to define spacing in our theme layouts.

What kind of spacing are we talking about?

First off, we already have root-level padding which is a fancy way of describing padding on the <body> element. That’s nice because it ensures consistent spacing on an element that is shared on all pages and posts.

But there’s more to it because now we have a way for blocks to bypass that padding and align themselves full-width. That’s thanks to padding-aware alignments which is a new opt-in feature in theme.json. So, even if you have root-level padding, you can still allow, say, an image (or some other block) to break out and go full-width.

That gets us to another thing we get: constrained layouts. The idea here is that any blocks nested in the layout respect the layout’s content width — which is a global setting — and do not flow outside of it. We can override that behavior on a block-by-block basis with alignments, but we’ll get to that.

Let’s start with…

Root-level padding

Again, this isn’t new. We’ve had the ability to set padding on the <body> element in theme.json since the experimental Gutenberg plugin introduced it in version 11.7. We set it on the styles.spacing object, where we have margin and padding objects to define the top, right, bottom, and left spacing on the body:

{ "version": 2, "styles": { "spacing": { "margin": { "top": "60px", "right": "30px", "bottom": "60px", "left": "30px" }, "padding": { "top": "30px", "right": "30px", "bottom": "30px", "left": "30px" } } } }

This is a global setting. So, if we were to crack open DevTools and inspect the <body> element, we would see these CSS styles:

body { margin-top: 60px; margin-right: 30px; margin-bottom: 60px; margin-left: 30px; padding-top: 30px; padding-right: 30px; padding-bottom: 30px; padding-left: 30px; }

Cool. But herein lies the issue of how in the world we can allow some blocks to break out of that spacing to fill the full screen, edge-to-edge. That’s why the spacing is there, right? It helps prevent that from happening!

But there are indeed plenty of cases where you might want to break out of that spacing on a one-off instance when working in the Block Editor. Say we plop an Image block on a page and we want it to go full-width while the rest of the content respects the root-level padding?


Padding-aware alignments

While attempting to create the first default WordPress theme that defines all styles in the theme.json file, lead designer Kjell Reigstad illustrates the challenging aspects of breaking out of root-level padding in this GitHub issue.

Root-level padding prevents blocks from taking up the full viewport width (left). But with padding-aware alignments, some blocks can “opt-out” of that spacing and take up the full viewport width (right). (Image credit: Kjell Reigstad)

New features in WordPress 6.1 were created to address this issue. Let’s dig into those next.


A new useRootPaddingAwareAlignments property was created to address the problem. It was actually first introduced in the Gutenberg plugin v13.8. The original pull request is a nice primer on how it works.

{ "version": 2, "settings": { "appearanceTools": true, "useRootPaddingAwareAlignments": true, // etc. },

Right off the bat, notice that this is a feature we have to opt into. The property is set to false by default and we have to explicitly set it to true in order to enable it. Also notice that we have appearanceTools set to true as well. That opts us into UI controls in the Site Editor for styling borders, link colors, typography, and, yes, spacing which includes margin and padding.

Setting appearanceTools set to true automatically opts blocks into margin and padding without having to set either settings.spacing.padding or setting.spacing.margin to true.

When we do enable useRootPaddingAwareAlignments, we are provided with custom properties with root padding values that are set on the <body> element on the front end. Interestingly, it also applies the padding to the .editor-styles-wrapper class so the spacing is displayed when working in the back-end Block Editor. Pretty cool!

I was able to confirm those CSS custom properties in DevTools while digging around.

Enabling useRootPaddingAwareAlignments also applies left and right padding to any block that supports the “content” width and “wide” width values in the Global Styles image above. We can also define those values in theme.json:

{ "version": 2, "settings": { "layout": { "contentSize": "640px", "wideSize": "1000px" } } }

If the Global Styles settings are different than what is defined in theme.json, then the Global Styles take precedence. You can learn all about managing block theme styles in my last article.

  • contentSize is the default width for blocks.
  • wideSize provides a “wide” layout option and establishes a wider column for blocks to stretch out.

So, that last code example will give us the following CSS:

/* The default content container */ .wp-container-[id] > * { max-width: 640px; margin-left: auto !important; margin-right: auto !important; } /* The wider content container */ .wp-container-[id] > .alignwide { max-width: 1000px; }

[id] indicates a unique number automatically generated by WordPress.

But guess what else we get? Full alignment as well!

.wp-container-[id] .alignfull { max-width: none; }

See that? By enabling useRootPaddingAwareAlignments and defining contentSize and wideSize, we also get a full alignment CSS class for a total of three container configurations for controlling the width of blocks that are added to pages and posts.

This applies to the following layout-specific blocks: Columns, Group, Post Content, and Query Loop.

Block layout controls

Let’s say we add any of those aforementioned layout-specific blocks to a page. When we select the block, the block settings UI offers us new layout settings based on the settings.layout values we defined in theme.json (or the Global Styles UI).

We’re dealing with very specific blocks here — ones that can have other blocks nested inside. So, these Layout settings are really about controlling the width and alignment of those nested blocks. The “Inner blocks use content width” setting is enabled by default. If we toggle it off, then we have no max-width on the container and the blocks inside it go edge-to-edge.

If we leave the toggle on, then nested blocks will adhere to either the contentWidth or wideWidth values (more on that in a bit). Or we can use the numeric inputs to define custom contentWidth and wideWidth values in this one-off instance. That’s great flexibility!

Wide blocks

The settings we just looked are set on the parent block. Once we’ve nested a block inside and select it, we have additional options in that block to use the contentWidth, wideWidth, or go full-width.

This Image block is set to respect the contentWidth setting, labeled “None” in the contextual menu, but can also be set to wideWidth (labeled “Wide width”) or “Full width”.

Notice how WordPress multiplies the root-level padding CSS custom properties by -1 to create negative margins when selecting the “Full width” option.

The .alignfull class sets negative margins on a nested block to ensure it takes up the full viewport width without conflicting with the root-level padding settings. Using a constrained layout

We just covered the new spacing and alignments we get with WordPress 6.1. Those are specific to blocks and any nested blocks within blocks. But WordPress 6.1 also introduces new layout features for even more flexibility and consistency in a theme’s templates.

Case in point: WordPress has completely restructured its Flex and Flow layout types and gave us a constrained layout type that makes it easier to align block layouts in themes using the content width settings in the Site Editor’s Global Styles UI.

Flex, Flow, and Constrained layouts

The difference between these three layout types is the styles that they output. Isabel Brison has an excellent write-up that nicely outlines the differences, but let’s paraphrase them here for reference:

  • Flow layout: Adds vertical spacing between nested blocks in the margin-block direction. Those nested blocks can also be aligned to the left, right, or center.
  • Constrained layout: Same exact deal as a Flow layout, but with width constraints on nested blocks that are based on the contentWidth and wideWidth settings (either in theme.json or Global Styles).
  • Flex layout: This was unchanged in WordPress 6.1. It uses CSS Flexbox to create a layout that flows horizontally (in a row) by default, but can flow vertically as well so blocks stack one on top of another. Spacing is applied using the CSS gap property.

This new slate of layout types creates semantic class names for each layout:

Semantic layout classLayout typeSupported blocks.is-layout-flowFlow layoutColumns, Group, Post Content, and Query Loop..is-layout-constrainedConstrained layoutColumns, Group, Post Content, and Query Loop..is-layout-flexFlex layoutColumns, Buttons, Social Icons

Justin Tadlock has an extensive write-up on the different layout types and semantic classes, including use cases and examples.

Updating your theme to support constrained layouts

If you’re already using a block theme of your own making, you’re going to want to update it to support constrained layouts. All it takes is swapping out a couple of things in theme.json:

{ "version": 2, "settings": { "layout": { "type": "constrained", // replaces `"inherit": true` "type": "default", // replaces `"inherit": false` } } }

These are recently released block themes that have enabled spacing settings with useRootPaddingAwareAlignments and have an updated theme.json file that defines a constrained layout:

ThemeRoot-level paddingConstrained layout featuresTT3Source codeSource codeTemplatesProWPSource codeSource codeTemplatesTriangulateSource codeSource codeTemplatesOaknutSource codeSource codeTemplatesLoudnessSource codeSource codeTemplatesPixlSource codeSource codeTemplatesBlock CanvasSource codeSource code, TemplatesRainfallSource codeSource codeTemplates Disabling layout styles

The base layout styles are default features that ship in WordPress 6.1 Core. In other words, they’re enabled right out of the box. But we can disable them if we need to with this little snippet in functions.php:

// Remove layout styles. add_theme_support( 'disable-layout-styles' );

Big warning here: disabling support for the default layout types also removes all of the base styling for those layouts. That means you’ll need to roll your own styles for spacing, alignments, and anything else needed to display content in different template and block contexts.

Wrapping up

As a great fan of full-width images, the new contained WordPress 6.1 layout and padding aware alignment features are two of my most favorites yet. Taken together with other tools including, better margin and padding control, fluid typography, and updated List and Quote blocks, among others, is solid proof that WordPress is moving towards a better content creation experience.

Now, we have to wait and look at how the imagination and creativity of ordinary designers and content creators use these incredible tools and take it to a new level.

Because of the site editor development iterations in progress, we should always anticipate a difficult path ahead. However, as an optimist, I am eager to see what will happen in the upcoming version of WordPress 6.2. Some of the thing, that I am keeping a close eye on are things like features being considered for inclusion, support for sticky positioning, new layout class names for inner block wrappers, updated footer alignment options, and adding constrained and flow layout options to Cover blocks.

This GitHub issues #44720 lists the layout related discussions slated for WordPress 6.2.

Additional resources

I consulted and referenced a lot of sources while digging into all of this. Here’s a big ol’ list of things I found helpful and think you might enjoy as well.

Tutorials WordPress posts GitHub pull requests and issues

Using The New Constrained Layout In WordPress Block Themes originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

More Than “Slapping Paint on a Website”

Tue, 11/29/2022 - 5:03am

I’m a sucker for anything about front-end job titles.

Anselm Hannemann:

CSS evolved and we’re beyond the point where everyone can just do it as a side interest. We all can learn it and build amazing stuff with it, but using it wisely and correctly in a large-scale context isn’t an easy job anymore. It deserves people whose work is to focus on that part of the code.

Anselm is partly in responding to Sacha Greif’s “Is There Too Much CSS Now?” and the overall sentiment that CSS has a much higher barrier to entry for those learning it today than it did, say, in the CSS3 days. Back then, there was a super direct path to see the magic of CSS. Rachel Andrew perfectly captures that magic feeling in a prescient post from 2019:

There is something remarkable about the fact that, with everything we have created in the past 20 years or so, I can still take a complete beginner and teach them to build a simple webpage with HTML and CSS, in a day. […] We just need a text editor and a few hours. This is how we make things show up on a webpage.

That’s the real entry point here […]

“HTML, CSS and our vanishing industry entry points”

Rachel is speaking to the abstraction of frameworks on top of vanilla CSS (and HTML) but you might as well tack big, shiny, and fairly new features on there, like CSS grid, flexbox, container queries, cascade layers, custom properties, and relational pseudo-classes, to name a few. Not that those are abstractions, of course. There’s just a lot to learn right now, whether you’ve been writing CSS for 20 days or 20 years.

But back to Anselm’s post. Do we need to think about CSS as more than just, you know, styling things? I often joke that my job is slapping paint on websites to make them pretty. But, honestly, I know it’s a lot more than that. We all know it’s more than that.

Maybe CSS is an industry in itself. Think of all the possible considerations that have to pass through your brain when writing CSS rules. Heck, Ahmad Shadeed recently shared all the things his brain processes just to style a Hero component. CSS touches so much of the overall user experience — responsiveness, accessibility, performance, cross-browser, etc. — that it clearly goes well beyond “slapping paint on websites”. So far beyond that each of those things could be someone’s full-time gig, depending on the project.

So, yes, CSS has reached a point where I could imagine seeing “CSS Engineer” on some job board. As Anselm said, “[CSS] deserves people whose work is to focus on that part of the code.” Seen that way, it’s not so hard to imagine front-end development as a whole evolving into areas of specialization, just like many other industries.

More Than “Slapping Paint on a Website” originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

Newer Things to Know About Good Ol’ HTML Lists

Mon, 11/28/2022 - 4:05am

HTML lists are boring. They don’t do much, so we don’t really think about them despite how widely used they are. And we’re still able to do the same things we’ve always done to customize them, like removing markers, reversing order, and making custom counters.

There are, however, a few “newer” things — including dangers — to know when using lists. The dangers are mostly minor, but way more common than you might think. We’ll get to those, plus some new stuff we can do with lists, and even new ways to approach old solutions.

To clarify, these are the HTML elements we’re talking about:

  • Ordered lists <ol>
  • Unordered lists <ul>
  • Description lists <dl>
  • Interactive lists <menu>

Ordered lists, unordered lists, and interactive lists contain list items (<li>) which are displayed according to what kind of list we’re dealing with. An ordered list (<ol>) displays numbers next to list items. Unordered lists (<ul>) and menu elements (<menu>) displays bullet points next to list items. We call these “list markers” and they can even be styled using the ::marker pseudo-element. Description lists use description terms (<dt>) and description details (<dd>) instead of <li> and don’t have list markers. They‘re supposed to be used to display metadata and glossaries, but I can’t say I’ve ever seen them in the wild.

CodePen Embed Fallback

Let’s start off with the easy stuff — how to correctly (at least in my opinion) reset list styles. After that, we’ll take a look at a couple of accessibility issues before shining a light on the elusive <menu> element, which you may be surprised to learn… is actually a type of list, too!

Resetting list styles

Browsers automatically apply their own User Agent styles to help with the visual structure of lists right out of the box. That can be great! But if we want to start with a blank slate free of styling opinions, then we have to reset those styles first.

For example, we can remove the markers next to list items pretty easily. Nothing new here:

/* Zap all list markers! */ ol, ul, menu { list-style: none; }

But modern CSS has new ways to help us target specific list instances. Let’s say we want to clear markers from all lists, except if those lists appear in long-form content, like an article. If we combine the powers of newer CSS pseudo-class functions :where() and :not(), we can isolate those instances and allow the markers in those cases:

/* Where there are lists that are not articles where there are lists... */ :where(ol, ul, menu):not(article :where(ol, ul, menu)) { list-style: none; }

Why use :where() instead of :is()? The specificity of :where() is always zero, whereas :is() takes the specificity of the most specific element in its list of selectors. So, using :where() is a less forceful way of overriding things and can be easily overridden itself.

UA styles also apply padding to space a list item’s content from its marker. Again, that’s a pretty nice affordance right out of the box in some cases, but if we’re already removing the list markers like we did above, then we may as well wipe out that padding too. This is another case for :where():

:where(ol, ul, menu) { padding-left: 0; /* or padding-inline-start */ }

OK, that’s going to prevent marker-less list items from appearing to float in space. But we sort of tossed out the baby with the bathwater and removed the padding in all instances, including the ones we previously isolated in an <article>. So, now those lists with markers sorta hang off the edge of the content box.

CodePen Embed Fallback

Notice that UA styles apply an extra 40px to the <menu> element.

So what we want to do is prevent the list markers from “hanging” outside the container. We can fix that with the list-style-position property:

CodePen Embed Fallback

Or not… maybe it comes down to stylistic preference?

Newer accessibility concerns with lists

Unfortunately, there are a couple of accessibility concerns when it comes to lists — even in these more modern times. One concern is a result of applying list-style: none; as we did when resetting UA styles.

In a nutshell, Safari does not read ordered and unordered lists styled with list-style: none as actual lists, like when navigating content with a screen reader. In other words, removing the markers also removes the list’s semantic meaning. The fix for this fix it to apply an ARIA list role on the list and a listitem role to the list items so screen readers will pick them up:

<ol style="list-style: none;" role="list"> <li role="listItem">...</li> <li role="listItem">...</li> <li role="listItem">...</li> </ol> <ul style="list-style: none;" role="list"> <li role="listItem">...</li> <li role="listItem">...</li> <li role="listItem">...</li> </ul>

Oddly, Safari considers this to be a feature rather than a bug. Basically, users would report that screen readers were announcing too many lists (because developers tend to overuse them), so now, only those with role="list" are announced by screen readers, which actually isn’t that odd after all. Scott O’Hara has a detailed rundown of how it all went down.

A second accessibility concern isn’t one of our own making (hooray!). So, you know how you’re supposed to add an aria-label to <section> elements without headings? Well, it sometimes makes sense to do the same with a list that doesn’t contain a heading element that helps describe the list.

<!-- This list is somewhat described by the heading --> <section> <h2>Grocery list</h2> <ol role="list"> <!-- ... --> </ol> </section> <!-- This list is described by the aria-label --> <ol role="list" aria-label="Grocery list"> <!-- ... --> </ol>

You absolutely don’t have to use either method. Using a heading or an ARIA label is just added context, not a requirement — be sure to test your websites with screen readers and do what offers the best user experience for the situation.

In somewhat related news, Eric Bailey wrote up an excellent piece on why and how he considers aria-label to be a code smell.

Wait, <menu> is a list, too?

OK, so, you’re likely wondering about all of the <menu> elements that I’ve been slipping into the code examples. It’s actually super simple; menus are unordered lists except that they’re meant for interactive items. They’re even exposed to the accessibility tree as unordered lists.

In the early days of the semantic web, I mistakenly believed that menus were like <nav>s before believing that they were for context menus (or “toolbars” as the spec says) because that’s what early versions of the HTML spec said. (MDN has an interesting write-up on all of the deprecated stuff related to <menu> if you’re at all interested.)

Today, however, this is the semantic way to use menus:

<menu aria-label="Share article"> <li><button>Email</button></li> <li><button>Twitter</button></li> <li><button>Facebook</button></li> </menu>

Personally, I think there are some good use-cases for <menu>. That last example shows a list of social sharing buttons wrapped up in a labeled <menu> element, the notable aspect being that the “Share article” label contributes a significant amount of context that helps describe what the buttons do.

Are menus absolutely necessary? No. Are they HTML landmarks? Definitely not. But they’re there if you enjoy fewer <div>s and you feel like the component could use an aria-label for additional context.

Anything else?

Yes, there’s also the aforementioned <dl> (description list) element, however, MDN doesn’t seem to consider them lists in the same way — it’s a list of groups containing terms — and I can’t say that I’ve really seen them in use. According to MDN, they’re supposed to be used for metadata, glossaries, and other types of key-value pairs. I would just avoid them on the grounds that all screen readers announce them differently.

But let’s not end things on a negative note. Here’s a list of super cool things you can do with lists:

Newer Things to Know About Good Ol’ HTML Lists originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

Apple Messages & Color Contrast

Wed, 11/23/2022 - 4:25am

Well, color me this! I was griping to myself last night about just how gosh dang hard it is to read text messages in Apple Messages. You know, not the blue bubbles that you get when messaging other iPhone users. Those are iMessages.

What I’m talking about are the green bubbles you get when messaging non-iPhone users. Those are standard text messages.

iMessage (left) and text message (right)

Let’s run the green through a contrast checker to see what’s up.


Oomph. Now I know why I always reach for my reading glasses when a text message pops up. That 2.17:1 ratio is below the WCAG 2.0 AA requirement of 4.5:1 and wayyyyy below the AAA level of 7:1.

Turns out I’m not the only one griping. A quick search turned up a little trove of news and blog posts — some as recent as last week — about the readability of those green text message bubbles.

I’m no conspiracy theorist and like to give benefit to doubt. Buuuuut…

Apple Messages in iOS 6 (left) and iOS 7 (right)
Credit: Phoceis
  • iOS 6: Dark text on a green gradient background
  • iOS 7: White text on a #5AB539 background (or something close to it)
  • iOS 16.1: White text on a #6ACC46 background

That second one is based on old screenshots and might not be the most accurate color value. But still, the transition from iOS 6 with dark text to what we have today in iOS 16.1 shows a clear regression. I’d like think the design team checked the updated values against WCAG guidelines, sure, but at least against their own Human Interface Guidelines.

The current green background (#65C466) appears to be different than what is listed as the green “system color” (#30D158, converted from a RGB of 48, 209, 88) in the iOS palette listed in the guidelines. But it’s not like that gets us any closer to a passing WCAG AA or AAA rating.



Apple Messages & Color Contrast originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

WordPress Developer Blog

Tue, 11/22/2022 - 8:36am

Well, hey check this out. Looks like there is a brand spankin’ new blog over at WordPress.org all about WordPress development. In the original proposal for the blog, Birgit Pauli-Haak writes:

The Make Core blog has a heavy emphasis on meeting notes for the various core teams, rather than highlighting new features. This makes it difficult for developers who are not contributors or who just occasionally contribute to find the relevant information among the team-related posts.

Josepha describes the blog further in the announcement post:

These are types of content that lend themselves more toward the long-form content of a blog.  However, there are more practical reasons for this new home for developers on WordPress.org:

  • Posts that detail updated or new APIs.
  • A way to subscribe to development-related updates.
  • A place to keep up with ongoing discussions.

Perhaps the most important reason for the Developer Blog is to have a central place for WordPress extenders.  Information can fragment across various sites, and developers spend valuable time seeking it out.  This blog is an attempt to provide a curated experience of the most important updates. 

Hear, hear! This is exactly the sort of thing I feel has been missing in the WordPress development space: quality information from established developers that shares useful tips, tricks, and best practices for working with WordPress in this new era of full-site editing. With WordPress Core development taking place at break-neck speeds, having a central source of updated information and a way to syndicate it is a welcome enhancement for sure.

There are already a few excellent articles in there to kick-start things:

It’s WordPress, of course, so anyone and everyone is encouraged to contribute. If you do, it’s a good idea to first check our the writing tips and guidelines. And, naturally, there is an RSS feed you can use to keep up with the lastest posts.

If you wanna go down the ol’ rabbit trail for how the blog came together, here are a few links to get that context:

(High fives to Ganesh Dahal for the tip!)

To Shared LinkPermalink on CSS-Tricks

WordPress Developer Blog originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

Taming the Cascade With BEM and Modern CSS Selectors

Mon, 11/21/2022 - 3:59am

BEM. Like seemingly all techniques in the world of front-end development, writing CSS in a BEM format can be polarizing. But it is – at least in my Twitter bubble – one of the better-liked CSS methodologies.

Personally, I think BEM is good, and I think you should use it. But I also get why you might not.

Regardless of your opinion on BEM, it offers several benefits, the biggest being that it helps avoid specificity clashes in the CSS Cascade. That’s because, if used properly, any selectors written in a BEM format should have the same specificity score (0,1,0). I’ve architected the CSS for plenty of large-scale websites over the years (think government, universities, and banks), and it’s on these larger projects where I’ve found that BEM really shines. Writing CSS is much more fun when you have confidence that the styles you’re writing or editing aren’t affecting some other part of the site.

There are actually exceptions where it is deemed totally acceptable to add specificity. For instance: the :hover and :focus pseudo classes. Those have a specificity score of 0,2,0. Another is pseudo elements — like ::before and ::after — which have a specificity score of 0,1,1. For the rest of this article though, let’s assume we don’t want any other specificity creep. &#x1f913;

But I’m not really here to sell you on BEM. Instead, I want to talk about how we can use it alongside modern CSS selectors — think :is(), :has(), :where(), etc. — to gain even more control of the Cascade.

What’s this about modern CSS selectors?

The CSS Selectors Level 4 spec gives us some powerful new(ish) ways to select elements. Some of my favorites include :is(), :where(), and :not(), each of which is supported by all modern browsers and is safe to use on almost any project nowadays.

:is() and :where() are basically the same thing except for how they impact specificity. Specifically, :where() always has a specificity score of 0,0,0. Yep, even :where(button#widget.some-class) has no specificity. Meanwhile, the specificity of :is() is the element in its argument list with the highest specificity. So, already we have a Cascade-wrangling distinction between two modern selectors that we can work with.

The incredibly powerful :has() relational pseudo-class is also rapidly gaining browser support (and is the biggest new feature of CSS since Grid, in my humble opinion). However, at time of writing, browser support for :has() isn’t quite good enough for use in production just yet.

Lemme stick one of those pseudo-classes in my BEM and…

/* ❌ specificity score: 0,2,0 */ .something:not(.something--special) { /* styles for all somethings, except for the special somethings */ }

Whoops! See that specificity score? Remember, with BEM we ideally want our selectors to all have a specificity score of 0,1,0. Why is 0,2,0 bad? Consider a similar example, expanded:

.something:not(a) { color: red; } .something--special { color: blue; }

Even though the second selector is last in the source order, the first selector’s higher specificity (0,1,1) wins, and the color of .something--special elements will be set to red. That is, assuming your BEM is written properly and the selected element has both the .something base class and .something--special modifier class applied to it in the HTML.

Used carelessly, these pseudo-classes can impact the Cascade in unexpected ways. And it’s these sorts of inconsistencies that can create headaches down the line, especially on larger and more complex codebases.

Dang. So now what?

Remember what I was saying about :where() and the fact that its specificity is zero? We can use that to our advantage:

/* ✅ specificity score: 0,1,0 */ .something:where(:not(.something--special)) { /* etc. */ }

The first part of this selector (.something) gets its usual specificity score of 0,1,0. But :where() — and everything inside it — has a specificity of 0, which does not increase the specificity of the selector any further.

:where() allows us to nest

Folks who don’t care as much as me about specificity (and that’s probably a lot of people, to be fair) have had it pretty good when it comes to nesting. With some carefree keyboard strokes, we may wind up with CSS like this (note that I’m using Sass for brevity):

.card { ... } .card--featured { /* etc. */ .card__title { ... } .card__title { ... } } .card__title { ... } .card__img { ... }

In this example, we have a .card component. When it’s a “featured” card (using the .card--featured class), the card’s title and image needs to be styled differently. But, as we now know, the code above results in a specificity score that is inconsistent with the rest of our system.

A die-hard specificity nerd might have done this instead:

.card { ... } .card--featured { ... } .card__title { ... } .card__title--featured { ... } .card__img { ... } .card__img--featured { ... }

That’s not so bad, right? Frankly, this is beautiful CSS.

There is a downside in the HTML though. Seasoned BEM authors are probably painfully aware of the clunky template logic that’s required to conditionally apply modifier classes to multiple elements. In this example, the HTML template needs to conditionally add the --featured modifier class to three elements (.card, .card__title, and .card__img) though probably even more in a real-world example. That’s a lot of if statements.

The :where() selector can help us write a lot less template logic — and fewer BEM classes to boot — without adding to the level of specificity.

.card { ... } .card--featured { ... } .card__title { ... } :where(.card--featured) .card__title { ... } .card__img { ... } :where(.card--featured) .card__img { ... }

Here’s same thing but in Sass (note the trailing ampersands):

.card { ... } .card--featured { ... } .card__title { /* etc. */ :where(.card--featured) & { ... } } .card__img { /* etc. */ :where(.card--featured) & { ... } }

Whether or not you should opt for this approach over applying modifier classes to the various child elements is a matter of personal preference. But at least :where() gives us the choice now!

What about non-BEM HTML?

We don’t live in a perfect world. Sometimes you need to deal with HTML that is outside of your control. For instance, a third-party script that injects HTML that you need to style. That markup often isn’t written with BEM class names. In some cases those styles don’t use classes at all but IDs!

Once again, :where() has our back. This solution is slightly hacky, as we need to reference the class of an element somewhere further up the DOM tree that we know exists.

/* ❌ specificity score: 1,0,0 */ #widget { /* etc. */ } /* ✅ specificity score: 0,1,0 */ .page-wrapper :where(#widget) { /* etc. */ }

Referencing a parent element feels a little risky and restrictive though. What if that parent class changes or isn’t there for some reason? A better (but perhaps equally hacky) solution would be to use :is() instead. Remember, the specificity of :is() is equal to the most specific selector in its selector list.

So, instead of referencing a class we know (or hope!) exists with :where(), as in the above example, we could reference a made up class and the <body> tag.

/* ✅ specificity score: 0,1,0 */ :is(.dummy-class, body) :where(#widget) { /* etc. */ }

The ever-present body will help us select our #widget element, and the presence of the .dummy-class class inside the same :is() gives the body selector the same specificity score as a class (0,1,0)… and the use of :where() ensures the selector doesn’t get any more specific than that.

That’s it!

That’s how we can leverage the modern specificity-managing features of the :is() and :where() pseudo-classes alongside the specificity collision prevention that we get when writing CSS in a BEM format. And in the not too distant future, once :has() gains Firefox support (it’s currently supported behind a flag at the time of writing) we’ll likely want to pair it with :where() to undo its specificity.

Whether you go all-in on BEM naming or not, I hope we can agree that having consistency in selector specificity is a good thing!

Taming the Cascade With BEM and Modern CSS Selectors originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

Making Static Noise From a Weird CSS Gradient Bug

Fri, 11/18/2022 - 3:55am

&#x1f44b; The demos in this article experiment with a non-standard bug related to CSS gradients and sub-pixel rendering. Their behavior may change at any time in the future. They’re also heavy as heck. We’re serving them async where you click to load, but still want to give you a heads-up in case your laptop fan starts spinning.

Do you remember that static noise on old TVs with no signal? Or when the signal is bad and the picture is distorted? In case the concept of a TV signal predates you, here’s a GIF that shows exactly what I mean.

View image (contains auto-playing media)

Yes, we are going to do something like this using only CSS. Here is what we’re making:

CodePen Embed Fallback

Before we start digging into the code, I want to say that there are better ways to create a static noise effect than the method I am going to show you. We can use SVG, <canvas>, the filter property, etc. In fact, Jimmy Chion wrote a good article showing how to do it with SVG.

What I will be doing here is kind of a CSS experiment to explore some tricks leveraging a bug with gradients. You can use it on your side projects for fun but using SVG is cleaner and more suitable for a real project. Plus, the effect behaves differently across browsers, so if you’re checking these out, it’s best to view them in Chrome, Edge, or Firefox.

Let’s make some noise!

To make this noise effect we are going to use… gradients! No, there is no secret ingredient or new property that makes it happen. We are going to use stuff that’s already in our CSS toolbox!

The “trick” relies on the fact that gradients are bad at anti-aliasing. You know those kind of jagged edges we get when using hard stop colors? Yes, I talk about them in most of my articles because they are a bit annoying and we always need to add or remove a few pixels to smooth things out:

CodePen Embed Fallback

As you can see, the second circle renders better than the first one because there is a tiny difference (0.5%) between the two colors in the gradient rather than using a straight-up hard color stop using whole number values like the first circle.

Here’s another look, this time using a conic-gradient where the result is more obvious:

CodePen Embed Fallback

An interesting idea struck me while I was making these demos. Instead of fixing the distortion all the time, why not trying to do the opposite? I had no idea what would happen but it was a fun surprise! I took the conic gradient values and started to decrease them to make the poor anti-aliasing results look even worse.

CodePen Embed Fallback

Do you see how bad the last one is? It’s a kind of scrambled in the middle and nothing is smooth. Let’s make it full-screen with smaller values:

CodePen Embed Fallback

I suppose you see where this is going. We get a strange distorted visual when we use very small decimal values for the hard colors stops in a gradient. Our noise is born!

We are still far from the grainy noise we want because we can still see the actual conic gradient. But we can decrease the values to very, very small ones — like 0.0001% — and suddenly there’s no more gradient but pure graininess:

CodePen Embed Fallback

Tada! We have a noise effect and all it takes is one CSS gradient. I bet if I was to show this to you before explaining it, you’d never realize you’re looking at a gradient. You have to look very carefully at center of the gradient to see it.

We can increase the randomness by making the size of the gradient very big while adjusting its position:

CodePen Embed Fallback

The gradient is applied to a fixed 3000px square and placed at the 60% 60% coordinates. We can hardly notice its center in this case. The same can be done with radial gradient as well:

CodePen Embed Fallback

And to make things even more random (and closer to a real noise effect) we can combine both gradients and use background-blend-mode to smooth things out:

CodePen Embed Fallback

Our noise effect is perfect! Even if we look closely at each example, there’s no trace of either gradient in there, but rather beautiful grainy static noise. We just turned that anti-aliasing bug into a slick feature!

Now that we have this, let’s see a few interesting examples where we might use it.

Animated no TV signal

Getting back to the demo we started with:

CodePen Embed Fallback

If you check the code, you will see that I am using a CSS animation on one of the gradients. It’s really as simple as that! All we’re doing is moving the conic gradient’s position at a lightning fast duration (.1s) and this is what we get!

I used this same technique on a one-div CSS art challenge:

CodePen Embed Fallback Grainy image filter

Another idea is to apply the noise to an image to get an old-time-y look. Hover each image to see them without the noise.

CodePen Embed Fallback

I am using only one gradient on a pseudo-element and blending it with the image, thanks to mix-blend-mode: overlay.

We can get an even funnier effect if we use the CSS filter property

CodePen Embed Fallback

And if we add a mask to the mix, we can make even more effects!

CodePen Embed Fallback Grainy text treatment

We can apply this same effect to text, too. Again, all we need is a couple of chained gradients on a background-image and then blend the backgrounds. The only difference is that we’re also reaching for background-clip so the effect is only applied to the bounds of each character.

CodePen Embed Fallback Generative art

If you keep playing with the gradient values, you may get more surprising results than a simple noise effect. We can get some random shapes that look a lot like generative art!

CodePen Embed Fallback CodePen Embed Fallback CodePen Embed Fallback CodePen Embed Fallback

Of course, we are far from real generative art, which requires a lot of work. But it’s still satisfying to see what can be achieved with something that is technically considered a bug!

Monster face

One last example I made for CodePen’s divtober 2022 collection:

CodePen Embed Fallback Wrapping up

I hope you enjoyed this little CSS experiment. We didn’t exactly learn something “new” but we took a little quirk with gradients and turned it into something fun. I’ll say it again: this isn’t something I would consider using on a real project because who knows if or when anti-aliasing will be addressed at some point in time. Instead, this was a very random, and pleasant, surprise when I stumbled into it. It’s also not that easy to control and it behaves inconsistently across browsers.

This said, I am curious to see what you can do with it! You can play with the values, combine different layers, use a filter, or mix-blend-mode, or whatever, and you will for sure get something really cool. Share your creations in the comment section — there are no prizes but we can get a nice collection going!

Making Static Noise From a Weird CSS Gradient Bug originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

Creating a Settings UI for a Custom WordPress Block

Thu, 11/17/2022 - 3:48am

So far, we’ve covered how to work with data from an external API in a custom WordPress block. We walked through the process of fetching that data for use on the front end of a WordPress site, and how to render it directly in the WordPress Block Editor when placing the block in content. This time, we’re going to bridge those two articles by hooking into the block editor’s control panel to create a settings UI for the block we made.

Working With External APIs in WordPress Blocks

You know the control panel I’m referring to, right? It’s that panel on the right that contains post and block settings in the block editor.

See that red highlighted area? That’s the control panel. A Paragraph block is currently selected and the settings for it are displayed in the panel. We can change styles, color, typography… a number of things!

Well, that’s exactly what we’re doing this time around. We’re going to create the controls for the settings of the Football Rankings block we worked on in the last two articles. Last time, we made a button in our block that fetches the external data for the football rankings. We already knew the URL and endpoints we needed. But what if we want to fetch ranking for a different country? Or maybe a different league? How about data from a different season?

We need form controls to do that. We could make use of interactive React components — like React-Select — to browse through the various API options that are available to parse that data. But there’s no need for that since WordPress ships with a bunch of core components that we hook right into!

The documentation for these components — called InspectorControls — is getting better in the WordPress Block Editor Handbook. That’ll get even better over time, but meanwhile, we also have the WordPress Gutenberg Storybook and WordPress Gutenberg Components sites for additional help.

The API architecture

Before we hook into anything, it’s a good idea to map out what it is we need in the first place. I’ve mapped out the structure of the RapidAPI data we’re fetching so we know what’s available to us:

Credit: API-Football

Seasons and countries are two top-level endpoints that map to a leagues endpoint. From there, we have the rest of the data we’re already using to populate the rankings table. So, what we want to do is create settings in the WordPress Block Editor that filter the data by Season, Country, and League, then pass that filtered data into the rankings table. That gives us the ability to drop the block in any WordPress page or post and display variations of the data in the block.

In order to get the standings, we need to first get the leagues. And in order to get the leagues, we first need to get the countries and/or the seasons. You can view the various endpoints in the RapidAPI dashboard.

There are different combinations of data that we can use to populate the rankings, and you might have a preference for which data you want. For the sake of this article, we are going to create the following options in the block settings panel:

  • Choose Country
  • Choose League
  • Choose Season

Then we’ll have a button to submit those selections and fetch the relevant data and pass them into the rankings table.

Load and store a list of countries

We can’t select which country we want data for if we don’t have a list of countries to choose from. So, our first task is to grab a list of countries from RapidAPI.

The ideal thing is to fetch the list of countries when the block is actually used in the page or post content. There’s no need to fetch anything if the block isn’t in use. The approach is very similar to what we did in the first article, the difference being that we are using a different API endpoint and different attributes to store the list of returned countries. There are other WordPress ways to fetch data, like api-fetch, but that‘s outside the scope of what we’re doing here.

We can either include the country list manually after copying it from the API data, or we could use a separate API or library to populate the countries. But the API we’re using already has a list of countries, so I would just use one of its endpoints. Let’s make sure the initial country list loads when the block is inserted into the page or post content in the block editor:

// edit.js const [countriesList, setCountriesList] = useState(null); useEffect(() => { let countryOptions = { method: "GET", headers: { "X-RapidAPI-Key": "Your Rapid API key", "X-RapidAPI-Host": "api-football-v1.p.rapidapi.com", }, }; fetch("https://api-football-v1.p.rapidapi.com/v3/countries", countryOptions) .then( (response) => response.json() ) .then( (response) => { let countriesArray = { ...response }; console.log("Countries list", countriesArray.response); setCountriesList(countriesArray.response); }) .catch((err) => console.error(err)); }, []);

We have a state variable to store the list of countries. Next, we are going to import a component from the @wordpress/block-editor package called InspectorControls which is where all of the components we need to create our settings controls are located.

import { InspectorControls } from "@wordpress/block-editor";

The package’s GitHub repo does a good job explaining InspectorControls. In our example, we can use it to control the API data settings like Country, League, and Season. Here’s a preview so that you get an idea of the UI we’re making:

And once those selections are made in the block settings, we use them in the block’s Edit function:

<InspectorControls> { countriesList && ( <LeagueSettings props={props} countriesList={ countriesList } setApiData={ setApiData } ></LeagueSettings> )} </InspectorControls>

Here, I am making sure that we are using conditional rendering so that the function only loads the component after the list of countries is loaded. If you’re wondering about that LeagueSettings component, it is a custom component I created in a separate components subfolder in the block so we can have a cleaner and more organized Edit function instead of hundreds of lines of country data to deal with in a single file.

We can import it into the edit.js file like this:

import { LeagueSettings } from "./components/LeagueSettings";

Next, we’re passing the required props to the LeagueSettings component from the parent Edit component so that we can access the state variables and attributes from the LeagueSettings child component. We can also do that with other methods like the Context API to avoid prop drilling, but what we have right now is perfectly suitable for what we’re doing.

The other parts of the Edit function can also be converted into components. For example, the league standings code can be put inside a separate component — like maybe LeagueTable.js — and then imported just like we imported LeagueSettings into the Edit function.

Inside the LeagueSettings.js file

LeagueSettings is just like another React component from which we can destructure the props from the parent component. I am going to use three state variables and an additional leagueID state because we are going to extract the ID from the league object:

const [country, setCountry] = useState(null); const [league, setLeague] = useState(null); const [season, setSeason] = useState(null); const [leagueID, setLeagueID] = useState(null);

The first thing we’re going to do is import the PanelBody component from the @wordpress/block-editor package:

import { PanelBody } from "@wordpress/block-editor";

…and include it in our return function:

<PanelBody title="Data settings" initialOpen={false}></PanelBody>

There are other panel tags and attributes — it’s just my personal preference to use these ones. None of the others are required… but look at all the components we have available to make a settings panel! I like the simplicity of the PanelBody for our use case. It expands and collapses to reveal the dropdown settings for the block and that’s it.

Speaking of which, we have a choice to make for those selections. We could use the SelectControl component or a ComboBoxControl, which the docs describe as “an enhanced version of a SelectControl, with the addition of being able to search for options using a search input.” That’s nice for us because the list of countries could get pretty long and users will be able to either do a search query or select from a list.

Here’s an example of how a ComboboxControl could work for our country list:

<ComboboxControl label="Choose country" value={country} options={ filteredCountryOptions } onChange={ (value) => handleCountryChange(value) } onInputChange={ (inputValue) => { setFilteredCountryOptions( setupCountrySelect.filter((option) => option.label .toLowerCase() .startsWith(inputValue.toLowerCase()) ) ); }} />

The ComboboxControl is configurable in the sense that we can apply different sizing for the control’s label and values:

{ value: 'small', label: 'Small', },

But our API data is not in this syntax, so we can convert the countriesList array that comes from the parent component when the block is included:

let setupCountrySelect; setupCountrySelect = countriesList.map((country) => { return { label: country.name, value: country.name, }; });

When a country is selected from the ComboboxControl, the country value changes and we filter the data accordingly:

function handleCountryChange(value) { // Set state of the country setCountry(value); // League code from RapidAPI const options = { method: "GET", headers: { "X-RapidAPI-Key": "Your RapidAPI key", "X-RapidAPI-Host": "api-football-v1.p.rapidapi.com", }, }; fetch(`https://api-football-v1.p.rapidapi.com/v3/leagues?country=${value}`, options) .then((response) => response.json()) .then((response) => { return response.response; }) .then((leagueOptions) => { // Set state of the league variable setLeague(leagueOptions); // Convert it as we did for Country options setupLeagueSelect = leagueOptions.map((league) => { return { label: league.league.name, value: league.league.name, }; }); setFilteredLeagueOptions(setupLeagueSelect); }) .catch((err) => console.error(err)); }

Note that I am using another three state variables to handle changes when the country selection changes:

const [filteredCountryOptions, setFilteredCountryOptions] = useState(setupCountrySelect); const [filteredLeagueOptions, setFilteredLeagueOptions] = useState(null); const [filteredSeasonOptions, setFilteredSeasonOptions] = useState(null); What about the other settings options?

I will show the code that I used for the other settings but all it does is take normal cases into account while defining errors for special cases. For example, there will be errors in some countries and leagues because:

  • there are no standings for some leagues, and
  • some leagues have standings but they are not in a single table.

This isn’t a JavaScript or React tutorial, so I will let you handle the special cases for the API that you plan to use:

function handleLeagueChange(value) { setLeague(value); if (league) { const selectedLeague = league.filter((el) => { if (el.league.name === value) { return el; } }); if (selectedLeague) { setLeague(selectedLeague[0].league.name); setLeagueID(selectedLeague[0].league.id); setupSeasonSelect = selectedLeague[0].seasons.map((season) => { return { label: season.year, value: season.year, }; }); setFilteredSeasonOptions(setupSeasonSelect); } } else { return; } } function handleSeasonChange(value) { setSeason(value); } Submitting the settings selections

In the last article, we made a button in the block editor that fetches fresh data from the API. There’s no more need for it now that we have settings. Well, we do need it — just not where it currently is. Instead of having it directly in the block that’s rendered in the block editor, we’re going to move it to our PanelBody component to submit the settings selections.

So, back in LeagueSettings.js:

// When countriesList is loaded, show the country combo box { countriesList && ( <ComboboxControl label="Choose country" value={country} options={filteredCountryOptions} onChange={(value) => handleCountryChange(value)} onInputChange={(inputValue) => { setFilteredCountryOptions( setupCountrySelect.filter((option) => option.label .toLowerCase() .startsWith(inputValue.toLowerCase()) ) ); }} /> )} // When filteredLeagueOptions is set through handleCountryChange, show league combobox { filteredLeagueOptions && ( <ComboboxControl label="Choose league" value={league} options={filteredLeagueOptions} onChange={(value) => handleLeagueChange(value)} onInputChange={(inputValue) => { setFilteredLeagueOptions( setupLeagueSelect.filter((option) => option.label .toLowerCase() .startsWith(inputValue.toLowerCase()) ) ); }} /> )} // When filteredSeasonOptions is set through handleLeagueChange, show season combobox { filteredSeasonOptions && ( <> <ComboboxControl label="Choose season" value={season} options={filteredSeasonOptions} onChange={(value) => handleSeasonChange(value)} onInputChange={ (inputValue) => { setFilteredSeasonOptions( setupSeasonSelect.filter((option) => option.label .toLowerCase() .startsWith(inputValue.toLowerCase() ) ); } } /> // When season is set through handleSeasonChange, show the "Fetch data" button { season && ( <button className="fetch-data" onClick={() => getData()}>Fetch data</button> ) } </> </> )} Here’s the result!

We’re in a very good place with our block. We can render it in the block editor and the front end of the site. We can fetch data from an external API based on a selection of settings we created that filters the data. It’s pretty darn functional!

But there’s another thing we have to tackle. Right now, when we save the page or post that contains the block, the settings we selected for the block reset. In other words, those selections are not saved anywhere. There’s a little more work to make those selections persistent. That’s where we plan to go in the next article, so stay tuned.

Creating a Settings UI for a Custom WordPress Block originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

DigitalOcean Welcomes Cloudways to the Family

Wed, 11/16/2022 - 1:38pm

Hey folks! If you’ve been keeping up with the latest DigitalOcean news, you might be aware that we recently announced our acquisition of a company called Cloudways. In case you’re curious about what this means, we thought it might be helpful to share a short description of Cloudways and why we’re pumped to have them join the DO and CSS-Tricks family!

What is Cloudways?

Many of the technologies and tricks we love at CSS-Tricks make it easier for us and you to design cool websites and build applications. One of the reasons DigitalOcean was excited to team up with CSS-Tricks is that we love helping developers and small technology-powered businesses do what they love. At DigitalOcean and CSS-Tricks, we strive to do this through education and products that make your lives easier.

And this is why Cloudways was so interesting to our team. Cloudways offers managed hosting right on top of a cloud provider. So, in addition to the 24/7 support, back-ups, monitoring, SSL, optimized caching, and other benefits you get from a managed host, you also get to deploy from Cloudways on a variety of cloud providers (including DigitalOcean, among others, of course!) in a few easy clicks.

That means you gain a bunch of things, like speedy CDN delivery, serverless functions, and Kubernetes — basically everything you’d want from a cloud provider — baked right into a managed hosting plan that lets you deploy a new site in minutes.

What’s next?

We are happy to go more in-depth into Cloudways offerings and how they might be relevant to designers and front-end developers if that’s helpful. Let us know in the comments if you’d like to learn more!

If you’re ready to start exploring, you can test out Cloudways with a $50 credit on us!

To Shared LinkPermalink on CSS-Tricks

DigitalOcean Welcomes Cloudways to the Family originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

Finding Front-End Development Scholarships

Wed, 11/16/2022 - 4:11am

I’m often asked where to learn web development. The answer varies, of course, and we’ve published a few posts on the topic over the years, the most recent of which was Chris taking a stab at different learning paths in 2020.

The answer doesn’t have to be school. But sometimes it is, and if your goal is to earn a degree in front-end web development from a college or university, there are other questions you probably have. One of those is probably how the heck do I afford tuition? That’s the second most-asked question I get from the students I teach at my little city college. (Well, that and if they can get an extension on an assignment.)

Scholarships! That’s the golden ticket, right? Unlike a loan, a scholarship is money you don’t have to pay back. There are strings attached. You might need to write an essay with your application, demonstrate a certain level of academic success, or even complete the program before the any funds are dispersed.

Where to look for scholarships

Good gosh, there are practically hundreds of sites that turn up with a simple online search. The problem is that most of them are aggregate sites littered with ads and affiliate links, many of which busted years ago.

That makes finding a legit source of scholarships like finding a needle in the proverbial haystack. It’s a little easier if your goal is to sign up for some sort of coding bootcamp because those usually offer needs-based discounts up front. But that doesn’t help afford a college degree.

Besides an online search, your best bet for finding scholarships might be on major corporate websites, like Google and Microsoft, both of which offer annually recurring scholarships for a number of programs related to technology.

Finding front-end scholarships

As great as big companies can be for scholarships, you might find it tough to find a scholarship that’s explicitly for front-end development. That’s because many scholarships are only loosely tied to front-end development. Many of the scholarships I’ve found are more like:

  • Graphic communications
  • Media design
  • Digital media
  • Interactive design
  • Computer science
  • Information technology
  • Video game design

That’s a bummer because you’re essentially applying for a scholarship that’s open to other fields not directly related to front-end development.

And a certain scholarship offered this year might not be offered next year. It all depends on who is doling out the money and how much money they have to give. Often it’s going to be some private foundation, endowment, or small company that offers the most promising scholarships, and it could be a one-time deal. I found that many of the most enticing front-end scholarships ended years ago, but are still popping up all over those pesky aggregate sites that claim to have updated information for the current year.

Some scholarships worth looking at

I went down a few rabbit holes trying to find academic scholarships that specifically say “front-end development” or “web design” in their materials. Again, different scholarships have different strings attached to them and what I found today could be gone tomorrow.

That said, here’s a few (in no particular order) that passed my filters:

ScholarshipWho it’s forHow much it offersBOWEN Web Design Scholarship Full or part-time students, with a declared major related to web design or development.$1,500Chee Web Development ScholarshipStudents embarking on a web-related certificate or undergraduate degree.$1,000The Web Development of Tomorrow ScholarshipLGBTQ individuals$1,000Lounge Lizard Web Design Students at an accredited school, or accepted to begin school at an accredited school within 6 months of application.$1,000Digital Excellence Awards ScholarshipStudents under 25 in an accredited school looking for an education in web design, digital marketing, SEO, or computer science.$1,000Net Solutions Annual ScholarshipStudents enrolled in a four-year college or university in the U.S., U.K., Canada, or Australia.$2,500

There could be more! These were just the ones I found. Link me up to others you know of in the comments and I’ll check ’em out to add to the list.

Front-end adjacent scholarships

If you’re willing to apply for scholarships that are less directly tied to front-end development, there are more to consider:

Online programs that offer scholarships

Not attending a college or university? Coding bootcamps are all the rage, or so I hear, and many of them are offered online with discounts and scholarship opportunities. I can’t personlly vouch for each and every bootcamp out there. If you’re interested in taking one — like maybe Fullstack Academy, Udacity, Coding Dojo, or any of the learning paths at Frontend Masters — then it’s worth a quick check for financial help.

We need better scholarships for budding developers

Seriously! For all the college and university students I know who are enrolled in a front-end development program, there is scant resourcing available to make their education more affordable — at least ones without stipulations for which school you’re attending or that are directly related to the front-end field.

Part of me is tempted to go on some sort of rant, but instead what I’ll say is this is a ripe area for private individuals and companies to step in and make a difference. Sure, there are lots of ways to “give back” and it doesn’t have to be a scholarship.

But I see a big hole here and I imagine it’s relatively easy for any mid-sized company to hand out $500-$1,000 once a year to promote education. It’s not only good for students and good for the web, but most likely good for the company, too.

Different types of front-end scholarships

Every scholarship I found is either directly tied to front-end development (or web design), or groups front-end development with other loosely-related disciplines. How cool would it be to see scholarships that are specifically for front-end and geared toward front-end disciplines?!

There are so many areas we could support…

  • Accessibility
  • Content management systems
  • Design systems
  • Front-end curriculum design
  • Inclusive design
  • Interoperability
  • Specifications and documentation
  • Technical writing
  • Web performance

…just to name a few.

Finding Front-End Development Scholarships originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

Behind the CSScenes, November 2022

Tue, 11/15/2022 - 5:54am

Is it Fall? Winter? I don’t know, but I woke up with snow in the front yard this morning and felt like it was time to write a little update about what’s been happening around CSS-Tricks this past month, as we’re known to do from time to time.

First up is the CSS-Tricks Newsletter! It’s starting to feel like we’re getting our rhythm down after months of hiatus. The last edition went out at the very end of October. That’s the third consecutive month we’ve been able to shoot it out which I’d call a big win for consistency. Nah, it’s not the weekly cadence we had before, but that’s something we’re aspiring to as our team continues to establish itself.

Speaking of which — we have a new team member! We brought Andrea Anderson on board. She’s a well-established technical editor and we’re lucky as heck she’s here. While she might work on a CSS-Tricks piece from time to time, her main focus is working on content that’s integrated into DigitalOcean’s Community site.

Oh, and while we’re on the topic of DigitalOcean’s Community, check out this Developer Markepear post deep-diving into DigitalOcean’s writing process. Seriously, it’s an incredibly deep dive that gets into the way tutorials are outlined and structured, the UX of navigating the tutorial archives, and even the delicate interplay between the content and advertising in each article.

I really like how DigitalOcean’s tutorials are described as “give-first” content that “has a smell of value all over it.” It speaks volumes about the team’s work ethic, which I can personally attest is top-notch. It’s really the reason DigitalOcean and CSS-Tricks make a great match.

Advertising is also pretty top-of-mind for us right now. When we ran a survey the other month, we knew that there’d be concerns about how CSS-Tricks ads would be affected after the DigitalOcean acquisition. Would we remove them? Make them all about DigitalOcean? Keep everything as-is? I mean, CSS-Tricks has traditionally relied on an advertising model to keep the lights on, but now that it’s backed by a company, how much do we really need to rely on ads at all?

Turns out many of you like the ads, according to the survey. They’re sort of like product recommendations baked into the site, and I think that’s a testament to Chris’s effort to make sure ads are (1) promoting good stuff and (2) are relevant to the front-end work we do. Case in point: we recently swapped out a bunch of DigitalOcean ads to promote Cloudways hosting after DigitalOcean acquired it. Those ads didn’t do so well, so we swapped the DigitalOcean ones back in, which were already doing quite well.

(The advertised deal is pretty darn good, by the way… $200 in free credits to spin up your project.)

The work to move CSS-Tricks from WordPress to the same CMS the DigitalOcean Community uses for its content is still in progress. A lot of the work is still mapping WordPress content fields to the new CMS. That’s no trivial task when we’re talking about a website with 7,000-odd articles over a 15-year span. That’s going well, as is the initial site architecture. Next up, we need to figure out how we’re handling WordPress blocks, replicating their features, and creating an inventory of all that we need to carry over. Phew!

New faces!

As always, we tend to have a few new faces on the site each month as we work with new guest authors. This month, we welcomed Krzysztof Gonciarz and Lorenzo Bonannella. Check out their articles and give ’em a high five for sharing their work. It takes a lot of work to write, not to mention some courage to put your ideas in front of other people. So, thanks a bunch Krzysztof and Lorenzo!

Meet Mojtaba Seyedi

I thought catching up with one of our long-time writers would be a nice way to cap off this month’s update. And few people have contributed as many articles to CSS-Tricks as Mojtaba Seyedi. You may not see his name pop up in the archives all that much, but it’s only because he spends so much time in the Almanac.

I asked Mojtaba a few questions about his work and he graciously responded with these answers…

Your very first article with us was a roundup of plugins for the Sublime Text editor way back in 2017. What made you think to publish it on CSS-Tricks?

I used to be very passionate about the Sublime Text editor and its plugins. I could always find a plugin to ease the pain whenever I was tired of doing repetitive tasks. I would show my co-workers how interesting whatever plugin I was using was and encourage them to use it.

One of my New Year’s resolutions back then — in 2017 — was to publish an article on CSS-Tricks. I always thought the idea had been highly technical. It never occurred to me I could simply create a list of Sublime Text plugins that I happened to find useful for development! Nowadays, I can see how the high bar I had set was preventing me from writing about something that I loved.

There was a brief moment when I considered giving up on that first article. I had psyched myself out thinking that there were tons of other posts already covering the exact same thing. But out of curiosity, I Googled some of the top Sublime Text plugin posts, and surprisingly, I didn’t see any of the plugins I was writing about. So, that’s how I submitted my first article on this website!

You’ve written a total of 35 articles for CSS-Tricks, 33 of which are in the Almanac. What do you enjoy about writing technical information like that?

Almanac entries are referenceable. We keep coming back to them to check the syntax of a property or a selector. For example, we might need to visit the background shorthand property to remember whether the background-position value goes before or after the slash (/). References never get old, which is why the Almanac is special to me.

Along the same lines, documentation is challenging. One of the challenges of writing for the CSS-Tricks Almanac is reading and understanding the W3C’s specifications. For example, when I wanted to write about the mask-border property, the CSS spec was practically my only source. I needed to figure out all the aspects of that module and how different values behave in different situations because there were scant examples in the wild. I enjoy that sort of challenge and feel great when I can turn my findings into something tangible that other developers can understand and use in their own work.

There’s also the joy of completeness. Documentation allows me to get deep into details that might not make it in a typical article. I get satisfaction when I’m able to grasp a property or selector and explain it in my own words. The CSS-Tricks Alamanc gives me that opportunity.

What can you say about the editing process for those who haven’t gone through it?

First, enjoy a clean and easy process. The CSS-Tricks editorial team will help you improve it and make it better than you can on your own.

Also, be sure to edit your draft first. Always edit the article yourself before submitting it. The more ready your writing is, the more time it gives the editor to help you improve your work. If the editor needs to spend a lot of time fixing basic grammar and spelling, that’s time that could have been spent pushing the idea further with feedback and other considerations.

And, of course, learn from your mistakes. Be open to learning while you’re in the editing process. The editorial team here is very experienced and helpful. I try to review what they have changed in my article and put them into practice in my next writings. I would love to thank Geoff, from whom I have learned a lot about technical writing.

Do you have any tips for someone thinking about submitting an article proposal?

Do not overthink the idea. Your article doesn’t have to be rocket science. Anything you know well enough to write about can be helpful to others.

Another piece of advice: do not underestimate yourself. When Chris Coyier invited the community to contribute to the CSS-Tricks Almanac, I told myself there were many more qualified people who could do that, even though I had experience writing CSS docs. And yes, many folks were (and maybe still are) more knowledgeable than me. But as it turned out, I could be a part of this because I was willing to try.

Another big deal is to not worry about repeating others. Your idea doesn’t have to be unique. You can write about something others have written about in a new and different way. Your point of view and perspective matter! Your approach to solving a problem and how you explain it might be different in a super helpful way.

And finally… what’s your favorite CSS trick?

My favorite trick used to be centering an element by setting display: table on it and letting auto margins do the rest. Nowadays, with CSS becoming much more awesome, I can do the same trick with min-content and without an additional wrapper.

I’m sure there is a blog post or article about this same topic somewhere on the web. But I would like to write about it here on CSS-Tricks. See? I want to share my own perspective with you and I want to explain it in my own way.

Have something you want to share on CSS-Tricks? Send us your pitch!

Behind the CSScenes, November 2022 originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

Classy and Cool Custom CSS Scrollbars: A Showcase

Mon, 11/14/2022 - 3:54am

In this article we will be diving into the world of scrollbars. I know, it doesn’t sound too glamorous, but trust me, a well-designed page goes hand-in-hand with a matching scrollbar. The old-fashioned chrome scrollbar just doesn’t fit in as much.

We will be looking into the nitty gritty details of a scrollbar and then look at some cool examples.

Components of a scrollbar

This is more of a refresher, really. There are a bunch of posts right here on CSS-Tricks that go into deep detail when it comes to custom scrollbar styling in CSS.

To style a scroll bar you need to be familiar with the anatomy of a scrollbar. Have a look at this illustration:

The two main components to keep in mind here are:

  1. The track is the background beneath the bar.
  2. The thumb is the part that the user clicks and drags around.

We can change the properties of the complete scrollbar using the vendor-prefixed::-webkit-scrollbar selector. We can give the scroll bar a fixed width, background color, rounded corners… lots of things! If we’re customizing the main scrollbar of a page, then we can use ::-webkit-scrollbar directly on the HTML element:

html::-webkit-scrollbar { /* Style away! */ }

If we’re customizing a scroll box that’s the result of overflow: scroll, then we can use ::-webkit-scrollbar on that element instead:

.element::-webkit-scrollbar { /* Style away! */ }

Here’s a quick example that styles the HTML element’s scrollbar so that it is wide with a red background:

CodePen Embed Fallback

What if we only want to change the scrollbar’s thumb or track? You guessed it — we have special prefixed pseudo-elements for those two: ::-webkit-scrollbar-thumb and ::-webkit-scrollbar-track, respectively. Here’s an idea of what’s possible when we put all of these things together:

CodePen Embed Fallback

Enough brushing up! I want to show you three degrees of custom scrollbar styling, then open up a big ol’ showcase of examples pulled from across the web for inspiration.

Simple and classy scrollbars

A custom scrollbar can still be minimal. I put together a group of examples that subtly change the appearance, whether with a slight color change to the thumb or track, or some light styling to the background.

CodePen Embed Fallback

As you can see, we don’t have to go nuts when it comes to scrollbar styling. Sometimes a subtle change is all it takes to enhance the overall user experience with a scrollbar that matches the overall theme.

Cool eye-catching scrollbars

But let’s admit it: it’s fun to go a little overboard and exercise some creativity. Here are some weird and unique scrollbars that might be “too much” in some cases, but they sure are eye-catching.

CodePen Embed Fallback One more…

How about we take the scrollbar for a spin in a train for the thumb and tracks for the, well, the track!

CodePen Embed Fallback

Classy and Cool Custom CSS Scrollbars: A Showcase originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

CSS Grid and Custom Shapes, Part 3

Fri, 11/11/2022 - 4:42am

After Part 1 and Part 2, I am back with a third article to explore more fancy shapes. Like the previous articles, we are going to combine CSS Grid with clipping and masking to create fancy layouts for image galleries.

CSS Grid and Custom Shapes series

Should I read the previous articles before?

It’s not mandatory but highly recommended to cover as many tricks as possible. You can also read them in any order, but following along in chronological is a good idea to see how we arrived here.

Enough talking, let’s jump straight to our first example.

The Die Cut Photo Gallery CodePen Embed Fallback

Before digging into the CSS, let’s check the markup:

<div class="gallery"> <img src="..." alt="..."> <img src="..." alt="..."> <img src="..." alt="..."> <img src="..." alt="..."> </div>

Nothing but a few <img> tags in a div wrapper, right? Remember, the main challenge for this series is to work with the smallest amount of HTML possible. All the examples we’ve seen throughout this series use the exact same HTML markup. No extra divs, wrappers, and whatnot. All that we need are images contained in a wrapper element.

Let’s check the CSS now:

.gallery { --g: 6px; /* the gap */ display: grid; width: 450px; /* the size */ aspect-ratio: 1; /* equal height */ grid: auto-flow 1fr / repeat(3, 1fr); gap: var(--g); } .gallery img:nth-child(2) { grid-area: 1 / 2 / span 2 / span 2; } .gallery img:nth-child(3) { grid-area: 2 / 1 / span 2 / span 2; }

Basically, this is a square grid with three equal columns. From there, all that’s happening is the second and third images are explicitly placed on the grid, allowing the first and last images to lay out automatically around them.

This automatic behavior is a powerful feature of CSS Grid called “auto-placement”. Same thing with the number of rows — none of them are explicitly defined. The browser “implicitly” creates them based on the placement of the items. I have a very detailed article that explores both concepts.

You might be wondering what’s going on with those grid and grid-area property values. They look strange and are tough to grok! That’s because I chose the CSS grid shorthand property, which is super useful but accepts an unseemly number of values from its constituent properties. You can see all of them in the Almanac.

But what you really need to know is this:

grid: auto-flow 1fr / repeat(3, 1fr);

…is equivalent to this:

grid-template-columns: repeat(3, 1fr); grid-auto-rows: 1fr; You can also use your favorite DevTools for further proof.

Same for the grid-area property. If we open DevTools and inspect our declaration: grid-area: 1/2/span 2/span 2; you will get the following:

grid-area: 1 / 2 / span 2 / span 2;

…that is the same as writing all this out:

grid-row-start: 1; /* 1st row */ grid-column-start: 2; /* 2nd column */ grid-row-end: span 2; /* take 2 rows */ grid-column-end: span 2; /* take 2 columns */

Same deal for the other grid-area declaration. When we put it all together, here’s what we get:

Yes, the second and third images are overlapped in the middle. That’s no mistake! I purposely spanned them on top of one another so that I can apply a clip-path to cut a portion from each one and get the final result:

How do we do that? We can cut the bottom-left corner of the second image (img:nth-child(2)) with the CSS clip-path property:

clip-path: polygon(0 0, 100% 0, 100% 100%, calc(50% + var(--g) / 4) 100%, 0 calc(50% - var(--g) / 4))

And the top-right corner of the third one:

clip-path: polygon(0 0, calc(50% - var(--g) / 4) 0, 100% calc(50% + var(--g) / 4), 100% 100%, 0 100%);

I know, I know. That’s a lot of numbers and whatnot. I do have an article that details the technique.

That’s it, we have our first grid of images! I added a grayscale filter on the <img> selector to get that neat little hover effect.

The Split Image Reveal

Let’s try something different. We can take what we learned about clipping the corner of an image and combine it with a nice effect to reveal the full image on hover.

CodePen Embed Fallback

The grid configuration for this one is less intense than the last one, as all we need are two overlapping images:

.gallery { display: grid; } .gallery > img { grid-area: 1 / 1; width: 350px; /* the size */ aspect-ratio: 1; /* equal height */ }

Two images that are the same size are stacked on top of each other (thanks to grid-area: 1 / 1).

The hover effect relies on animating clip-path. We will dissect the code of the first image to see how it works, then plug the same thing into the second image with updated values. Notice, though that we have three different states:

  1. When no images are hovered, half of each image is revealed.
  2. When we hover over the first image, it is more fully revealed but retains a small corner clip.
  3. When we hover over the second image, the first one has only a small triangle visible.

In each case, we have a triangular shape. That means we need a three-point polygon for the clip-path value.

What? The second state isn’t a triangle, but more of a square with a cut corner.

You are right, but if we look closely we can see a “hidden” triangle. Let’s add a box-shadow to the images.

CodePen Embed Fallback

A ha! Did you notice it?

What sort of magic is this? It’s a little known fact that clip-path accepts values outside the 0%-100% range, which allows us to create “overflowing” shapes. (Yes, I just coined this. You’re welcome.) This way, we only have to work with three points instead of the five it would take to make the same shape from the visible parts. Optimized CSS for the win!

This is the code after we plug in the polygon values into the clip-path property:

.gallery > img:first-child { clip-path: polygon(0 0, calc(100% + var(--_p)) 0 , 0 calc(100% + var(--_p))) } .gallery > img:last-child { clip-path: polygon(100% 100%, 100% calc(0% - var(--_p)), calc(0% - var(--_p)) 100%) }

Notice the --_p variable. I’m using that to optimize the code a bit as we add the hover transition. Instead of updating the whole clip-path we only update this variable to get the movement. Here is a video to see how the points should move between each state:

We can take slap a transition on the <img> selector, then update the --_p variable on the states to get the final effect:

.gallery { --g: 8px; /* the gap */ } .gallery > img { /* etc. */ --_p: calc(-1 * var(--g)); transition: .4s .1s; } .gallery:hover > img:last-child, .gallery:hover > img:first-child:hover{ --_p: calc(50% - var(--g)); } .gallery:hover > img:first-child, .gallery:hover > img:first-child:hover + img { --_p: calc(-50% - var(--g)); }

If we don’t consider the gap (defined as --g in the code) between the images, then the three values of --_p are 0%, 50%, and -50%. Each one defines one of the states we explained previously.

The Pie Image Reveal

Let’s increase the difficulty level from that last one and try to do the same trick but with four images instead of two.

CodePen Embed Fallback

Cool, right? Each image is a quarter of a circle and, on hover, we have an animation that transforms an image into a full circle that covers the remaining images. The effect may look impossible because there is no way to rotate points and transform them to fill the circle. In reality, though, we are not rotating any points at all. It’s an illusion!

For this example, I will only focus on the clip-path animation since the configuration of the grid is the same as the previous example: four equally-sized images stacked on top of each other.

And a video worth a boring and long explanation:

The clip-path is formed by seven points, where three of them are in a fixed position and the others move as shown in the video. The effect looks less cool when it’s running slowly but we can see how the clip-path morphs between shapes.

The effect is a little better if we add border-radius and we make it faster:

And by making it even faster like in the original example, we get the perfect illusion of one quarter of a circle morphing into a full circle. Here’s the polygon value for our clip-path on the first image in the sequence:

.gallery > img:nth-child(1) { clip-path: polygon(50% 50%, calc(50% * var(--_i, 0)) calc(120% * var(--_i, 0)), 0 calc(100% * var(--_i, 0)),0 0, 100% 0, 100% calc(100% * var(--_i, 0)), calc(100% - 50% * var(--_i, 0)) calc(120% * var(--_i, 0))); } .gallery > img:hover { --_i: 1; }

As usual, I am using a variable to optimize the code. The variable will switch between 0 and 1 to update the polygon.

The same goes for the others image but with a different clip-path configuration. I know that the values may look hard to decipher but you can always use online tools like Clippy to visualize the values.

The Mosaic of Images

You know mosaics, right? It’s an art style that creates decorative designs out of smaller individual pieces, like colored stones. But it can also be a composite image made up of other smaller images.

And, you guessed it: we can totally do that sort of thing in CSS!

CodePen Embed Fallback

First, let’s imagine what things are like if clip-path were taken out of the mix and all we had were five overlapping images:

I am cheating a little in this video because I am inspecting the code to identify the area of each image, but this is what you need to do in your head. For each image, try to complete the missing part to see the full rectangle and, with this, we can identify the position and size of each one.

We need to find how many columns and rows we need for the grid:

  1. We have two big images placed next to each other that each fill half the grid width and the full grid height. That means will probably need two columns (one for both images) and one row (for the full height of the grid).
  2. We have the image in the middle that overlaps the two other images. That means we actually need four columns instead of two, though we still only need the one row.
  3. The last two images each fill half the grid, just like the first two images. But they’re only half the height of the grid. We can use the existing columns we already have, but we’re going to need two rows instead of one to account for these images being half the grid height.
That leaves us with a tidy 4×2 grid.

I don’t want you to think that the way I sliced this up is the only way to do it. This is merely how I’ve made sense of it. I am sure there are other configurations possible to get the same layout!

Let’s take that information and define our grid, then place the images on it:

.gallery { display: grid; grid: repeat(2, 1fr) / repeat(4, 1fr); aspect-ratio: 2; } .gallery img:nth-child(1) { grid-area: 1 / 1 / span 2 / span 2; } .gallery img:nth-child(2) { grid-area: 1 / 2 / span 2 / span 2; } .gallery img:nth-child(3) { grid-area: span 2 / span 2 / -1 / -1; } .gallery img:nth-child(4) { grid-area: 2 / 1 / span 1 / span 2; } .gallery img:nth-child(5) { grid-area: span 1 / span 2 / -1 / -1; }

I think you get the idea of what’s happening here now that we’ve seen a few examples using the same approach. We define a grid and place images on it explicitly, using grid-area so the images overlap.

OK, but the aspect-ratio is different this time.

It is! If you get back to the reasoning we made, we have the first two images that are square next to each other having the same size. This means that the width of the grid needs to be equal to twice its height. Hence, aspect-ratio: 2.

Now it’s time for the clip-path values. We have four triangles and a rhombus.

We’re only showing the three unique shapes we’re making instead of the five total shapes.

Again, I’m using Clippy for all this math-y stuff. But, honestly, I can write many simple shapes by hand, having spent several years working closely with clip-path, and I know you can too with practice!

The Complex Mosaic of Images

Let’s increase the difficulty and try another mosaic, this time with less symmetry and more complex shapes.

CodePen Embed Fallback

Don’t worry, you will see that it’s the same concept as the one we just made! Again, let’s imagine each image is a rectangle, then go about defining the grid based on what we see.

We’ll start with two images:

They are both squares. The first image is equal to half the size of the second image. The first image takes up less than one half of the grid width, while the second image takes up more than half giving us a total of two columns with a different size (the first one is equal to half the second one). The first image is half the height, so let’s automatically assume we need two rows as well.

Let’s add another image to the layout

This one makes things a bit more complex! We need to draw some lines to identify how to update the grid configuration.

We will move from a 2×2 grid to four columns and three rows. Pretty asymmetric, right? Before we try to figure out that complete sizing, let’s see if the same layout holds up when we add the other images.

Looks like we still need more rows and columns for everything to fall into place. Based on the lines in that image, we’re going to have a total of five columns and four rows.

The logic is simple even though the layout is complex, right? We add the images one by one to find the right configuration that fits everything. Now we need to identify the size of each column and row.

If we say the smallest row/column is equal to one fraction of the grid (1fr) we will get:

grid-template-columns: 1fr 1fr 2fr 3fr 5fr;

…for the columns, and:

grid-template-rows: 3fr 1fr 2fr 2fr;

…for the rows. We can consolidate this using the grid shorthand property again:

grid: 3fr 1fr 2fr 2fr / 1fr 1fr 2fr 3fr 5fr;

You know the drill! Place the images on the grid and apply a clip-path on them:

.gallery img:nth-child(1) { grid-area: 1 / 1 /span 2 / span 3; clip-path: polygon(0 0, 100% 0, 0 100%); } .gallery img:nth-child(2) { grid-area: 1/2/span 3/span 3; clip-path: polygon(50% 0, 100% 50%, 50% 100%, 0 50%); } .gallery img:nth-child(3) { grid-area: 1 / span 2 / -1 / -1; clip-path: polygon(0 0, 100% 0, 100% 100%); } .gallery img:nth-child(4) { grid-area: span 3 / 1 / -1 / span 3; clip-path: polygon(25% 0, 100% 60%, 50% 100%, 0 100%, 0 20%); } .gallery img:nth-child(5) { grid-area: span 3/span 3/-1/-1; clip-path: polygon(50% 0, 100% 100%, 0 100%); }

We can stop here and our code is fine, but we will do a little more to optimize the clip-path values. Since we don’t have any gaps between our images, we can use the fact that our images overlap to slim things down. Here is a video to illustrate the idea:

As you can see, the image in the middle (the one with the camera) doesn’t need a clip-path. because the other images overlap it, giving us the shape without any additional work! And notice that we can use the same overflowing three-point clip-path concept we used earlier on the image in the bottom-left to keep the code smaller there as well.

In the end, we have a complex-looking grid of images with only four clip-path declarations — all of them are three-point polygons!

CodePen Embed Fallback Wrapping up

Wow, right? I don’t know about you, but I never get bored of seeing what CSS can do these days. It wasn’t long ago that all of this would have taken verbose hackery and definitely some JavaScript.

Throughout this series, we explored many, many different types of image grids, from the basic stuff to the complex mosaics we made today. And we got a lot of hands-on experience working with CSS clipping — something that you will definitely be able to use on other projects!

But before we end this, I have some homework for you…

Here are two mosaics that I want you to make using what we covered here. One is on the “easier” side, and the other is a bit tricky. It would be really awesome to see your work in the comments, so link them up! I’m curious to see if your approach is different from how I’d go about it!

CSS Grid and Custom Shapes, Part 3 originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

What CSS Do You Absolutely Have to Know in 2022?

Tue, 11/08/2022 - 3:51am

Sacha Greif openly wondered whether CSS has gotten to be, you know, too big. With all the goodies that’ve shipped in browsers the past couple of years — container queries! relative color syntax! cascade layers! logical properties! ranges in media queries! individual transforms! :has() selector! — and all of what’s on the possible horizon — CSS Toggles! nesting! color mixing! scroll-linked animations! scoped styles! — there’s definitely a different learning curve for CSS these days for new and seasoned front-enders alike.

There may have been a time when it was possible to know most CSS properties and how they work. Those days are long-gone, at least for an old hand like me. But that sort of begs the question: what CSS do you absolutely have to know?

Vincas Stonys recently took a stab at a list. Chris put one together based on features released since CSS3. You probably have an idea of what you would include in a list. If I had to put a Top 5 together and limit myself to only properties and selectors, it might look something like this…


I can’t say enough about the writing-mode property. What makes it important — especially from a learning perspective — is that it sets you up for inclusive principles that account for crafting layouts, regardless of the user’s language. A good understanding of writing-mode is going to lead to an understanding of logical properties and values, and those, in turn, set the stage for understanding document flow and thinking in terms of block, inline, start, and end rather than physical directions.


I have a hard time believing anyone can write good CSS without having a solid grasp on the display property. It’s both a property and a framework for creating layouts. There’s no Flexbox or CSS Grid without it, making it sort of like a gatekeeper to understanding those important features.

Plus, the display property perfectly complements writing-mode. It’s exactly what you’ll need once writing-mode has exposed you to document flow and logical directions. You’re going to need a property to either change an element’s normal flow (like changing a block element to an inline one) or start laying things out (like creating a flexible layout context) and that is where display comes into play.

margin / padding / border

Ugh, I’m totally cheating here but think learning margin, padding, and border together is sort of unavoidable. They’re all parts of The Box Model, all help with spacing and styling, and all require getting acquainted with CSS length units. Knowing what these properties are desgined to do and how they contribute to the computed size of an element certainly gives you a lot more styling control, and dispels any confusion about why an element is the size that it is — a common CSS headache!

::before and ::after

Another one where I’m cheating a bit. Yes, ::before and ::after are two individual pseudo-elements, but again, I can’t imagine learning about one without the other. It’s a two-fer!

I remember how mind-blowing it was for me to learn that these existed and can be used to create everything from cool UI effects to complete single-div illustrations. It opens up new possibilities and provides a first peek at how powerful CSS really is.


Oof, I’m already at my fifth and final item in the list and feel like there’s still so much CSS goodness that belongs here. But if I have to choose one last thing, it would be media queries. Why? Because it’s a prime ingredient for creating fluid, flexible layouts and different viewing contexts. Container queries might wind up knocking this off my list as it matures, but for now, @media is a great primer for responsive design.

Beyond that, @media is a nice first step into the conditional qualities of CSS. Whether we’re writing a query based on the type of device thats being used (e.g., screen or print) or a when the browser’s viewport meets a certain criteria (e.g., width >= 768px), the @media syntax is incredibly useful for creating layouts that are optimized for different conditions.

Oh, and we haven’t even touched on how @media relates to accessibility, thanks to its ability to apply styles based on a user’s preferences (e.g., prefers-reduced-motion). So, in addition to crafting conditional layouts, media queries are a nice next step toward understanding inclusive design.

Honerable mentions

Distilling CSS into a list of five must-know properties and selectors is tough, especially now that CSS more powerful today than it was, say, even five years ago. There are a number of other items I really wanted to include, like (in no particular order):

  • calc()
  • has()
  • color
  • font
  • overflow
  • position (especially this)
  • z-index

But I stand by my choices. Learning CSS is more important than memorizing a list of properties. It’s a journey and I think the five I chose carve a nice little learning path that sets the stage for writing good style rules and next steps for diving deeper into CSS.

Alright, tell me yours!

Disagree with my list? You should! I’ll bet you have some smart opinions and I want to see how you would have rounded out a Top 5 list.

What CSS Do You Absolutely Have to Know in 2022? originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

Managing CSS Styles in a WordPress Block Theme

Mon, 11/07/2022 - 4:05am

The way we write CSS for WordPress themes is in the midst of sweeping changes. I recently shared a technique for adding fluid type support in WordPress by way of theme.json, a new file that WordPress has been pushing hard to become a central source of truth for defining styles in WordPress themes that support full-site editing (FSE) features.

Wait, no style.css file? We still have that. In fact, style.css is still a required file in block themes, though its role is greatly reduced to meta information used for registering the theme. That said, the fact is that theme.json is still in active development, meaning we’re in a transitional period where you might find styles defined there, in styles.css or even at the block level.

So, what does styling actually look like in these WordPress FSE days? That’s what I want to cover in this article. There’s a lack of documentation for styling block themes in the WordPress Theme Developer Handbook, so everything we’re covering here is what I’ve gathered about where things currently are as well as discussions about the future of WordPress theming.

The evolution of WordPress styles

The new developmental features that are included in WordPress 6.1 get us closer to a system of styles that are completely defined in theme.json, but there is still be plenty of work to do before we can fully lean on it. One way we can get an idea of what’s coming in future releases is by using the Gutenberg plugin. This is where experimental features are often given a dry run.

Another way we can get a feel for where we are is by looking at the evolution of default WordPress themes. To date, there are three default themes that support full-site editing:

But don’t start trading the CSS in style.css for JSON property-value pairs in theme.json just yet. There are still CSS styling rules that need to be supported in theme.json before we think about doing that. The remaining significant issues are currently being discussed with an aim to fully move all the CSS style rules into theme.json and consolidate different sources of theme.json into a UI for for setting global styles directly in the WordPress Site Editor.

The Global Styles UI is integrated with the right panel in the Site Editor. (Credit: Learn WordPress)

That leaves us in a relatively tough spot. Not only is there no clear path for overriding theme styles, but it’s unclear where the source of those styles even come from — is it from different layers of theme.json files, style.css, the Gutenberg plugin, or somewhere else?

Why theme.json instead of style.css?

You might be wondering why WordPress is moving toward a JSON-based definition of styles instead of a traditional CSS file. Ben Dwyer from the Gutenberg development team eloquently articulates why the theme.json approach is a benefit for theme developers.

It’s worth reading Ben’s post, but the meat is in this quote:

Overriding CSS, whether layout, preset, or block styles, presents an obstacle to integration and interoperability: visual parity between the frontend and editor becomes more difficult to maintain, upgrades to block internals may conflict with overrides. Custom CSS is, furthermore, less portable across other block themes.

By encouraging theme authors to use theme.json API where possible, the hierarchy of “base > theme > user” defined styles can be resolved correctly.

One of the major benefits of moving CSS to JSON is that JSON is a machine-readable format, which means it can be exposed in the WordPress Site Editor UI by fetching an API, thus allowing users to modify default values and customize a site’s appearance without writing any CSS at all. It also provides a way to style blocks consistently, while providing a structure that creates layers of specificity such that the user settings take the highest priority over those defined in theme.json. That interplay between theme-level styles in theme.json and the user-defined styles in the Global Styles UI is what makes the JSON approach so appealing.

Developers maintain consistency in JSON, and users gain flexibility with code-less customizations. That’s a win-win.

There are other benefits, for sure, and Mike McAlister from WP Engine lists several in this Twitter thread. You can find even more benefits in this in-depth discussion over at the Make WordPress Core blog. And once you’ve given that a read, compare the benefits of the JSON approach with the available ways to define and override styles in classic themes.

Defining styles with JSON elements

We’ve already seen a lot of progress as far as what parts of a theme theme.json is capable of styling. Prior to WordPress 6.1, all we could really do was style headings and links. Now, with WordPress 6.1, we can add buttons, captions, citations, and headings to the mix.

And we do that by defining JSON elements. Think of elements as individual components that live in a WordPress block. Say we have a block that contains a heading, a paragraph, and a button. Those individual pieces are elements, and there’s an elements object in theme.json where we define their styles:

{ "version": 2, "settings": {}, // etc. "styles": { // etc. "elements": { "button": { ... }, "h1": { ... }, "heading": { ... }, }, }, "templateParts": {} }

A better way to describe JSON elements is as low-level components for themes and blocks that do not need the complexity of blocks. They are representations of HTML primitives that are not defined in a block but can be used across blocks. How they are supported in WordPress (and the Gutenberg plugin) is described in the Block Editor Handbook and this Full Site Editing tutorial by Carolina Nymark.

For example, links are styled in the elements object but are not a block in their own right. But a link can be used in a block and it will inherit the styles defined on the elements.link object in theme.json. This doesn’t fully encapsulate the definition of an element, though, as some elements are also registered as blocks, such as the Heading and Button blocks — but those blocks can still be used within other blocks.

Here is a table of the elements that are currently available to style in theme.json, courtesy of Carolina:

ElementSelectorWhere it’s supportedlink<a>WordPress Coreh1 – h6The HTML tag for each heading level: <h1>, <h2>, <h3>, <h4>, <h5> and <h6>WordPress CoreheadingStyles all headings globally by individual HTML tag: <h1>, <h2>, <h3>, <h4>, <h5> and <h6>Gutenberg pluginbutton.wp-element-button.wp-block-button__linkGutenberg plugincaption.wp-element-caption,
.wp-block-audio figcaption,
.wp-block-embed figcaption,
.wp-block-gallery figcaption,
.wp-block-image figcaption,
.wp-block-table figcaption,
.wp-block-video figcaptionGutenberg plugincite.wp-block-pullquote citeGutenberg plugin

As you can see, it’s still early days and plenty still needs to move from the Gutenberg plugin into WordPress Core. But you can see how quick it would be to do something like style all headings in a theme globally without hunting for selectors in CSS files or DevTools.

Further, you can also start to see how the structure of theme.json sort of forms layers of specificity, going from global elements (e.g. headings) to individual elements (e.g. h1), and block-level styles (e.g. h1 contained in a block).

Additional information on JSON elements is available in this Make WordPress proposal and this open ticket in the Gutenberg plugin’s GitHub repo.

JSON and CSS specificity

Let’s keep talking about CSS specificity. I mentioned earlier that the JSON approach to styling establishes a hierarchy. And it’s true. Styles that are defined on JSON elements in theme.json are considered default theme styles. And anything that is set by the user in the Global Styles UI will override the defaults.

In other words: user styles carry more specificity than default theme styles. Let’s take a look at the Button block to get a feel for how this works.

I’m using Emptytheme, a blank WordPress theme with no CSS styling. I’m going to add a Button block on a new page.

The background color, text color, and rounded borders come from the block editor’s default settings.

OK, we know that WordPress Core ships with some light styling. Now, I’m going to switch to the default TT3 theme from WordPress 6.1 and activate it. If I refresh my page with the button, the button changes styles.

The background color, text color, and rounded corner styles have changed.

You can see exactly where those new styles are coming from in TT3’s theme.json file. This tells us that the JSON element styles take precedence over WordPress Core styles.

Now I am going to modify TT3 by overriding it with a theme.json file in a child theme, where the default background color of the Button block is set to red.

The default style for the Button block has been updated to red.

But notice the search button in that last screenshot. It should be red, too, right? That must mean it is styled at another level if the change I made is at the global level. If we want to change both buttons, we could do it at the user level using the Global Styles UI in the site editor.

We changed the background color of both buttons to blue and modified the text as well using the Global styles UI. Notice that the blue from there took precedence over the theme styles!

The Style Engine

That’s a very quick, but good, idea of how CSS specificity is managed in WordPress block themes. But it’s not the complete picture because it’s still unclear where those styles are generated. WordPress has its own default styles that come from somewhere, consumes the data in theme.json for more style rules, and overrides those with anything set in Global Styles.

Are those styles inline? Are they in a separate stylesheet? Maybe they’re injected on the page in a <script>?

That’s what the new Style Engine is hopefully going to solve. The Style Engine is a new API in WordPress 6.1 that is meant to bring consistency to how styles are generated and where styles are applied. In other words, it takes all of the possible sources of styling and is singularly responsible for properly generating block styles. I know, I know. Yet another abstraction on top of other abstractions just to author some styles. But having a centralized API for styles is probably the most elegant solution given that styles can come from a number of places.

We’re only getting a first look at the Style Engine. In fact, here’s what has been completed so far, according to the ticket:

  • Audit and consolidate where the code generates block support CSS in the back end so that they are delivered from the same place (as opposed to multiple places). This covers CSS rules such as margin, padding, typography, colors, and borders.
  • Remove repetitive layout-specific styles and generate semantic class names.
  • Reduce the number of inline style tags we print to the page for block, layout, and element support.

Basically, this is the foundation for establishing a single API that contains all the CSS style rules for a theme, wherever they come from. It cleans up the way WordPress would inject inline styles pre-6.1 and establishes a system for semantic class names.

Further details on the long-term and short-term goals of Style Engine can be found in this Make WordPress Core discussion. You can also follow the tracking issue and project board for more updates.

Working with JSON elements

We talked a bit about JSON elements in the theme.json file and how they are basically HTML primitives for defining default styles for things like headings, buttons, and links, among others. Now, let’s look at actually using a JSON element and how it behaves in various styling contexts.

JSON elements generally have two contexts: the global level and the block level. The global level styles are defined with less specificity than they are at the block level to ensure that block-specific styles take precedence for consistency wherever blocks are used.

Global styles for JSON elements

Let’s look at the new default TT3 theme and examine how its buttons are styled. The following is an abbreviated copy-paste of the TT3 theme.json file (here’s the full code) showing the global styles section, but you can find the original code here.

View code { "version": 2, "settings": {}, // ... "styles": { // ... "elements": { "button": { "border": { "radius": "0" }, "color": { "background": "var(--wp--preset--color--primary)", "text": "var(--wp--preset--color--contrast)" }, ":hover": { "color": { "background": "var(--wp--preset--color--contrast)", "text": "var(--wp--preset--color--base)" } }, ":focus": { "color": { "background": "var(--wp--preset--color--contrast)", "text": "var(--wp--preset--color--base)" } }, ":active": { "color": { "background": "var(--wp--preset--color--secondary)", "text": "var(--wp--preset--color--base)" } } }, "h1": { "typography": { } }, // ... "heading": { "typography": { "fontWeight": "400", "lineHeight": "1.4" } }, "link": { "color": { "text": "var(--wp--preset--color--contrast)" }, ":hover": { "typography": { "textDecoration": "none" } }, ":focus": { "typography": { "textDecoration": "underline dashed" } }, ":active": { "color": { "text": "var(--wp--preset--color--secondary)" }, "typography": { "textDecoration": "none" } }, "typography": { "textDecoration": "underline" } } }, // ... }, "templateParts": {} }

All buttons are styled at the global level (styles.elements.button).

Every button in the Twenty Twenty-Three theme shares the same background color, which is set to the --wp--preset--color--primary CSS variable, or #B4FD55.

We can confirm this in DevTools as well. Notice that a class called .wp-element-button is the selector. The same class is used to style the interactive states as well.

Again, this styling is all happening at the global level, coming from theme.json. Whenever we use a button, it is going to have the same background because they share the same selector and no other style rules are overriding it.

As an aside, WordPress 6.1 added support for styling interactive states for certain elements, like buttons and links, using pseudo-classes in theme.json — including :hover, :focus, and :active — or the Global Styles UI. Automattic Engineer Dave Smith demonstrates this feature in a YouTube video.

We could override the button’s background color either in theme.json (preferably in a child theme since we’re using a default WordPress theme) or in the Global Styles settings in the site editor (no child theme needed since it does not require a code change).

But then the buttons will change all at once. What if we want to override the background color when the button is part of a certain block? That’s where block-level styles come into play.

Block-level styles for elements

To understand how we can use and customize styles at the block level, let’s change the background color of the button that is contained in the Search block. Remember, there is a Button block, but what we’re doing is overriding the background color at the block level of the Search block. That way, we’re only applying the new color there as opposed to applying it globally to all buttons.

To do that, we define the styles on the styles.blocks object in theme.json. That’s right, if we define the global styles for all buttons on styles.elements, we can define the block-specific styles for button elements on styles.block, which follows a similar structure:

{ "version": 2, // ... "styles": { // Global-level syles "elements": { }, // Block-level styles "blocks": { "core/search": { "elements": { "button": { "color": { "background": "var(--wp--preset--color--quaternary)", "text": "var(--wp--preset--color--base)" } } }, // ... } } } }

See that? I set the background and text properties on styles.blocks.core/search.elements.button with two CSS variables that are preset in WordPress.

The result? The search button is now red (--wp--preset--color--quaternary), and the default Button block retains its bright green background.

We can see the change in DevTools as well.

The same is true if we want to style buttons that are included in other blocks. And buttons are merely one example, so let’s look at another one.

Example: Styling headings at each level

Let’s drive all this information home with an example. This time, we will:

  • Style all headings globally
  • Style all Heading 2 elements
  • Style Heading 2 elements in the Query Loop block

First, let’s start with the basic structure for theme.json:

{ "version": 2, "styles": { // Global-level syles "elements": { }, // Block-level styles "blocks": { } } }

This establishes the outline for our global and block-level styles.

Style all headings globally

Let’s add the headings object to our global styles and apply some styles:

{ "version": 2, "styles": { // Global-level syles "elements": { "heading": { "color": "var(--wp--preset--color--base)" }, }, // Block-level styles "blocks": { } } }

That sets the color for all headings to the preset base color in WordPress. Let’s change the color and font size of Heading 2 elements at the global level as well:

{ "version": 2, "styles": { // Global-level syles "elements": { "heading": { "color": "var(--wp--preset--color--base)" }, "h2": { "color": "var(--wp--preset--color--primary)", "typography": { "fontSize": "clamp(2.625rem, calc(2.625rem + ((1vw - 0.48rem) * 8.4135)), 3.25rem)" } } }, // Block-level styles "blocks": { } } }

Now, all Heading 2 elements are set to be the primary preset color with a fluid font size. But maybe we want a fixed fontSize for the Heading 2 element when it is used in the Query Look block:

{ "version": 2, "styles": { // Global-level syles "elements": { "heading": { "color": "var(--wp--preset--color--base)" }, "h2": { "color": "var(--wp--preset--color--primary)", "typography": { "fontSize": "clamp(2.625rem, calc(2.625rem + ((1vw - 0.48rem) * 8.4135)), 3.25rem)" } } }, // Block-level styles "blocks": { "core/query": { "elements": { "h2": { "typography": { "fontSize": 3.25rem } } } } } } }

Now we have three levels of styles for Heading 2 elements: all headings, all Heading 2 elements, and Heading 2 elements that are used in the Query Loop block.

Existing theme examples

While we only looked at the styling examples for buttons and headings in this article, WordPress 6.1 supports styling additional elements. There’s a table outlining them in the “Defining styles with JSON elements” section.

You’re probably wondering which JSON elements support which CSS properties, not to mention how you would even declare those. While we wait for official documentation, the best resources we have are going to be the theme.json files for existing themes. I’m going to provide links to themes based on the elements they customize, and point out what properties are customized.

ThemeWhat’s customizedTheme JSONBlockbaseButtons, headings, links, core blocksSource codeBlock CanvasButtons, headings, links, core blocksSource codeDiscoButtons, headings, core blocksSource codeFrostButtons, headings, links, captions, cite, core blocksSource codePixlButtons, headings, links, core blocksSource codeRainfallButtons, headings, links, core blocksSource codeTwenty Twenty-ThreeButtons, headings, links, core blocksSource codeVivreButtons, headings, links, core blocksSource code

Be sure to give each theme.json file a good look because these themes include excellent examples of block-level styling on the styles.blocks object.

Wrapping up

Frequent changes to the full-site editor are becoming a major sources of irritation to many people, including tech-savvy Gutenberg users. Even very simple CSS rules, which work well with classic themes, don’t seem to work for block themes because of the new layers of specificity we covered earlier.

Regarding a GitHub proposal to re-design the site editor in a new browser mode, Sara Gooding writes in a WP Tavern post:

It’s easy to get lost while trying to get around the Site Editor unless you are working day and night inside the tool. The navigation is jumpy and confusing, especially when going from template browsing to template editing to modifying individual blocks.

Even as a keen early rider in the world of Gutenberg block editor and block-eye themes, I do have tons of my own frustrations. I’m optimistic, though, and anticipate that the site editor, once completed, will be a revolutionary tool for users and techno-savvy theme developers alike. This hopeful tweet already confirms that. In the meantime, it seems that we should be preparing for more changes, and perhaps even a bumpy ride.


I’m listing all of the resources I used while researching information for this article.

JSON elements Global Styles Style Engine

Thanks for reading! I’d love to hear your own reflections on using the block themes and how you managing your CSS.

Managing CSS Styles in a WordPress Block Theme originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

A Couple Changes Coming in Chrome 108

Fri, 11/04/2022 - 3:13am

“A change to overflow on replaced elements in CSS”:

From Chrome 108, the following replaced elements respect the overflow property: img, video and canvas. In earlier versions of Chrome, this property was ignored on these elements.

This means that an image which was earlier clipped to its content box can now draw outside those bounds if specified to do so in a style sheet.

So any image, video, and canvas elements that used to overflow by default might get clipped when Chrome 108 ships. The noted situations where this might affect your existing work:

  • The object-fit property is used to scale the image and fill the box. If the aspect ratio does not match the box, the image will draw outside of the bounds.
  • The border-radius property makes a square image look like a circle, but because overflow is visble the clipping no longer occurs.
  • Setting inherit: all and causing all properties to inherit, including overflow.

Worth reading the full article for code examples, but the solution for unwanted clipping is overriding the UA’s default overflow: clip with overflow: visible:

img { overflow: visible; }

“Prepare for viewport resize behavior changes coming to Chrome on Android”:

In November, with the release of Chrome 108, Chrome will make some changes to how the Layout Viewport behaves when the on-screen keyboard (OSK) gets shown. With this change, Chrome on Android will no longer resize the Layout Viewport, and instead resize only the Visual Viewport. This will bring Chrome on Android’s behavior up to par with that of Chrome on iOS and Safari on iOS.

This is a change related to the common headaches of working with viewport units and fixed positioning on mobile touch devices. We’ve covered (and tried solving) it over the years:

The change means that Chrome on Android will no longer resize the Layout Viewport when the on-screen keyboard is shown. So, the computed values of viewport units will no longer shrink when a device’s keyboard is displayed. Same goes for elements that are designed to take up the full viewport no longer shrinking to accomodate the keyboard. And no longer will a fixed-position element wind up who knows where when the keyboard pops up.

This brings more consistent cross-browser behavior that is on line with Chrome, Safari, and Edge on iOS and iPadOS. That’s great, even if the updated behavior is less than ideal because the keyboard UI can still cover and obscure elements that get in its way.

If you prefer elements to remain visible when that happens, it’s worth looking at a solution Chris shared a long while back that uses the prefixed webkit-fill-available property:

body { min-height: 100vh; min-height: -webkit-fill-available; } html { height: -webkit-fill-available; }

That uses the available space in the viewport rather than what’s covered by the UI… but Chrome currently ignores the property, and I’d bet the nickel in my pocket that it is unlikely to start respecting it in the 108 release. That may be a moot point, though, as Chrome 108 also introduces support for small, large, and dynamic viewport units, which are already supported in Safari and Firefox.

This browser support data is from Caniuse, which has more detail. A number indicates that browser supports the feature at that version and up.

DesktopChromeFirefoxIEEdgeSafari108101NoNo15.4Mobile / TabletAndroid ChromeAndroid FirefoxAndroidiOS SafariNo106No15.4

A Couple Changes Coming in Chrome 108 originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

The Difference Between Web Sockets, Web Workers, and Service Workers

Thu, 11/03/2022 - 2:56am

Web Sockets, Web Workers, Service Workers… these are terms you may have read or overheard. Maybe not all of them, but likely at least one of them. And even if you have a good handle on front-end development, there’s a good chance you need to look up what they mean. Or maybe you’re like me and mix them up from time to time. The terms all look and sound awful similar and it’s really easy to get them confused.

So, let’s break them down together and distinguish Web Sockets, Web Workers, and Service Workers. Not in the nitty-gritty sense where we do a deep dive and get hands-on experience with each one — more like a little helper to bookmark the next time I you need a refresher.

Quick reference

We’ll start with a high-level overview for a quick compare and contrast.

FeatureWhat it isWeb SocketEstablishes an open and persistent two-way connection between the browser and server to send and receive messages over a single connection triggered by events.Web WorkerAllows scripts to run in the background in separate threads to prevent scripts from blocking one another on the main thread.Service WorkerA type of Web Worker that creates a background service that acts middleware for handling network requests between the browser and server, even in offline situations. Web Sockets

A Web Socket is a two-way communication protocol. Think of this like an ongoing call between you and your friend that won’t end unless one of you decides to hang up. The only difference is that you are the browser and your friend is the server. The client sends a request to the server and the server responds by processing the client’s request and vice-versa.

The communication is based on events. A WebSocket object is established and connects to a server, and messages between the server trigger events that send and receive them.

This means that when the initial connection is made, we have a client-server communication where a connection is initiated and kept alive until either the client or server chooses to terminate it by sending a CloseEvent. That makes Web Sockets ideal for applications that require continuous and direct communication between a client and a server. Most definitions I’ve seen call out chat apps as a common use case — you type a message, send it to the server, trigger an event, and the server responds with data without having to ping the server over and again.

Consider this scenario: You’re on your way out and you decide to switch on Google Maps. You probably already know how Google Maps works, but if you don’t, it finds your location automatically after you connect to the app and keeps track of it wherever you go. It uses real-time data transmission to keep track of your location as long as this connection is alive. That’s a Web Socket establishing a persistent two-way conversation between the browser and server to keep that data up to date. A sports app with real-time scores might also make use of Web Sockets this way.

The big difference between Web Sockets and Web Workers (and, by extension as we’ll see, Service Workers) is that they have direct access to the DOM. Whereas Web Workers (and Service Workers) run on separate threads, Web Sockets are part of the main thread which gives them the ability to manipulate the DOM.

There are tools and services to help establish and maintain Web Socket connections, including: SocketCluster, AsyncAPI, cowboy, WebSocket King, Channels, and Gorilla WebSocket. MDN has a running list that includes other services.

More Web Sockets information Web Workers

Consider a scenario where you need to perform a bunch of complex calculations while at the same time making changes to the DOM. JavaScript is a single-threaded application and running more than one script might disrupt the user interface you are trying to make changes to as well as the complex calculation being performed.

This is where the Web Workers come into play.

Web Workers allow scripts to run in the background in separate threads to prevent scripts from blocking one another on the main thread. That makes them great for enhancing the performance of applications that require intensive operations since those operations can be performed in the background on separate threads without affecting the user interface from rendering. But they’re not so great at accessing the DOM because, unlike Web Sockets, a web worker runs outside the main thread in its own thread.

A Web Worker is an object that executes a script file by using a Worker object to carry out the tasks. And when we talk about workers, they tend to fall into one of three types:

  • Dedicated Workers: A dedicated worker is only within reach by the script that calls it. It still executes the tasks of a typical web worker, such as its multi-threading scripts.
  • Shared Workers: A shared worker is the opposite of a dedicated worker. It can be accessed by multiple scripts and can practically perform any task that a web worker executes as long as they exist in the same domain as the worker.
  • Service Workers: A service worker acts as a network proxy between an app, the browser, and the server, allowing scripts to run even in the event when the network goes offline. We’re going to get to this in the next section.
More Web Workers information Service Workers

There are some things we have no control over as developers, and one of those things is a user’s network connection. Whatever network a user connects to is what it is. We can only do our best to optimize our apps so they perform the best they can on any connection that happens to be used.

Service Workers are one of the things we can do to progressively enhance an app’s performance. A service worker sits between the app, the browser, and the server, providing a secure connection that runs in the background on a separate thread, thanks to — you guessed it — Web Workers. As we learned in the last section, Service Workers are one of three types of Web Workers.

So, why would you ever need a service worker sitting between your app and the user’s browser? Again, we have no control over the user’s network connection. Say the connection gives out for some unknown reason. That would break communication between the browser and the server, preventing data from being passed back and forth. A service worker maintains the connection, acting as an async proxy that is capable of intercepting requests and executing tasks — even after the network connection is lost.

This is the main driver of what’s often referred to as “offline-first” development. We can store assets in the local cache instead of the network, provide critical information if the user goes offline, prefetch things so they’re ready when the user needs them, and provide fallbacks in response to network errors. They’re fully asynchronous but, unlike Web Sockets, they have no access to the DOM since they run on their own threads.

The other big thing to know about Service Workers is that they intercept every single request and response from your app. As such, they have some security implications, most notably that they follow a same-origin policy. So, that means no running a service worker from a CDN or third-party service. They also require a secure HTTPS connection, which means you’ll need a SSL certificate for them to run.

More Service Workers information Wrapping up

That’s a super high-level explanation of the differences (and similarities) between Web Sockets, Web Workers, and Service Workers. Again, the terminology and concepts are similar enough to mix one up with another, but hopefully, this gives you a better idea of how to distinguish them.

We kicked things off with a quick reference table. Here’s the same thing, but slightly expanded to draw thicker comparisons.

FeatureWhat it isMultithreaded?HTTPS?DOM access?Web SocketEstablishes an open and persistent two-way connection between the browser and server to send and receive messages over a single connection triggered by events.Runs on the main threadNot requiredYesWeb WorkerAllows scripts to run in the background in separate threads to prevent scripts from blocking one another on the main thread.Runs on a separate threadRequiredNoService WorkerA type of Web Worker that creates a background service that acts middleware for handling network requests between the browser and server, even in offline situations.Runs on a separate threadRequiredNo

The Difference Between Web Sockets, Web Workers, and Service Workers originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

Some Links About CSS Gradients

Wed, 11/02/2022 - 2:59am

Every once in a while, the blogging zeitgiest seems to coalesce around a certain topic and it’s like the saved articles in my bookmarks folder are having a conversation. The conversation sitting in there now is all about CSS Gradients and I thought I’d link some of the more interesting pieces.

Some Links About CSS Gradients originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

Rendering External API Data in WordPress Blocks on the Back End

Tue, 11/01/2022 - 2:57am

This is a continuation of my last article about “Rendering External API Data in WordPress Blocks on the Front End”. In that last one, we learned how to take an external API and integrate it with a block that renders the fetched data on the front end of a WordPress site.

The thing is, we accomplished this in a way that prevents us from seeing the data in the WordPress Block Editor. In other words, we can insert the block on a page but we get no preview of it. We only get to see the block when it’s published.

Let’s revisit the example block plugin we made in the last article. Only this time, we’re going to make use of the JavaScript and React ecosystem of WordPress to fetch and render that data in the back-end Block Editor as well.

Working With External APIs in WordPress Blocks Where we left off

As we kick this off, here’s a demo where we landed in the last article that you can reference. You may have noticed that I used a render_callback method in the last article so that I can make use of the attributes in the PHP file and render the content.

Well, that may be useful in situations where you might have to use some native WordPress or PHP function to create dynamic blocks. But if you want to make use of just the JavaScript and React (JSX, specifically) ecosystem of WordPress to render the static HTML along with the attributes stored in the database, you only need to focus on the Edit and Save functions of the block plugin.

  • The Edit function renders the content based on what you want to see in the Block Editor. You can have interactive React components here.
  • The Save function renders the content based on what you want to see on the front end. You cannot have the the regular React components or the hooks here. It is used to return the static HTML that is saved into your database along with the attributes.

The Save function is where we’re hanging out today. We can create interactive components on the front-end, but for that we need to manually include and access them outside the Save function in a file like we did in the last article.

So, I am going to cover the same ground we did in the last article, but this time you can see the preview in the Block Editor before you publish it to the front end.

The block props

I intentionally left out any explanations about the edit function’s props in the last article because that would have taken the focus off of the main point, the rendering.

If you are coming from a React background, you will likely understand what is that I am talking about, but if you are new to this, I would recommend checking out components and props in the React documentation.

If we log the props object to the console, it returns a list of WordPress functions and variables related to our block:

We only need the attributes object and the setAttributes function which I am going to destructure from the props object in my code. In the last article, I had modified RapidAPI’s code so that I can store the API data through setAttributes(). Props are only readable, so we are unable to modify them directly.

Block props are similar to state variables and setState in React, but React works on the client side and setAttributes() is used to store the attributes permanently in the WordPress database after saving the post. So, what we need to do is save them to attributes.data and then call that as the initial value for the useState() variable.

The edit function

I am going to copy-paste the HTML code that we used in football-rankings.php in the last article and edit it a little to shift to the JavaScript background. Remember how we created two additional files in the last article for the front end styling and scripts? With the way we’re approaching things today, there’s no need to create those files. Instead, we can move all of it to the Edit function.

Full code import { useState } from "@wordpress/element"; export default function Edit(props) { const { attributes, setAttributes } = props; const [apiData, setApiData] = useState(null); function fetchData() { const options = { method: "GET", headers: { "X-RapidAPI-Key": "Your Rapid API key", "X-RapidAPI-Host": "api-football-v1.p.rapidapi.com", }, }; fetch( "https://api-football-v1.p.rapidapi.com/v3/standings?season=2021&league=39", options ) .then((response) => response.json()) .then((response) => { let newData = { ...response }; // Deep clone the response data setAttributes({ data: newData }); // Store the data in WordPress attributes setApiData(newData); // Modify the state with the new data }) .catch((err) => console.error(err)); } return ( <div {...useBlockProps()}> <button onClick={() => getData()}>Fetch data</button> {apiData && ( <> <div id="league-standings"> <div className="header" style={{ backgroundImage: `url(${apiData.response[0].league.logo})`, }} > <div className="position">Rank</div> <div className="team-logo">Logo</div> <div className="team-name">Team name</div> <div className="stats"> <div className="games-played">GP</div> <div className="games-won">GW</div> <div className="games-drawn">GD</div> <div className="games-lost">GL</div> <div className="goals-for">GF</div> <div className="goals-against">GA</div> <div className="points">Pts</div> </div> <div className="form-history">Form history</div> </div> <div className="league-table"> {/* Usage of [0] might be weird but that is how the API structure is. */} {apiData.response[0].league.standings[0].map((el) => { {/* Destructure the required data from all */} const { played, win, draw, lose, goals } = el.all; return ( <> <div className="team"> <div class="position">{el.rank}</div> <div className="team-logo"> <img src={el.team.logo} /> </div> <div className="team-name">{el.team.name}</div> <div className="stats"> <div className="games-played">{played}</div> <div className="games-won">{win}</div> <div className="games-drawn">{draw}</div> <div className="games-lost">{lose}</div> <div className="goals-for">{goals.for}</div> <div className="goals-against">{goals.against}</div> <div className="points">{el.points}</div> </div> <div className="form-history"> {el.form.split("").map((result) => { return ( <div className={`result-${result}`}>{result}</div> ); })} </div> </div> </> ); } )} </div> </div> </> )} </div> ); }

I have included the React hook useState() from @wordpress/element rather than using it from the React library. That is because if I were to load the regular way, it would download React for every block that I am using. But if I am using @wordpress/element it loads from a single source, i.e., the WordPress layer on top of React.

This time, I have also not wrapped the code inside useEffect() but inside a function that is called only when clicking on a button so that we have a live preview of the fetched data. I have used a state variable called apiData to render the league table conditionally. So, once the button is clicked and the data is fetched, I am setting apiData to the new data inside the fetchData() and there is a rerender with the HTML of the football rankings table available.

You will notice that once the post is saved and the page is refreshed, the league table is gone. That is because we are using an empty state (null) for apiData‘s initial value. When the post saves, the attributes are saved to the attributes.data object and we call it as the initial value for the useState() variable like this:

const [apiData, setApiData] = useState(attributes.data); The save function

We are going to do almost the same exact thing with the save function, but modify it a little bit. For example, there’s no need for the “Fetch data” button on the front end, and the apiData state variable is also unnecessary because we are already checking it in the edit function. But we do need a random apiData variable that checks for attributes.data to conditionally render the JSX or else it will throw undefined errors and the Block Editor UI will go blank.

Full code export default function save(props) { const { attributes, setAttributes } = props; let apiData = attributes.data; return ( <> {/* Only render if apiData is available */} {apiData && ( <div {...useBlockProps.save()}> <div id="league-standings"> <div className="header" style={{ backgroundImage: `url(${apiData.response[0].league.logo})`, }} > <div className="position">Rank</div> <div className="team-logo">Logo</div> <div className="team-name">Team name</div> <div className="stats"> <div className="games-played">GP</div> <div className="games-won">GW</div> <div className="games-drawn">GD</div> <div className="games-lost">GL</div> <div className="goals-for">GF</div> <div className="goals-against">GA</div> <div className="points">Pts</div> </div> <div className="form-history">Form history</div> </div> <div className="league-table"> {/* Usage of [0] might be weird but that is how the API structure is. */} {apiData.response[0].league.standings[0].map((el) => { const { played, win, draw, lose, goals } = el.all; return ( <> <div className="team"> <div className="position">{el.rank}</div> <div className="team-logo"> <img src={el.team.logo} /> </div> <div className="team-name">{el.team.name}</div> <div className="stats"> <div className="games-played">{played}</div> <div className="games-won">{win}</div> <div className="games-drawn">{draw}</div> <div className="games-lost">{lose}</div> <div className="goals-for">{goals.for}</div> <div className="goals-against">{goals.against}</div> <div className="points">{el.points}</div> </div> <div className="form-history"> {el.form.split("").map((result) => { return ( <div className={`result-${result}`}>{result}</div> ); })} </div> </div> </> ); })} </div> </div> </div> )} </> ); }

If you are modifying the save function after a block is already present in the Block Editor, it would show an error like this:

That is because the markup in the saved content is different from the markup in our new save function. Since we are in development mode, it is easier to remove the bock from the current page and re-insert it as a new block — that way, the updated code is used instead and things are back in sync.

This situation of removing it and adding it again can be avoided if we had used the render_callback method since the output is dynamic and controlled by PHP instead of the save function. So each method has it’s own advantages and disadvantages.

Tom Nowell provides a thorough explanation on what not to do in a save function in this Stack Overflow answer.

Styling the block in the editor and the front end

Regarding the styling, it is going to be almost the same thing we looked at in the last article, but with some minor changes which I have explained in the comments. I’m merely providing the full styles here since this is only a proof of concept rather than something you want to copy-paste (unless you really do need a block for showing football rankings styled just like this). And note that I’m still using SCSS that compiles to CSS on build.

Editor styles /* Target all the blocks with the data-title="Football Rankings" */ .block-editor-block-list__layout .block-editor-block-list__block.wp-block[data-title="Football Rankings"] { /* By default, the blocks are constrained within 650px max-width plus other design specific code */ max-width: unset; background: linear-gradient(to right, #8f94fb, #4e54c8); display: grid; place-items: center; padding: 60px 0; /* Button CSS - From: https://getcssscan.com/css-buttons-examples - Some properties really not needed :) */ button.fetch-data { align-items: center; background-color: #ffffff; border: 1px solid rgb(0 0 0 / 0.1); border-radius: 0.25rem; box-shadow: rgb(0 0 0 / 0.02) 0 1px 3px 0; box-sizing: border-box; color: rgb(0 0 0 / 0.85); cursor: pointer; display: inline-flex; font-family: system-ui, -apple-system, system-ui, "Helvetica Neue", Helvetica, Arial, sans-serif; font-size: 16px; font-weight: 600; justify-content: center; line-height: 1.25; margin: 0; min-height: 3rem; padding: calc(0.875rem - 1px) calc(1.5rem - 1px); position: relative; text-decoration: none; transition: all 250ms; user-select: none; -webkit-user-select: none; touch-action: manipulation; vertical-align: baseline; width: auto; &:hover, &:focus { border-color: rgb(0, 0, 0, 0.15); box-shadow: rgb(0 0 0 / 0.1) 0 4px 12px; color: rgb(0, 0, 0, 0.65); } &:hover { transform: translateY(-1px); } &:active { background-color: #f0f0f1; border-color: rgb(0 0 0 / 0.15); box-shadow: rgb(0 0 0 / 0.06) 0 2px 4px; color: rgb(0 0 0 / 0.65); transform: translateY(0); } } } Front-end styles /* Front-end block styles */ .wp-block-post-content .wp-block-football-rankings-league-table { background: linear-gradient(to right, #8f94fb, #4e54c8); max-width: unset; display: grid; place-items: center; } #league-standings { width: 900px; margin: 60px 0; max-width: unset; font-size: 16px; .header { display: grid; gap: 1em; padding: 10px; grid-template-columns: 1fr 1fr 3fr 4fr 3fr; align-items: center; color: white; font-size: 16px; font-weight: 600; background-color: transparent; background-repeat: no-repeat; background-size: contain; background-position: right; .stats { display: flex; gap: 15px; &amp; &gt; div { width: 30px; } } } } .league-table { background: white; box-shadow: rgba(50, 50, 93, 0.25) 0px 2px 5px -1px, rgba(0, 0, 0, 0.3) 0px 1px 3px -1px; padding: 1em; .position { width: 20px; } .team { display: grid; gap: 1em; padding: 10px 0; grid-template-columns: 1fr 1fr 3fr 4fr 3fr; align-items: center; } .team:not(:last-child) { border-bottom: 1px solid lightgray; } .team-logo img { width: 30px; top: 3px; position: relative; } .stats { display: flex; gap: 15px; &amp; &gt; div { width: 30px; text-align: center; } } .last-5-games { display: flex; gap: 5px; &amp; &gt; div { width: 25px; height: 25px; text-align: center; border-radius: 3px; font-size: 15px; &amp; .result-W { background: #347d39; color: white; } &amp; .result-D { background: gray; color: white; } &amp; .result-L { background: lightcoral; color: white; } } }

We add this to src/style.scss which takes care of the styling in both the editor and the frontend. I will not be able to share the demo URL since it would require editor access but I have a video recorded for you to see the demo:

Check out the demo

Pretty neat, right? Now we have a fully functioning block that not only renders on the front end, but also fetches API data and renders right there in the Block Editor — with a refresh button to boot!

But if we want to take full advantage of the WordPress Block Editor, we ought to consider mapping some of the block’s UI elements to block controls for things like setting color, typography, and spacing. That’s a nice next step in the block development learning journey.

Rendering External API Data in WordPress Blocks on the Back End originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

©2003 - Present Akamai Design & Development.