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.
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.
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 Tab Atkins’ unofficial proposal for CSS Toggles. 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.
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.
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:
Turning website design files into a combination of HTML, CSS and JavaScript is the bread and butter of many front-end web development jobs, but there’s a part of this work that doesn’t neatly fit in to tutorials on any specific topic. There’s a process of breaking down a design and figuring out how to approach the build that seems to fall under on-the-job training for new front-end developers. It’s not something taught alongside core technologies (no matter where we are learning those technologies—college, bootcamp, or on our own).
In this post, we’ll take a look at how to go from design to code, and why you might want to follow a process like this instead of just diving into code head-first—which, let’s face it, we love to do! The contents of this post are based on my experiences onboarding new developers, the kinds of conversations we’ve had, and the help and feedback I’ve provided in those situations.
One reason the process of moving from design to code is a core professional skill for a front-end developer is that without having some way to dig in and predict how you will approach something, it’s very difficult to provide an estimate for how long it takes to make or what questions you might need answered before you start. Many designs might appear simple at first glance, but quickly become complex once you get into the weeds. I’ve seen this lead to overpromises, which often leads to tighter deadlines, stress and even worse side effects. Suddenly everything takes much longer than we initially thought. Yuck. Let’s see if we can avoid that.
Evaluating a design
As a way to talk about this, let’s use an example design for a “marketing-style” web page and assume we have been asked to implement it. We can also assume this site is created in a context where marketing professionals may come in and edit the content via some content management system (CMS), re-order the sections, replace images, and make style changes. So we need to decide on the components of the page that will be the building blocks used in the CMS.
This gets at another reason that this can be missed in education: often in our own solo projects, we can have static content right there in the HTML, and component parts aren’t going to be Frankenstein-ed together by strangers to form whole new pages and sections. But once you step into more real-world dev situations, things are a lot more dynamic, and we are often working at the layer of “make things that a non-developer can use to make a web page.”
Let’s use this website for a clinical trial is example. As we can see there are a lot of familiar design elements. Marketing sites tend to share common patterns:
a big hero section
product images
small separate sections of short-form content emphasizing one thing or another
information about the company
etc.
On mobile, we can take it as a given that in each section, the left columns will stack on top of the right, and some other fairly typical reflow will happen. Nothing structural will change on mobile. So what we are looking at is the core of the design.
In this example, there is a header, then a lot of distinct sections, and a footer. At a glance, some of the sections look kind of similar—several have a two-column layout, for example. There are button and heading styles that seem to be consistent throughout. As soon as you take a look at something like this, your eye will start to notice repeated patterns like that.
This is where we start making notes. Before we do any coding, let’s understand the ideas contained in the design. These ideas can fall into a few buckets, and we want our final product—the web page itself—to correctly represent all these ideas. Here are the buckets I commonly use:
Layout-level patterns—repeating layout ideas and the overall grid
Element-level patterns—headings, text sizes, fonts, spacing, icons, button sizes
Color palette
Structural ideas—the logical organization of sections, independent from the layout
Everything else—ideas that are only present in one component
Documenting the patterns this way comes in handy for figuring out our CSS approach, but also for choosing what HTML elements to use and starting conversations with the designer or other stakeholders if something is unclear. If you have access to the source files for the design, sections and layers might be labelled in there that give you a good idea what the designer was thinking. This can be helpful when you want to talk about specific sections.
So let’s look at the ideas contained in our example design. We’re going to do several passes through the design, and in all of them, we’re going outside-in, top-to-bottom, and left-to-right to ensure we evaluate every element. In each of the five passes, we’re looking for stuff that goes into just one of the buckets.
We’re unconcerned with getting this list perfect; it can always be changed later—we just want to record our first impressions.
Pass 1: Layout-level ideas
In this design we have a few layout ideas that stand out right off the bat.
A header with a horizontal nav section
A main content column within the content area—left and right edges align within all sections from header to footer
Sections with two columns
Sections with a single centered column
Sections with a single left-aligned column
A footer with three columns
Fairly generous padding inside each section
First impressions
We should note any other first impressions we have during this first pass, good or bad. We can never have a first impression twice, and some of our gut reactions and questions can be forgotten if we neglect noting them now. Plus, identifying specific stuff that you like in the design can be nice when we get to talking with the designer. It both helps to celebrate the good stuff and mix it in with other constructive criticism.
Our first impressions might be things like:
👍 The design is clean-looking and readable.
👍 The sections are all titled by questions (good, helps draw reader in and gives each section a clear purpose).
🤨 Question marks are used inconsistently in the titles (possibly just an oversight?).
🙋♀️ Sometimes there are very similar font sizes right next to each other (may need to follow up to see if this is intentional because it seems a less slick and professional than the rest of the site).
👍 The logo is nice with that little gradient going on.
Pass 2: Element-level ideas
Here are things we might notice in this second pass:
Primary (blue) and Secondary (white) button styles, plus a “Learn more” button in the header with a little arrow (an expanding menu maybe?)
Heading and sub-heading styles
Three “body text” sizes (16px, 18px, 20px)
A “dark-mode” section where text color is white and the background is dark
A consistent presentation of “image & caption” sets
Custom bullet points of various kinds
Inline links in the text are underlined and, other links, like those in the footer, are not.
A repeated card component with an icon on top, and a heading and a list inside the card
The logo repeats a few times in different contexts/sizes.
The footer contains uppercase headings that don’t appear elsewhere.
Pass 3: Color palette
There is not too much going on in this design color-wise.
blue/purple primary color
light/dark body text colors
light/dark link colors
nice gradient under the word “hope” in the logo
light gray background color
dark navy background color
specific red and green “checkmark” and “stop” icon colors
Some design tools let you export the color hex values used in the design file, or even full-on Sass or CSS variable declarations. If you have that option, use it. Otherwise, find your own way to grab those values and name them because those are the foundation for lots of our initial CSS work.
Throughout our CSS and other code, we want to be refer to colors with labels or classes like “primary” and “secondary” that we can reuse throughout the code. This makes it easier to adjust values in the future, and to add themes down the line if we ever need to.
Pass 4: Structural ideas
This is where we might actually name the regions of the page in ways that make sense to us, and identify the hierarchy of the content. We can start to understand the accessibility needs of our implementation by documenting in plain language what we see as the nature and structure of the content in the page. As always, going outside-in, top-to bottom, left-to-right as we make our evaluations.
Focusing on structure helps us figure out the underlying patterns that eventually become our components, and also helps us understand the way we want people who use assistive technology to perceive the content. In turn, that guides us as far as what HTML elements we need to use to write semantic HTML. Semantic HTML speaks to the nature and structure of the content so that it can be perceived correctly by browsers. Browsers use our HTML to create the accessibility tree that assistive tech, like screen readers, uses to present the page. They need a strong outline for that to succeed and semantic HTML provides that solid structure.
This means we need to understand the nature of what’s on the page well enough that we could explain it verbally with no visual support if we had to. From that understanding, we can work backwards to figure out the correct HTML that expresses this understanding via the accessibility tree, which can be inspected in you browser’s developer tools.
Here’s a quick example of the accessibility tree in Chrome if everything on the page is a div, and if elements are correctly chosen to match the nature of the content. Determining the best element choice in a given situation is outside the scope of this post, but suffice it to say that the expressive, non-”generic generic generic” accessibility tree below is entirely created with HTML elements and attributes, and makes the content and its organization much easier for somebody to perceive using assistive technology.
So, in this fourth pass, here are notes we might make:
Top-level structure:
Header
Main Content
Footer
Not so bad for the first top-to-bottom pass. Let’s go a level deeper. We’re still unconcerned with the child inside elements of the sections themselves yet—we want just enough info to label the top level items inside each sections.
Within Header there is:
Home link
Navigation section
Within Main Content there is:
Hero section
Short explainer about the disease itself
Explainer about the treatment
Intro to the trial
Explainer with more details about the trial
Statement about who can join the study
Call-to-action to participate
Short explainer about the company
Within Footer there is:
Logo
Summary Sentence
Some lists of links with titles
Divider
Copyright notice
This pass reveals a few things. First, the Header and Footer sections are pretty shallow and are already revealing raw elements like links and text. Second, the Main section has eight distinct subsections, each with its own topic.
We’re going to do one more pass here and get at some of the deeper details in those sections.
Header home link—Woohoo, it’s just a link and we’re done.
Header nav—This is an expanding hover nav that needs JavaScript to work correctly. There are probably lots of accessible examples to follow, but still will take more time to develop and test than if we were working with raw links.
Hero
Title
Column 1
Subtitle (we missed this in the first element-level pass)
Paragraph
Primary button link
Secondary button link
Column 2
Hero image
Disease Explainer
Title
Paragraph
Unordered list
Large text
Unordered list
Image and caption (figure and figcaption)
List of links
Treatment Explainer
Title
Column 1
Paragraphs
Column 2
Image and caption (figure and figcaption)
Trial—Intro
Title
Column 1
YouTube Video Player
Column 2
Paragraphs
Trial—More Details
Title
Subtitle
Cards (with Icon, Title, and List)
“Who Can Join” statement
Title
Column 1
Paragraph
Unordered list
Column 2
Paragraph
Unordered list
Call-to-Action
Title
Paragraph
Secondary button link
About the Company
Title
Paragraph
Yowza, that got long fast! But now we understand pretty well the kinds of things we need to build, and what parts might be tricky. We also see that there may be some helper components to be built that aren’t quite represented by any one of these sections, for example, a two-column layout component that stacks to one column on mobile and has a title on top, or a generic “section container” component that takes a background and foreground color and provides the right styles plus standardized internal padding.
Incidentally, with this breakdown we’ve done a pretty good job expressing the final accessibility tree we want our HTML to create, so we are well on our way to having the implementation be a smooth experience without a lot of re-work to get accessibility right.
Pass 5: Everything else
What are some other ideas contained in this design, things that stick out, or challenges we notice? Maybe not much, but this is kind of the other side of the “first impressions” notes. Now our heads are filled with context for what it is in the design.
If something stands out now, especially if it’s a question about how something works, capture it. An example is, “Hmm, what is the ‘Learn More’ link in the nav supposed to do?” The answer might be: “It’s a list of links to each section that open when you hover there.” Every now and then, a designer expects that this kind of thing is already implied in the design, even if it is not explicitly documented, and it only comes up at the design review stage after that thing is developed—which is often too late to correct without affecting dates and deadlines.
We should also now look deeply and identify any hidden “glue work”— things like getting our styles in order, handling mobile, configuring the CMS, adding and testing authoring options and arrangements for our building blocks, and adding automated tests. Stuff like that.
At this point, we are ready to nail down exactly what components can be created in the CMS. Maybe we already have some of the basic setup done in the current system from past work. Whatever the case, we have enough to draw on to offer a decent estimate of the work needed, grouped into categories. For example, we might categorize components that:
are already 100% ready (no dev time needed),
exist but need tweaks for this new purpose (predictable dev time needed),
are totally new, but the path is obvious and safe (predictable dev time needed),
are totally new and need some research to implement. Or the design is unclear, or something about it gives you the heebie-jeebies and you need to have discussions with stakeholders. The earlier you can spot this, the better. Talk it over with whoever is running the project.
Now we have enough information to make a reasonable estimate. And more to the point, we’ve reduced the total time the project will take, and limited the trouble we might have along the way, because we’ve gained several advantages from planning it out.
The advantages of having a process
The exact steps we take and what order they are completed in is not the main point. What matters most is understanding the kind of information you need to gather when starting on a project. You might have your own ideas about how the work is done and in what order, whatever works for you is great.
Here are the advantages I’ve realized when assessing a design with a process in mind, compared to just kinda diving in, “getting stuff working,” and handling things as they come up.
You do a little extra discovery
As much as we’d like every project to arrive fully formed and perfectly ready to start, in reality, designs often contain assumptions that might be impractical to implement, or contradict something else we care about, like accessibility. In that case, you can assess the whole thing up front and get the conversations started with people who can resolve those issues early in the process. This can happen while you dive into the pieces that are ready to code, and will stop you from bumping into these roadblocks later when you are about to build that part of it. Flagging concerns early is definitely appreciated by the people who need to solve them.
You can be helped by others
Without a plan, it can be difficult to understand how far along you are in completing the project, as well as knowing if you need help meeting a deadline. Even if you do need help and are able to ask for it, it’s tough to use extra helping hands effectively without the work being broken out in to separate little chunks that be easily divided. When you have a plan that makes sense, you can quickly delegate certain tickets, knowing that the jigsaw pieces will fit together in the end.
It’s easy (and common) for a new developer to think think that huge workloads and working around the clock is a good thing. But as you mature in the role, you’ll see that having a deep understanding of the whole picture of a project, or even a single ticket, is more valuable, while creating a better impression that you are “on top of things.” Recognizing early that a timeline doesn’t look right gives you options about how to address it in ways other than trying to be a hero and throwing some weekends at it.
Component architecture flows better
Architectural decisions are the worst for me. Naming components, figuring out where stuff should live, which components need to talk to each other, where to break stuff up into smaller components. A lot of those choices only make sense when we look at the bigger picture and think about all various ways that certain elements might be used by visitors and content creators. And a lot of these choices are marginal—choosing the “best” option between two acceptable solutions can be a huge time suck or completely subjective.
Have a process helps with that because you are going to get all, or most, of the information you need about the designs before digging deeply into the development work. For me, figuring out what pieces I need to make, and figuring out the best possible code to make those pieces, are two different things. Sometimes what I need is not the thing that comes most naturally from the code. And sometimes, after learning a bit about what is needed, I can avoid time spent bikeshedding marginal decisions because it’s more clear which decisions truly don’t matter.
You still learn new things as you write the code, of course, but you’re now better prepared to handle those things when they pop up. You even have a good idea about the kinds of that might present themselves.
Styles make more sense
As you plan the work, you can truly figure out which styles are global, which are section-specific, which are or component-specific, and which are one-off exceptions. If you don’t already have a system you like for this, Andy Bell’s Cube CSS pairs very well with the kind of breakdown I’m talking about. Here’s a video of Andy working through an example with Chris Coyier that you can check out for more about this approach.
Accessibility starts early in the process
This one is huge for me. By really understanding the ideas contained in the design, you will have an easier time picking semantic HTML elements and finding appropriate accessible patterns to build out what you see there. Accessibility can work its way into the daily work you do, rather than an afterthought or extra burden. Our perspective becomes that high-quality front-end code correctly expresses the nature and structure of its content to all users, and accessibility is how we measure that.
Like I said at the start, a lot of this falls under on-the-job training, the “oral tradition” of web development. It’s the kind of stuff you might hear from a senior developer on your team as you’re getting started in your first front-end role. I’m sure lots of people would have different priorities than I do and recommend a slightly different process. I also know for sure that a lot of folks out there work in situations without a solid process in place, and no one senior to consult.
If you are in that situation, or not yet in your first role, I hope this gives you a baseline you can relate to when you think about how to do the job. Ideally the job is not just diving in and putting stuff in divs until things look “right” but that is often our mode of operation. We are eager to make progress and see results.
I’m very grateful that I did have somebody working with me at my first development job who showed me how to split up pieces of a design and estimate work for large, long-term projects. That’s what inspired me to start thinking about this and—as I began supervising other developers and teams—thinking about how I wanted to adapt the process and make it my own. I also realized it wasn’t something I’d noticed people talking much about when teaching technical skills about using a particular language. So thanks, Nate!
Thanks also to Drew Clements and Nerando Johnson for providing early feedback on this post. You are the best.
Let’s talk shadows in web design. Shadows add texture, perspective, and emphasize the dimensions of objects. In web design, using light and shadow can add physical realism and can be used to make rich, tactile interfaces.
Take the landing page below. It is for cycling tours in Iceland. Notice the embellished drop shadow of the cyclist and how it creates the perception that they are flying above not only the content on the page, but the page itself, as though they are “popping” over the screen. It feels dynamic and immediate, which is perfect for the theme of adventure.
Compare that with this next example. It’s a “flat” design, sans shadows. In this case, the bike itself is the focal point. The absence of depth and realism allows the bike to stand out on its own.
You can appreciate the differences between these approaches. Using shadows and depth is a design choice; they should support the theme and the message you want the content to convey.
Light and shadows
As we just saw, depth can enhance content. And what exactly makes a shadow? Light!
It’s impossible to talk about shadow without getting into light. It controls the direction of a shadow as well as how deep or shallow the shadow appears. You can’t have one without the other.
Google’s Material Design design system is a good example of employing light and shadows effectively. You’ve certainly encountered Material Design’s aesthetics because Google employs it on nearly all of its products.
The design system takes cues from the physical world and expresses interfaces in three-dimensional space using light, surfaces, and cast shadows. Their guidelines on using light and shadows covers this in great detail.
In the Material Design environment, virtual lights illuminate the UI. Key lights create sharper, directional shadows, called key shadows. Ambient light appears from all angles to create diffused, soft shadows, called ambient shadows.
Shadows are a core component of Material Design. Compare that with Apple’s Human Interface Guidelines for macOS, where translucency and blurring is more of a driving factor for evoking depth.
In this case, light is still an influential factor, as it allows elements to either blend into the desktop, or even into other panels in the UI. Again, it’s is a design choice to employ this in your interface. Either way, you can see how light influences the visual perception of depth.
Light sources and color
Now that we understand the relationship between light and shadows, we ought to dig in a little deeper to see how light affects shadows. We’ve already seen how the strength of light produces shadows at different depths. But there’s a lot to say about the way light affects the direction and color of shadows.
There are two kinds of shadows that occur when a light shines on an object, a drop shadow and a form shadow.
Drop shadows
A drop shadow is cast when an object blocks a light source. A drop shadow can vary in tone and value. Color terminology can be dense and confusing, so let’s talk about tone and value for a moment.
Tone is a hue blended with grey. Value describes the overall lightness or darkness of a color. Value is a big deal in painting as it is how the artist translates light and object relationships to color.
In the web design world, these facets of color are intrinsic to the color picker UI.
Form shadows
A form shadow, on the other hand, is the side of an object facing away from the light source. A form shadow has softer, less defined edges than a drop shadow. Form shadows illustrate the volume and depth of an object.
The appearance of a shadow depends on the direction of light, the intensity of light, and the distance between the object and the surface where the shadow is cast. The stronger the light, the darker and sharper the shadow is. The softer the light, the fainter and softer the shadow is. In some cases, we get two distinct shadows for directional light. The umbra is where light is obstructed and penumbra is where light is cast off.
If a surface is close to an object, the shadow will be sharper. If a surface is further away, the shadow will be fainter. This is not some abstract scientific stuff. This is stuff we encounter every day, whether you realize it or not.
Light may also be reflected from sides of an object or another surface. Bright surfaces reflect light, dark surfaces absorb light.
These are the most valuable facets of light to understand for web design. The physics behind light is a complex topic, I have just lightly touched on some of it here. If you’d like to see explicit examples of what shadows are cast based on different light sources, this guide to drawing shadows for comics is instructive.
Positioning light sources
Remember, shadows go hand-in-hand with light, so defining a light source — even though there technically isn’t one — is the way to create impressive shadow effects. The trick is to consistently add shadows relative to the light source. A light source positioned above an element will cast a shadow below the element. Placing a light source to the left of an element will cast a shadow to the right. Placing multiple light sources to the top, bottom, left and right of an element actually casts no shadow at all!
A light source can be projected in any direction you choose. Just make sure it’s used consistently in your design, so the shadow on one element matches other shadows on the page.
Elevation
Shadows can also convey elevation. Once again, Material Design is a good example because it demonstrates how shadows are used to create perceived separation between elements.
Speaking of elevation, the box-shadow property is the only property that can create inner shadows for a sunken effect. So, instead of elevating up, the element appears to be pressed in. That’s thanks to the inset keyword.
That good for something like an effect where clicking a button appears to physically press it.
It’s also possible to “fake” an inner text shadow with a little trickery that’s mostly supported across browsers:
Layering shadows
We’re not limited to a single shadow per element! For example, we can provide a comma-separated list of shadows on the box-shadow property. Why would we want to do that? Smoother shadows, for one.
Interesting effects is another.
Layering shadows can even enhance typography using the text-shadow property.
Just know that layering shadows is a little different for filter: drop-shadow() It’s syntax also takes a list, but it’s space-separated instead of comma-separated.
The <feDropShadow> element works the exact same way for SVGs.
Shadows and accessibility
Here’s something for you to chew on: shadows can help improve accessibility.
Google conducted a study with low-vision participants to better understand how shadows and outlines impact an individual’s ability to identify and interact with a component. They found that using shadows and outlines:
increases the ease and speed of finding a component when scanning pages, and
improves one’s ability to determine whether or not a component is interactive.
That wasn’t a wide-ranging scientific study or anything, so let’s turn around and see what the W3C says in it’s guidelines for WCAG 2.0 standards:
[…] the designer might darken the background behind the letter, or add a thin black outline (at least one pixel wide) around the letter in order to keep the contrast ratio between the letter and the background above 4.5:1.
That’s talking about light text on a light background. WCAG recommends a contrast ratio that’s at least 4.5:1 between text and images. You can use text shadows to add a stronger contrast between them.
Before diving into shadows and adding them on all the things, it’s worth calling out that they do affect performance.
For example, filter: drop-shadow is hardware-accelerated by some browsers. A new compositor layer may be created for that element, and offloaded to the GPU. You don’t want to have too many layers, as it takes up limited GPU memory, and will eventually degrade performance. You can assess this in your browser’s DevTools.
Blurring is an expensive operation, so use it sparingly. When you blur something, it mixes the colors from pixels all around the output pixel to generate a blurred result. For example, if your <blur-radius> parameter is 2px, then the filter needs to look at two pixels in every direction around each output pixel to generate the mixed color. This happens for each output pixel, so that means a lot of calculations that grow exponentially. So, shadows with a large blur radius are generally slower to render than other shadows.
Did you know?
Did you know that shadows don’t influence the document layout?
A shadow is the same size as the element it targets. You can modify the size of a box-shadow (through the spread radius parameter), but other properties cannot modify the shadow size.
And did you know that a shadow implicitly has a lower z-index than elements? That’s why shadows sit below other elements.
And what about clipping and masking? If an element with a box-shadow is clipped (with clip-path) or uses a mask (with mask), the shadow isn’t shown. Conversely, if an element with text-shadow or filter: drop-shadow() is clipped, a shadow is shown, as long as it is within the clip region.
Here’s another: We can’t create oblique shadows (with diagonal lines) with shadow properties. That requires creating a shadow element and use a transform:skew() on it.
Oh, and one more: box-shadow follows border-radius. If an element has rounded corners, the shadow is rounded as well. In other words, the shadow mirrors the shape of the box. On the other hand, filter: drop-shadow() can create an irregular shape because it respects transparency and follows the shape of the content.
Best use cases for different types of shadows
Practically anything on the web can have a shadow and there are multiple CSS properties and functions that create shadows. But choosing the right type of shadow is what makes a shadow effective.
Let’s evaluate the options:
box-shadow: This CSS property creates shadows that conform to the elements bounding box. It’s versatile and can be used on anything from cards to buttons to just about anything where the shadow simply needs to follow the element’s box.
text-shadow: This is a CSS property that creates shadows specifically for text elements.
filter: drop-shadow(): The CSS property here is filter, but what create the shadow is the drop-shadow function it accepts. What makes this type of shadow different from, say box-shadow, is that it follows the rendered shape of any element (including pseudos).
<feDropShadow>: This is actually an SVG element, whereas the rest are CSS properties. So, you would use this to create drop shadows directly in SVG markup.
Once you get the hang of the different types of shadows and each one’s unique shadow-creating powers, the possibilities for shadow effects feels endless. From simple drop shadows to floating elements, and even inner shadows, we can create interesting visuals that add extra meaning or value to UI.
The same goes for text shadows.
Shadows in the wild
Shadows are ubiquitous. We’re seeing them used in new and interesting ways all the time.
Have you heard the buzzword “neumorphism” floating around lately? That’s all about shadows. Here’s an implementation by Maria Muñoz:
CSS relies on existing DOM structure in the browser. It’s not possible to generate new elements other than ::before and ::after. Sometimes I really wish CSS had the ability to do so straightforwardly.
Yet, we can partially make up for this by creating various shadows and gradients entirely in CSS.
That’s why having drop-shadow is so exciting. Together with text-shadow and box-shadow we can do a lot more.
Just check out how he uses drop shadows to create intricate patterns.
Yes, that’s pretty crazy. And speaking of crazy, it’s worth mentioning that going too crazy can result in poor performance, so tread carefully.
What about pseudo-elements?
Oh yes, shadow properties are supported by the ::before and ::after pseudo-elements.
Other pseudos that respect shadows? The ::first-letter pseudo-element accepts box-shadow and text-shadow. The ::first-line pseudo-element accepts text-shadow.
Look at how Jhey Tompkins got all creative using box-shadow on pseudo elements to create animated loaders.
Animating shadows
Yes, we can make them move! The properties and function we’ve covered here are totally compatible with CSS animations and transitions. That means we can move shadows, blur shadows, expand/shrink shadows (with box-shadow), and alter the color.
Animating a shadow can provide a user with a cue that an element is interactive, or that an action has taken place. We saw earlier with our button example that an inset shadow showed that the button had been pressed. Another common animation pattern is elevating a card on hover.
If you want to optimize the animation performance, avoid animating box-shadow! It is more performant to animate drop-shadow(). But if you want the smoothest animation, a hack is the best option! Add an ::after pseudo-element with a bigger box-shadow, and animate its opacity instead.
Of course, there is a lot more you can animate. I will leave that exploration up to you!
Wrapping up
Phew, who knew there was so much to something as seemingly “simple” as CSS shadows! There’s the light source and how shadows are cast. The different types of shadows and their color. There’s using shadows for evoking depth, elevating elements and insetting them. There’s the fact that we can layer shadows on top of other shadows. And that we have a selection of CSS properties that we can use for different use cases. Then, there are the accessibility and performance implications that come with them. And, hey, animation is thing! That’s a heckuva lot!
Anyway, hopefully this broad overview gave you something new to chew on, or at the very least, helped you brush up on some concepts.
A table of contents is a list of links that allows you to quickly jump to specific sections of content on the same page. It benefits long-form content because it shows the user a handy overview of what content there is with a convenient way to get there.
This tutorial will show you how to parse long Markdown text to HTML and then generate a list of links from the headings. After that, we will make use of the Intersection Observer API to find out which section is currently active, add a scrolling animation when a link is clicked, and finally, learn how Vue’s <transition-group> allow us to create a nice animated list depending on which section is currently active.
Parsing Markdown
On the web, text content is often delivered in the form of Markdown. If you haven’t used it, there are lots of reasons why Markdown is an excellent choice for text content. We are going to use a markdown parser called marked, but any other parser is also good.
We will fetch our content from a Markdown file on GitHub. After we loaded our Markdown file, all we need to do is call the marked(<markdown>, <options>) function to parse the Markdown to HTML.
After we fetch and parse our data, we will pass the parsed HTML to our DOM by replacing the content with innerHTML.
async function init() { const $ main = document.querySelector('#app'); const htmlContent = await fetchAndParseMarkdown(); $ main.innerHTML = htmlContent } init();
Generating a list of heading links
Now that we’ve generated the HTML, we need to transform our headings into a clickable list of links. To find the headings, we will use the DOM function querySelectorAll('h1, h2'), which selects all <h1> and <h2> elements within our markdown container. Then we’ll run through the headings and extract the information we need: the text inside the tags, the depth (which is 1 or 2), and the element ID we can use to link to each respective heading.
This snippet results in an array of elements that looks like this:
[ {title: "The Red Panda", depth: "1", id: "the-red-panda"}, {title: "About", depth: "2", id: "about"}, // ... ]
After getting the information we need from the heading elements, we can use ES6 template literals to generate the HTML elements we need for the table of contents.
First, we loop through all the headings and create <li> elements. If we’re working with an <h2> with depth: 2, we will add an additional padding class, .pl-4, to indent them. That way, we can display <h2> elements as indented subheadings within the list of links.
Finally, we join the array of <li> snippets and wrap it inside a <ul> element.
Next, we need to find out which part of the content we’re currently reading. Intersection Observers are the perfect choice for this. MDN defines Intersection Observer as follows:
The Intersection Observer API provides a way to asynchronously observe changes in the intersection of a target element with an ancestor element or with a top-level document’s viewport.
So, basically, they allow us to observe the intersection of an element with the viewport or one of its parent’s elements. To create one, we can call a new IntersectionObserver(), which creates a new observer instance. Whenever we create a new observer, we need to pass it a callback function that is called when the observer has observed an intersection of an element. Travis Almand has a thorough explanation of the Intersection Observer you can read, but what we need for now is a callback function as the first parameter and an options object as the second parameter.
The observer is created, but nothing is being observed at the moment. We will need to observe the heading elements in our Markdown, so let’s loop over them and add them to the observer with the observe() function.
Since we want to update our list of links, we will pass it to the observer function as a $ links parameter, because we don’t want to re-read the DOM on every update for performance reasons. In the handleObserver function, we find out whether a heading is intersecting with the viewport, then obtain its id and pass it to a function called updateLinks which handles updating the class of the links in our table of contents.
Let’s write the function to update the list of links. We need to loop through all links, remove the .is-active class if it exists, and add it only to the element that’s actually active.
function updateLinks(visibleId, $ links) { $ links.map(link => { let href = link.getAttribute('href') link.classList.remove('is-active') if(href === visibleId) link.classList.add('is-active') }) }
The end of our init() function creates an observer, observes all the headings, and updates the links list so the active link is highlights when the observer notices a change.
async function init() { // Parsing Markdown const $ aside = document.querySelector('#aside'); // Generating a list of heading links const $ headings = [...$ main.querySelectorAll('h1, h2')]; // Adding an Intersection Observer const $ links = [...$ aside.querySelectorAll('a')] const observer = createObserver($ links) $ headings.map(heading => observer.observe(heading)) }
Scroll to section animation
The next part is to create a scrolling animation so that, when a link in the table of contents is clicked, the user is scrolled to the heading position rather abruptly jumping there. This is often called smooth scrolling.
Scrolling animations can be harmful if a user prefers reduced motion, so we should only animate this scrolling behavior if the user hasn’t specified otherwise. With window.matchMedia('(prefers-reduced-motion)'), we can read the user preference and adapt our animation accordingly. That means we need a click event listener on each link. Since we need to scroll to the headings, we will also pass our list of $ headings and the motionQuery.
Let’s write our handleLinkClick function, which is called whenever a link is clicked. First, we need to prevent the default behavior of links, which would be to jump directly to the section. Then we’ll read the href attribute of the clicked link and find the heading with the corresponding id attribute. With a tabindex value of -1 and focus(), we can focus our heading to make the users aware of where they jumped to. Finally, we add the scrolling animation by calling scroll() on our window.
Here is where our motionQuery comes in. If the user prefers reduced motion, the behavior will be instant; otherwise, it will be smooth. The top option adds a bit of scroll margin to the top of the headings to prevent them from sticking to the very top of the window.
function handleLinkClick(evt, $ headings, motionQuery) { evt.preventDefault() let id = evt.target.getAttribute("href").replace('#', '') let section = $ headings.find(heading => heading.getAttribute('id') === id) section.setAttribute('tabindex', -1) section.focus() window.scroll({ behavior: motionQuery.matches ? 'instant' : 'smooth', top: section.offsetTop - 20 }) }
Animate the list of links
For the last part, we will make use of Vue’s <transition-group>, which is very useful for list transitions. Here is Sarah Drasner’s excellent intro to Vue transitions if you’ve never worked with them before. They are especially great because they provide us with animation lifecycle hooks with easy access to CSS animations.
Vue automatically attaches CSS classes for us when an element is added (v-enter) or removed (v-leave) from a list, and also with classes for when the animation is active (v-enter-active and v-leave-active). This is perfect for our case because we can vary the animation when subheadings are added or removed from our list. To use them, we will need wrap our <li> elements in our table of contents with an <transition-group> element. The name attribute of the <transition-group> defines how the CSS animations will be called, the tag attribute should be our parent <ul> element.
Now we need to add the actual CSS transitions. Whenever an element is entering or leaving it, should animate from not visible (opacity: 0) and moved a bit to the bottom (transform: translateY(10px)).
Then we define what CSS property we want to animate. For performance reasons, we only want to animate the transform and the opacity properties. CSS allows us to chain the transitions with different timings: the transform should take 0.8 seconds and the fading only 0.4s.
Then we want to add a bit of a delay when a new element is added, so the subheadings fade in after the parent heading moved up or down. We can make use of the v-enter-active hook to do that:
Finally, we can add absolute positioning to the elements that are leaving to avoid sudden jumps when the other elements are animating:
.list-leave-active { position: absolute; }
Since the scrolling interaction is fading elements out and in, it’s advisable to debounce the scrolling interaction in case someone is scrolling very quickly. By debouncing the interaction we can avoid unfinished animations overlapping other animations. You can either write your own debouncing function or simply use the lodash debounce function. For our example the simplest way to avoid unfinished animation updates is to wrap the Intersection Observer callback function with a debounce function and pass the debounced function to the observer.
const debouncedFunction = _.debounce(this.handleObserver) this.observer = new IntersectionObserver(debouncedFunction,options)
Here’s the final demo
Again, a table of contents is a great addition to any long-form content. It helps make clear what content is covered and provides quick access to specific content. Using the Intersection Observer and Vue’s list animations on top of it can help to make a table of contents even more interactive and even allow it to serve as an indication of reading progress. But even if you only add a list of links, it will already be a great feature for the user reading your content.
Other Coil subscribers deposit small bits of money directly into my online wallet (I’m using Uphold). I set this up over a year ago and found it all quick and easy to get started. But to be fair, I wasn’t trying to understand every detail of it and I’m still not betting anything major on it. PPK went as far to say it was user-hostile and I’ll admit he has some good points…
Signing up for payment services is a complete hassle, because you don’t know what you’re doing while being granted the illusion of free choice by picking one of two or three different systems — that you don’t understand and that aren’t explained. Why would I pick EasyMoneyGetter over CoinWare when both of them are black boxes I never heard of?
Also, these services use insane units. Brave use BATs, though to their credit I saw a translation to US$ — but not to any other currency, even though they could have figured out from my IP address that I come from Europe. Coil once informed me I had earned 0.42 XBP without further comment. W? T? F?
Bigger and bigger sites are starting to use it. TechDirt, is one example. I’ve got it on CodePen as well.
If this was just a “sprinkle some pennies at sites” play, it would be doomed.
I’m pessimistic at that approach. Micropayments have been done over and over and it hasn’t worked and I just don’t see it ever having enough legs to do anything meaningful to the industry.
At a quick glance, that’s what this looks like, and that’s how it is behaving right now, and that deserves a little skepticism.
There are two things that make this different
This has a chance of being a web standard, not something that has to be installed to work.
There are APIs to actually do things based on people transferring money to a site.
Neither of these things are realized, but if both of them happen, then meaningful change is much more likely to happen.
With the APIs, a site could say, “You’ll see no ads on this site if you pay us $ 1/month,” and then write code to make that happen all anonymously. That’s so cool. Removing ads is the most basic and obvious use case, and I hope some people give that a healthy try. I don’t do that on this site, because I think the tech isn’t quite there yet. I’d want to clearly be able to control the dollar-level of when you get that perk (you can’t control how much you give sites on Coil right now), but more importantly, in order to really make good on the promise of not delivering ads, you need to know very quickly if any given user is supporting you at the required level or not. For example, you can’t wait 2600 milliseconds to decide whether ads need to be requested. Well, you can, but you’ll hurt your ad revenue. And you can’t simply request the ads and hide them when you find out, lest you are not really making good on a promise, as trackers’n’stuff will have already done their thing.
Coil said the right move here is the “100+20” Rule, which I think is smart. It says to give everyone the full value of your site, but then give people extra if they hit monetization thresholds. For example, on this site, if you’re a supporter (not a Coil thing, this is old-school eCommerce), you can download the screencast originals (nobody else can). That’s the kind of thing I’d be happy to unlock via Web Monetization if it became easy to write the code to do that.
Maybe the web really will get monetized at some point and thus fix the original sin of the internet. I’m not really up on where things are in the process, but there is a whole site for it.
I’m not really helping, yet
While I have Coil installed and I’m a fan of all this, what will actually make a difference is having sites that actually do things for users that pay them. Like my video download example above. Maybe recipe sites offer some neat little printable PDF shopping list for people that pay them via Web Monetization. I dunno! Stuff like that! I’m not doing anything cool like that yet, myself.
If this thing gets legs, we’ll see all sorts of creative stuff, and the standard will make it so there is no one service that lords over this. It will be standardized APIs, so there could be a whole ecosystem of online wallets that accept money, services that help dole money out, fancy in-browser features, and site owners doing creative things.
Webmention is a W3C recommendation last published on January 12, 2017. And what exactly is a Webmention? It’s described as…
[…] a simple way to notify any URL when you mention it on your site. From the receiver’s perspective, it’s a way to request notifications when other sites mention it.
In a nutshell, it’s a way of letting a website know it has been mentioned somewhere, by someone, in some way. The Webmention spec also describes it as a way for a website to let others know it cited them. What that basically bails down to is that your website is an active social media channel, channeling communication from other channels (e.g. Twitter, Instagram, Mastodon, Facebook, etc.).
How does a site implement Webmentions? In some cases, like WordPress, it’s as trivial as installing a couple of plugins. Other cases may not be quite so simple, but it’s still pretty straightforward. In fact, let’s do that now!
Here’s our plan
Declare an endpoint to receive Webmentions
Process social media interactions to Webmentions
Get those mentions into a website/app
Set the outbound Webmentions
Luckily for us, there are services in place that make things extremely simple. Well, except that third point, but hey, it’s not so bad and I’ll walk through how I did it on my own atila.io site.
My site is a server-side blog that’s pre-rendered and written with NextJS. I have opted to make Webmention requests client-side; therefore, it will work easily in any other React app and with very little refactoring in any other JavaScript application.
Step 1: Declare an endpoint to receive Webmentions
In order to have an endpoint we can use to accept Webmentions, we need to either write the script and add to our own server, or use a service such as Webmention.io (which is what I did).
Webmention.io is free and you only need to confirm ownership over the domain you register. Verification can happen a number of ways. I did it by adding a rel="me" attribute to a link in my website to my social media profiles. It only takes one such link, but I went ahead and did it for all of my accounts.
Verifying this way, we also need to make sure there’s a link pointing back to our website in that Twitter profile. Once we’ve done that, we can head back to Webmention.io and add the URL.
This gives us an endpoint for accepting Webmentions! All we need to do now is wire it up as <link> tags in the <head> of our webpages in order to collect those mentions.
Remember to replace {user} with your Webmention.io username.
Step 2: Process social media interactions into Webmentions
We are ready for the Webmentions to start flowing! But wait, we have a slight problem: nobody actually uses them. I mean, I do, you do, Max Böck does, Swyx does, and… that’s about it. So, now we need to start converting all those juicy social media interactions into Webmentions.
And guess what? There’s an awesome free service for it. Fair warning though: you’d better start loving the IndieWeb because we’re about to get all up in it.
Bridgy connects all our syndicated content and converts them into proper Webmentions so we can consume it. With a SSO, we can get each of our profiles lined up, one by one.
Step 3: Get those mentions into a website/app
Now it’s our turn to do some heavy lifting. Sure, third-party services can handle all our data, but it’s still up to us to use it and display it.
We’re going to break this up into a few stages. First, we’ll get the number of Webmentions. From there, we’ll fetch the mentions themselves. Then we’ll hook that data up to NextJS (but you don’t have to), and display it.
Get the number of mentions
type TMentionsCountResponse = { count: number type: { like: number mention: number reply: number repost: number } }
That is an example of an object we get back from the Webmention.io endpoint. I formatted the response a bit to better suit our needs. I’ll walk through how I did that in just a bit, but here’s the object we will get:
type TMentionsCount = { mentions: number likes: number total: number }
The request will not fail without it, but the data won’t come either. Both Max Böck and Swyx combine likes with reposts and mentions with replies. In Twitter, they are analogous.
Before getting to the response, please note that the response is paginated, where the endpoint accepts three parameters in the query:
page: the page being requested
per-page: the number of mentions to display on the page
target: the URL where Webmentions are being fetched
Once we hit https://webmention.io/api/mentions and pass the these params, the successful response will be an object with a single key links which is an array of mentions matching the type below:
type TMention = { source: string verified: boolean verified_date: string // date string id: number private: boolean data: { author: { name: string url: string photo: string // url, hosted in webmention.io } url: string name: string content: string // encoded HTML published: string // date string published_ts: number // ms } activity: { type: 'link' | 'reply' | 'repost' | 'like' sentence: string // pure text, shortened sentence_html: string // encoded html } target: string }
The above data is more than enough to show a comment-like section list on our site. Here’s how the fetch request looks in TypeScript:
We’re going to work in NextJS for a moment. It’s all good if you aren’t using NextJS or even have a web app. We already have all the data, so those of you not working in NextJS can simply move ahead to Step 4. The rest of us will meet you there.
As of version 9.3.0, NextJS has three different methods for fetching data:
getStaticProps: fetches data on build time
getStaticPaths: specifies dynamic routes to pre-render based on the fetched data
getServerSideProps: fetches data on each request
Now is the moment to decide at which point we will be making the first request for fetching mentions. We can pre-render the data on the server with the first batch of mentions, or we can make the entire thing client-side. I opted to go client-side.
If you’re going client-side as well, I recommend using SWR. It’s a custom hook built by the Vercel team that provides good caching, error and loading states — it and even supports React.Suspense.
Display the Webmention count
Many blogs show the number of comments on a post in two places: at the top of a blog post (like this one) and at the bottom, right above a list of comments. Let’s follow that same pattern for Webmentions.
First off, let’s create a component for the count:
const MentionsCounter = ({ postUrl }) => { const { t } = useTranslation() // Setting a default value for `data` because I don't want a loading state // otherwise you could set: if(!data) return <div>loading...</div> const { data = {}, error } = useSWR(postUrl, getMentionsCount) if (error) { return <ErrorMessage>{t('common:errorWebmentions')}</ErrorMessage> } // The default values cover the loading state const { likes = '-', mentions = '-' } = data return ( <MentionCounter> <li> <Heart title="Likes" /> <CounterData>{Number.isNaN(likes) ? 0 : likes}</CounterData> </li> <li> <Comment title="Mentions" />{' '} <CounterData>{Number.isNaN(mentions) ? 0 : mentions}</CounterData> </li> </MentionCounter> ) }
Thanks to SWR, even though we are using two instances of the WebmentionsCounter component, only one request is made and they both profit from the same cache.
Feel free to peek at my source code to see what’s happening:
Now that we have placed the component, it’s time to get all that social juice flowing!
At of the time of this writing, useSWRpages is not documented. Add to that the fact that the webmention.io endpoint doesn’t offer collection information on a response (i.e. no offset or total number of pages), I couldn’t find a way to use SWR here.
So, my current implementation uses a state to keep the current page stored, another state to handle the mentions array, and useEffect to handle the request. The “Load More” button is disabled once the last request brings back an empty array.
Thanks to Remy Sharp, handling outbound mentions from one website to others is quite easy and provides an option for each use case or preference possible.
The quickest and easiest way is to head over to Webmention.app, get an API token, and set up a web hook. Now, if you have RSS feed in place, the same thing is just as easy with an IFTT applet, or even a deploy hook.
If you prefer to avoid using yet another third-party service for this feature (which I totally get), Remy has open-sourced a CLI package called wm which can be ran as a postbuild script.
But that’s not enough to handle outbound mentions. In order for our mentions to include more than simply the originating URL, we need to add microformats to our information. Microformats are key because it’s a standardized way for sites to distribute content in a way that Webmention-enabled sites can consume.
At their most basic, microformats are a kind of class-based notations in markup that provide extra semantic meaning to each piece. In the case of a blog post, we will use two kinds of microformats:
h-entry: the post entry
h-card: the author of the post
Most of the required information for h-entry is usually in the header of the page, so the header component may end up looking something like this:
<header class="h-entry"> <!-- the post date and time --> <time datetime="2020-04-22T00:00:00.000Z" class="dt-published"> 2020-04-22 </time> <!-- the post title --> <h1 class="p-name"> Webmentions with NextJS </h1> </header>
And that’s it. If you’re writing in JSX, remember to replace class with className, that datetime is camelCase (dateTime), and that you can use the new Date('2020-04-22').toISOString() function.
It’s pretty similar for h-card. In most cases (like mine), author information is below the article. Here’s how my page’s footer looks:
<footer class="h-card"> <!-- the author name --> <span class="p-author">Atila Fassina</span> <!-- the authot image--> <img alt="Author’s photograph: Atila Fassina" class="u-photo" src="/images/internal-avatar.jpg" lazy /> </footer>
Now, whenever we send an outbound mention from this blog post, it will display the full information to whomever is receiving it.
Wrapping up
I hope this post has helped you getting to know more about Webmentions (or even about IndieWeb as a whole), and perhaps even helped you add this feature to your own website or app. If it did, please consider sharing this post to your network. I will be super grateful! 😉
I’ll be the first to admit that I’m writing this article, in part, because it’s something I look up often and want to be able to find it next time. Formatting a date string that you get from an API in JavaScript can take many shapes — anything from loading all of Moment.js to have very finite control, or just using a couple of lines to update it. This article is not meant to be comprehensive, but aims to show the most common path of human legibility.
UTC is an extremely common date format. You can generally recognize it by the Z at the end of the string. Here’s an example: 2020-05-25T04:00:00Z. When I bring data in from an API, it’s typically in UTC format.
If I wanted to format the above string in a more readable format, say May 25, 2020, I would do this:
First, I’m passing in options for how I want the output to be formatted. There are many, many other options we could pass in there to format the date in different ways. I’m just showing a fairly common example.
Next, I’m creating a new Date instance that represents a single moment in time in a platform-independent format.
return new Date(date)
Finally, I’m using the .toLocaleDateString() method to apply the formatting options.
return new Date(date).toLocaleDateString(undefined, options)
Note that I passed in undefined. Not defining the value in this case means the time will be represented by whatever the default locale is. You can also set it to be a certain area/language. Or, for apps and sites that are internationalized, you can pass in what the user has selected (e.g. 'en-US' for the United States, 'de-DE' for Germany, and so forth). There’s a nice npm package that includes list of more locales and their codes.
Hope that helps you get started! And high fives to future Sarah for not having to look this up again in multiple places. 🤚
It’s not at the level of demand as, say, container queries, but being able to make “masonry” layouts in CSS has been a big ask for CSS developers for a long time. Masonry being that kind of layout where unevenly-sized elements are layed out in ragged rows. Sorta like a typical brick wall turned sideways.
The layout alone is achievable in CSS alone already, but with one big caveat: the items aren’t arranged in rows, they are arranged in columns, which is often a deal-breaker for folks.
/* People usually don't want this */ 1 4 6 8 2 7 3 5 9
/* They want this *. 1 2 3 4 5 6 7 8 9
If you want that ragged row thing and horizontal source order, you’re in JavaScript territory. Until now, that is, as Firefox rolled this out under a feature flag in Firefox Nightly, as part of CSS grid.
An implementation of this proposal is now available in Firefox Nightly. It is disabled by default, so you need to load about:config and set the preference layout.css.grid-template-masonry-value.enabled to true to enable it (type “masonry” in the search box on that page and it will show you that pref).
Grid isn’t Masonry, because it’s a grid with strict rows and columns. If you take another look at the layout created by Masonry, we don’t have strict rows and columns. Typically we have defined rows, but the columns act more like a flex layout, or Multicol. The key difference between the layout you get with Multicol and a Masonry layout, is that in Multicol the items are displayed by column. Typically in a Masonry layout you want them displayed row-wise.
[…]
Speaking personally, I am not a huge fan of this being part of the Grid specification. It is certainly compelling at first glance, however I feel that this is a relatively specialist layout mode and actually isn’t a grid at all. It is more akin to flex layout than grid layout.
By placing this layout method into the Grid spec I worry that we then tie ourselves to needing to support the Masonry functionality with any other additions to Grid.
This is an experimental implementation — being discussed as a possible CSS specification. It is NOT yet official, and likely will change. Do not write blog posts saying this is definitely a thing. It’s not a thing. Not yet. It’s an experiment. A prototype. If you have thoughts, chime in at the CSSWG.
Houdini?
Last time there was chatter about native masonry, it was mingled with idea that the CSS Layout API, as part of Houdini, could do this. That is a thing, as you can see by opening this demo (repo) in Chrome Canary.
I’m not totally up to speed on whether Houdini is intended to be a thing so that ideas like this can be prototyped in the browser and ultimately moved out of Houdini, or if the ideas should just stay in Houdini, or what.
I was in a situation recently where I wanted to show an iPhone on a website. I wanted users to be able to interact with an application demo on this “mock” phone, so it had to be rendered in CSS, not an image. I found a great library called marvelapp/devices.css. The library implemented the device I needed with pure CSS, and they looked great, but there was a problem: the devices it offered were not responsive (i.e. they couldn’t be scaled). An open issue listed a few options, but each had browser incompatibilities or other issues. I set out to modify the library to make the devices responsive.
The original library was written in Sass and implements the devices using elements with fixed sizing in pixels. The authors also provided a straightforward HTML example for each device, including the iPhone X we’ll be working with throughout this article. Here’s a look at the original. Note that the device it renders, while detailed, is rather large and does not change sizes.
Here’s the approach
There are three CSS tricks that I used to make the devices resizable:
calc(), a CSS function that can perform calculations, even when inputs have different units
--size-divisor, a CSS custom property used with the var() function
@media queries separated by min-width
Let’s take a look at each of them.
calc()
The CSS calc() function allows us to change the size of the various aspects of the device. The function takes an expression as an input and returns the evaluation of the function as the output, with appropriate units. If we wanted to make the devices half of their original size, we would need to divide every pixel measurement by 2.
Before:
width: 375px;
After:
width: calc(375px / 2);
The second snippet yields a length half the size of the first snippet. Every pixel measurement in the original library will need to be wrapped in a calc() function for this to resize the whole device.
var(–size-divisor)
A CSS variable must first be declared at the beginning of the file before it can be used anywhere.
:root { --size-divisor: 3; }
With that, this value is accessible throughout the stylesheet with the var() function. This will be exceedingly useful as we will want all pixel counts to be divided by the same number when resizing devices, while avoiding magic numbers and simplifying the code needed to make the devices responsive.
width: calc(375px / var(--size-divisor));
With the value defined above, this would return the original width divided by three, in pixels.
@media
To make the devices responsive, they must respond to changes in screen size. We achieve this using media queries. These queries watch the width of the screen and fire if the screen size crosses given thresholds. I chose the breakpoints based on Bootstrap’s sizes for xs, sm, and so on.
There is no need to set a breakpoint at a minimum width of zero pixels; instead, the :root declaration at the beginning of the file handles these extra-small screens. These media queries must go at the end of the document and be arranged in ascending order of min-width.
Changing the values in these queries will adjust the magnitude of resizing that the device undergoes. Note that calc() handles floats just as well as integers.
Preprocessing the preprocessor with Python
With these tools in hand, I needed a way to apply my new approach to the multi-thousand-line library. The resulting file will start with a variable declaration, include the entire library with each pixel measurement wrapped in a calc() function, and end with the above media queries.
Rather than prepare the changes by hand, I created a Python script that automatically converts all of the pixel measurements.
This function, given a string containing NNpx, returns calc(NNpx / var(--size-divisor));. Throughout the file, there are fortunately only three matches for pixels: NNpx, NNpx; and NNpx);. The rest is just string concatenation. However, these tokens have already been generated by separating each line by space characters.
def build_file(scss): out = ':root {\n\t--size-divisor: 3;\n}\n\n' for line in scss: tokens = line.split(' ') for i in range(len(tokens)): if 'px' in tokens[i]: tokens[i] = scale(tokens[i]) out += ' '.join(tokens) out += "@media (min-width: 576px) {\n \ :root {\n\t--size-divisor: 2;\n \ }\n}\n\n@media (min-width: 768px) {\n \ :root {\n\t--size-divisor: 1.5;\n \ }\n}\n\n@media (min-width: 992px) { \ \n :root {\n\t--size-divisor: 1;\n \ }\n}\n\n@media (min-width: 1200px) { \ \n :root {\n\t--size-divisor: .67;\n }\n}" return out
This function, which builds the new library, begins by declaring the CSS variable. Then, it iterates through the entire old library in search of pixel measurements to scale. For each of the hundreds of tokens it finds that contain px, it scales that token. As the iteration progresses, the function preserves the original line structure by rejoining the tokens. Finally, it appends the necessary media queries and returns the entire library as a string. A bit of code to run the functions and read and write from files finishes the job.
if __name__ == '__main__': f = open('devices_old.scss', 'r') scss = f.readlines() f.close() out = build_file(scss) f = open('devices_new.scss', 'w') f.write(out) f.close()
This process creates a new library in Sass. To create the CSS file for final use, run:
sass devices_new.scss devices.css
This new library offers the same devices, but they are responsive! Here it is:
While the results of this process are pretty compelling, it was a bit of work to get them. Why didn’t I take a simpler approach? Here are three more approaches with their advantages and drawbacks.
zoom
One initially promising approach would be to use zoom to scale the devices. This would uniformly scale the device and could be paired with media queries as with my solution, but would function without the troublesome calc() and variable.
This won’t work for a simple reason: zoom is a non-standard property. Among other limitations, it is not supported in Firefox.
Replace px with em
Another find-and-replace approach prescribes replacing all instances of px with em. Then, the devices shrink and grow according to font size. However, getting them small enough to fit on a mobile display may require minuscule font sizes, smaller than the minimum sizes browsers, like Chrome, enforce. This approach could also run into trouble if a visitor to your website is using assistive technology that increases font size.
This could be addressed by scaling all of the values down by a factor of 100 and then applying standard font sizes. However, that requires just as much preprocessing as this article’s approach, and I think it is more elegant to perform these calculations directly rather than manipulate font sizes.
scale()
The scale() function can change the size of entire objects. The function returns a <transform-function>, which can be given to a transform attribute in a style. Overall, this is a strong approach, but does not change the actual measurement of the CSS device. I prefer my approach, especially when working with complex UI elements rendered on the device’s “screen.”
Chris Coyier prepared an example using this approach.
In conclusion
Hundreds of calc() calls might not be the first tool I would reach for if implementing this library from scratch, but overall, this is an effective approach for making existing libraries resizable. Adding variables and media queries makes the objects responsive. Should the underlying library be updated, the Python script would be able to process these changes into a new version of our responsive library.
Compared to the past, modern browsers have become really efficient at rendering the tangled web of HTML, CSS, and JavaScript code a typical webpage provides. It takes a mere milliseconds to render the code we give it into something people can use.
What could we, as front-end developers, do to actually help the browser be even faster at rendering? There are the usual best practices that are so easy to forget with our modern tooling — especially in cases where we may not have as much control over generated code. We could keep our CSS under control, for instance, with fewer and simpler selectors. We could keep our HTML under control; keep the tree flatter with fewer nodes, and especially fewer children. We could keep our JavaScript under control; while being careful with our HTML and CSS manipulations.
Actually, modern frameworks such as Vue and React do help out a good bit with that last part.
I would like to explore a CSS property that we could use to help the browser figure out what calculations it can reduce in priority or maybe even skip altogether.
This property is called contain. Here is how MDN defines this property:
The contain CSS property allows an author to indicate that an element and its contents are, as much as possible, independent of the rest of the document tree. This allows the browser to recalculate layout, style, paint, size, or any combination of them for a limited area of the DOM and not the entire page, leading to obvious performance benefits.
A simple way to look at what this property provides is that we can give hints to the browser about the relationships of the various elements on the page. Not necessarily smaller elements, such as paragraphs or links, but larger groups such as sections or articles. Essentially, we’re talking about container elements that hold content — even content that can be dynamic in nature. Think of a typical SPA where dynamic content is being inserted and removed throughout the page, often independent of other content on the page.
A browser cannot predict the future of layout changes to the webpage that can happen from JavaScript inserting and removing content on the page. Even simple things as inserting a class name to an element, animating a DOM element, or just getting the dimensions of an element can cause a reflow and repaint of the page. Such things can be expensive and should be avoided, or at least be reduced as much as possible.
Developers can sort of predict the future because they’ll know about possible future changes based on the UX of the page design, such as when the user clicks on a button it will call for data to be inserted in a div located somewhere in the current view. We know that’s a possibility, but the browser does not. We also know that there’s a distinct possibility that inserting data in that div will not change anything visually, or otherwise, for other elements on the page.
Browser developers have spent a good amount of time optimizing the browser to handle such situations. There are various ways of helping the browser be more efficient in such situations, but more direct hints would be helpful. The contain property gives us a way to provide these hints.
The various ways to contain
The contain property has three values that can be used individually or in combination with one another: size, layout, and paint. It also has two shorthand values for common combinations: strict and content. Let’s cover the basics of each.
Please keep in mind that there are a number of rules and edge cases for each of these that are covered in the spec. I would imagine these will not be of much concern in most situations. Yet, if you get an undesired result, then a quick look at the spec might be handy.
There is also a style containment type in the spec that this article will not cover. The reason being that the style containment type is considered of little value at this time and is currently at-risk of being removed from the spec.
Size containment
size containment is actually a simple one to explain. When a container with this containment is involved in the layout calculations, the browser can skip quite a bit because it ignores the children of that container. It is expected the container will have a set height and width; otherwise, it collapses, and that is the only thing considered in layout of the page. It is treated as if it has no content whatsoever.
Consider that descendants can affect their container in terms of size, depending on the styles of the container. This has to be considered when calculating layout; with size containment, it most likely will not be considered. Once the container’s size has been resolved in relation to the page, then the layout of its descendants will be calculated.
size containment doesn’t really provide much in the way of optimizations. It is usually combined with one of the other values.
Although, one benefit it could provide is helping with JavaScript that alters the descendants of the container based on the size of the container, such as a container query type situation. In some circumstances, altering descendants based on the container’s size can cause the container to change size after the change was done to the descendants. Since a change in the container’s size can trigger another change in the descendants you could end up with a loop of changes. size containment can help prevent that loop.
Here’s a totally contrived example of this resizing loop concept:
In this example, clicking the start button will cause the red box to start growing, based on the size of the purple parent box, plus five pixels. As the purple box adjusts in size, a resize observer tells the red square to again resize based on the size of the parent. This causes the parent to resize again and so on. The code stops this process once the parent gets above 300 pixels to prevent the infinite loop.
The reset button, of course, puts everything back into place.
Clicking the checkbox “set size containment” sets different dimensions and the size containment on the purple box. Now when you click on the start button, the red box will resize itself based on the width of the purple box. True, it overflows the parent, but the point is that it only resizes the one time and stops; there’s no longer a loop.
If you click on the resize container button, the purple box will grow wider. After the delay, the red box will resize itself accordingly. Clicking the button again returns the purple box back to its original size and then the red box will resize again.
While it is possible to accomplish this behavior without use of the containment, you will miss out on the benefits. If this is a situation that can happen often in the page the containment helps out with page layout calculations. When the descendants change internal to the containment, the rest of the page behaves as if the changes never happened.
Layout containment
layout containment tells the browser that external elements neither affect the internal layout of the container element, nor does the internal layout of the container element affect external elements. So when the browser does layout calculations, it can assume that the various elements that have the layout containment won’t affect other elements. This can lower the amount of calculations that need to be done.
Another benefit is that related calculations could be delayed or lowered in priority if the container is off-screen or obscured. An example the spec provides is:
[…] for example, if the containing box is near the end of a block container, and you’re viewing the beginning of the block container
The container with layout containment becomes a containing box for absolute or fixed position descendants. This would be the same as applying a relative position to the container. So, keep that in mind how the container’s descendants may be affected when applying this type of containment.
On a similar note, the container gets a new stacking context, so z-index can be used the same as if a relative, absolute, or fixed position was applied. Although, setting the top, right, bottom, or left properties has no effect on the container.
Here’s a simple example of this:
Click the box and layout containment is toggled. When layout containment is applied, the two purple lines, which are absolute positioned, will shift to inside the purple box. This is because the purple box becomes a containing block with the containment. Another thing to note is that the container is now stacked on top of the green lines. This is because the container now has a new stacking context and follows those rules accordingly.
Paint containment
paint containment tells the browser none of the children of the container will ever be painted outside the boundaries of the container’s box dimensions. This is similar to placing overflow: hidden; on the container, but with a few differences.
For one, the container gets the same treatment as it does under layout containment: it becomes a containing block with its own stacking context. So, having positioned children inside paint containment will respect the container in terms of placement. If we were to duplicate the layout containment demo above but use paint containment instead, the outcome would be much the same. The difference being that the purple lines would not overflow the container when containment is applied, but would be clipped at the container’s border-box.
Another interesting benefit of paint containment is that the browser can skip that element’s descendants in paint calculations if it can detect that the container itself is not visible within the viewport. If the container is not in the viewport or obscured in some way, then it’s a guarantee that its descendants are not visible as well. As an example, think of a nav menu that normally sits off-screen to the left of the page and it slides in when a button is clicked. When that menu is in its normal state off-screen, the browser just skips trying to paint its contents.
Containments working together
These three containments provide different ways of influencing parts of rendering calculations performed by the browser. size containment tells the browser that this container should not cause positional shifting on the page when its contents change. layout containment tells the browser that this container’s descendants should not cause layout changes in elements outside of its container and vice-versa. paint containment tells the browser that this container’s content will never be painted outside of the container’s dimensions and, if the container is obscured, then don’t bother painting the contents at all.
Since each of these provide different optimizations, it would make sense to combine some of them. The spec actually allows for that. For example, we could use layout and paint together as values of the contain property like this:
.el { contain: layout paint; }
Since this is such an obvious thing to do, the spec actually provides two shorthand values:
Shorthand
Longhand
content
layout paint
strict
layout paint size
The content value will be the most common to use in a web project with a number of dynamic elements, such as large multiple containers that have content changing over time or from user activity.
The strict value would be useful for containers that have a defined size that will never change, even if the content changes. Once in place, it’ll stay the intended size. A simple example of that is a div that contains third-party external advertising content, at industry-defined dimensions, that has no relation to anything else on the page DOM-wise.
Performance benefits
This part of the article is tough to explain. The problem is that there isn’t much in the way of visuals about the performance benefits. Most of the benefits are behind-the-scenes optimizations that help the browser decide what to do on a layout or paint change.
As an attempt to show the contain property’s performance benefits, I made a simple example that changes the font-size on an element with several children. This sort of change would normally trigger a re-layout, which would also lead to a repaint of the page. The example covers the contain values of none, content, and strict.
The radio buttons change the value of the contain property being applied to the purple box in the center. The “change font-size” button will toggle the font-size of the contents of the purple box by switching classes. Unfortunately, this class change is also a potential trigger for re-layout. If you’re curious, here is a list of situations in JavaScript and then a similar list for CSS that trigger such layout and paint calculations. I bet there’s more than you think.
My totally unscientific process was to select the contain type, start a performance recording in Chome’s developer tools, click the button, wait for the font-size change, then stop the recording after another second or so. I did this three times for each containment type to be able to compare multiple recordings. The numbers for this type of comparison are in the low milliseconds each, but there’s enough of a difference to get a feel for the benefits. The numbers could potentially be quite different in a more real-world situation.
But there are a few things to note other than just the raw numbers.
When looking through the recording, I would find the relevant area in the timeline and focus there to select the task that covers the change. Then I would look at the event log of the task to see the details. The logged events were: recalculate style, layout, update layer tree, paint, and composite layers. Adding the times of all those gives us the total time of the task.
The event log with no containment.
One thing to note for the two containment types is that the paint event was logged twice. I’ll get back to that in a moment.
Completing the task at hand
Here are the total times for the three containment types, three runs each:
Containment
Run 1
Run 2
Run 3
Average
none
24 ms
33.8 ms
23.3 ms
27.03 ms
content
13.2 ms
9 ms
9.2 ms
10.47 ms
strict
5.6 ms
18.9 ms
8.5 ms
11 ms
The majority of the time was spent in layout. There were spikes here and there throughout the numbers, but remember that these are unscientific anecdotal results. In fact, the second run of strict containment had a much higher result than the other two runs; I just kept it in because such things do happen in the real world. Perhaps the music I was listening to at the time changed songs during that run, who knows. But you can see that the other two runs were much quicker.
So, by these numbers you can start to see that the contain property helps the browser render more efficiently. Now imagine my one small change being multiplied over the many changes made to the DOM and styling of a typical dynamic web page.
Where things get more interesting is in the details of the paint event.
Layout once, paint twice
Stick with me here. I promise it will make sense.
I’m going to use the demo above as the basis for the following descriptions. If you wish to follow along then go to the full version of the demo and open the DevTools. Note that you have to open up the details of the “frame” and not the “main” timeline once you run the performance tool to see what I’m about to describe.
Showing frame details open and main details closed in DevTools
I’m actually taking screenshots from the “fullpage” version since DevTools works better with that version. That said, the regular “full” version should give roughly the same idea.
The paint event only fired once in the event log for the task that had no containment at all. Typically, the event didn’t take too long, ranging from 0.2 ms to 3.6 ms. The deeper details is where it gets interesting. In those details, it notes that the area of paint was the entire page. In the event log, if you hover on the paint event, DevTools will even highlight the area of the page that was painted. The dimensions in this case will be whatever the size of your browser viewport happens to be. It will also note the layer root of the paint.
Paint event details
Note that the page area to the left in the image is highlighted, even outside of the purple box. Over to the right, are the dimensions of the paint to the screen. That’s roughly the size of the viewport in this instance. For a future comparison, note the #document as the layer root.
Keep in mind that browsers have the concept of layers for certain elements to help with painting. Layers are usually for elements that may overlap each other due to a new stacking context. An example of this is the way applying position: relative; and z-index: 1; to an element will cause the browser to create that element as a new layer. The contain property has the same effect.
There is a section in DevTools called “rendering” and it provides various tools to see how the browser renders the page. When selecting the checkbox named “Layer borders” we can see different things based on the containment. When the containment is none then you should see no layers beyond the typical static web page layers. Select content or strict and you can see the purple box get converted to its own layer and the rest of the layers for the page shift accordingly.
Layers with no containmentLayers with containment
It may be hard to notice in the image, but after selecting content containment the purple box becomes its own layer and the page has a shift in layers behind the box. Also notice that in the top image the layer line goes across on top of the box, while in the second image the layer line is below the box.
I mentioned before that both content and strict causes the paint to fire twice. This is because two painting processes are done for two different reasons. In my demo the first event is for the purple box and the second is for the contents of the purple box.
Typically the first event will paint the purple box and report the dimensions of that box as part of the event. The box is now its own layer and enjoys the benefits that applies.
The second event is for the contents of the box since they are scrolling elements. As the spec explains; since the stacking context is guaranteed, scrolling elements can be painted into a single GPU layer. The dimensions reported in the second event is taller, the height of the scrolling elements. Possibly even narrower to make room for the scrollbar.
First paint event with content containmentSecond paint event with content containment
Note the difference in dimensions on the right of both of those images. Also, the layer root for both of those events is main.change instead of the #document seen above. The purple box is a main element, so only that element was painted as opposed as to whole document. You can see the box being highlighted as opposed to the whole page.
The benefits of this is that normally when scrolling elements come into view, they have to be painted. Scrolling elements in containment have already been painted and don’t require it again when coming into view. So we get some scrolling optimizations as well.
Again, this can be seen in the demo.
Back to that Rendering tab. This time, check “Scrolling performance issue” instead. When the containment is set to none, Chrome covers the purple box with an overlay that’s labeled “repaints on scroll.”
DevTools showing “repaints on scroll” with no containment
If you wish to see this happen live, check the “Paint flashing” option.
Please note: if flashing colors on the screen may present an issue for you in some way, please consider not checking the “Paint flashing” option. In the example I just described, not much changes on the page, but if one were to leave that checked and visited other sites, then reactions may be different.
With paint flashing enabled, you should see a paint indicator covering all the text within the purple box whenever you scroll inside it. Now change the containment to content or strict and then scroll again. After the first initial paint flash it should never reappear, but the scrollbar does show indications of painting while scrolling.
Paint flashing enabled and scrolling with no containmentPaint flashing and scrolling with content containment
Also notice that the “repaints on scroll” overlay is gone on both forms of containment. In this case, containment has given us not only some performance boost in painting but in scrolling as well.
An interesting accidental discovery
As I was experimenting with the demo above and finding out how the paint and scrolling performance aspects worked, I came across an interesting issue. In one test, I had a simple box in the center of page, but with minimal styling. It was essentially an element that scrolls with lots of text content. I was applying content containment to the container element, but I wasn’t seeing the scrolling performance benefits described above.
The container was flagged with the “repaints on scroll” overlay and the paint flashing was the same as no containment applied, even though I knew for a fact that content containment was being applied to the container. So I started comparing my simple test against the more styled version I discussed above.
I eventually saw that if the background-color of the container is transparent, then the containment scroll performance benefits do not happen.
I ran a similar performance test where I would change the font-size of the contents to trigger the re-layout and repaint. Both tests had roughly the same results, with only difference that the first test had a transparent background-color and the second test had a proper background-color. By the numbers, it looks like the behind-the-scenes calculations are still more performant; only the paint events are different. It appears the element doesn’t become its own layer in the paint calculations with a transparent background-color.
The first test run only had one paint event in the event log. The second test run had the two paint events as I would expect. Without that background color, it seems the browser decides to skip the layer aspect of the containment. I even found that faking transparency by using the same color as the color behind the element works as well. My guess is if the container’s background is transparent then it must rely on whatever is underneath, making it impossible to separate the container to its own paint layer.
I made another version of the test demo that changes the background-color of the container element from transparent to the same color used for the background color of the body. Here are two screenshots showing the differences when using the various options in the Rendering panel in DevTools.
Rendering panel with transparent background-color
You can see the checkboxes that have been selected and the result to the container. Even with a content containment applied, the box has “repaints on scroll” as well as the green overlay showing painting while scrolling.
Rendering panel with background-color applied
In the second image, you can see that the same checkboxes are selected and a different result to the container. The “repaints on scroll” overlay is gone and the green overlay for painting is also gone. You can see the paint overlay on the scrollbar to show it was active.
Conclusion: make sure to apply some form of background color to your container when applying containment to get all the benefits.
Here’s what I used for the test:
This is the bottom of the page
This article has covered the basics of the CSS contain property with its values, benefits, and potential performance gains. There are some excellent benefits to applying this property to certain elements in HTML; which elements need this applied is up to you. At least, that’s what I gather since I’m unaware of any specific guidance. The general idea is apply it to elements that are containers of other elements, especially those with some form of dynamic aspect to them.
Some possible scenarios: grid areas of a CSS grid, elements containing third-party content, and containers that have dynamic content based on user interaction. There shouldn’t be any harm in using the property in these cases, assuming you aren’t trying to contain an element that does, in fact, rely in some way on another element outside that containment.
Browser support is very strong. Safari is the only holdout at this point. You can still use the property regardless because the browser simply skips over that code without error if it doesn’t understand the property or its value.