Designing in the Browser, Season 3

As part of Designcember, Google Chrome Developer Relations Engineer Una Kravets published season three of “Designing in the Browser”, a series of videos on exploring user interface design through the lens of modern web technology. In these short and on-point videos, you’ll learn about certain Web/CSS features and DevTools along the way.

The third season consists of four episodes and is all about being responsive:

  1. Container Queries →
  2. Macro & Micro Layouts →
  3. Dark Mode →
  4. Responsive images & art direction →

The first video on container queries is also embedded at the top of this post.

All episode of “Designing in the Browser” (YouTube Playlist) →

Magic PostCSS Custom Combinators using :has()

Similar to how you can (ab)use :nth-child() to create “new” CSS selectors, you can leverage :has() to create some typical combinators. Brandon McConnell did just that:

  • y:has(+ x) selects the first preceding y sibling of x:

  • y:has(~ x) selects all preceding y sibling of x:

  • x + y, y:has(+ x) selects the first preceding and first succeeding y siblings of x:

And those are just the simple ones. Selecting “all preceding y elements up to the third one” for example is possible using y:has(~ * + * + x) 😅

🤔 Not familiar with the CSS :has() selector? Read up on it right here.

~

To more easily work with that, Brandon also created a (still unpublished?) PostCSS plugin that supports fictitious CSS Selectors which automatically translate to their working counterparts. For example:

  • x - y translates to y:has(+ x)
  • x -- y translates to y:has(~ x)
  • x % y translates to x + y, y:has(+ x)

Here’s a pen with all combinators he came up with:

Oh that’s clever! Perhaps we might one day see these selectors land in CSS itself?

👨‍🔬 Only Safari Technology Preview 137 supports :has() at the time of writing, so you’ll need to use that browser to see anything get colored in the “Actually Rendered” rows there.

During his work Brandon noticed a bug in Safari’s implementation in which it selects some non-targeted elements, so you might see some faulty ones.

A story on web engines interoperability related to wavy text decorations

This is a blog post explaining the process of fixing an issue related to wavy text decorations in Blink, that ended up with a similar fix in WebKit.

Interesting to read about the work and thought-process behind at-first sight “seemingly simple features” such as the wavy underlines for Spelling and Grammar errors. Be sure to also read the two linked posts by Delan if you want to lift the hood up even further.

A story on web engines interoperability related to wavy text decorations →

Reverse-Scrolling Columns with CSS Scroll-Timeline

The other day I saw “Alternate Column Scroll” by Manoela Ilic float by. It’s a tutorial/demo in which some of the content columns scroll in the opposite direction, powered by Locomotive Scroll.

Based on Manoela’s demo I recreated the scrolling part with CSS @scroll-timeline.

~

~

# The Basic Structure

I based myself upon Manoela’s original markup and start with one .columns wrapper containing three nested .column elements. The columns that should reverse scroll, get the class .column-reverse added as well.

<div class="columns">
  <div class="column column-reverse">…</div>
  <div class="column">…</div>
  <div class="column column-reverse">…</div>
</div>

The basic layout is done using CSS Grid:

/* Three column layout */
.columns {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
}

Inside each .column, the contained .column__item elements are positioned using CSS Flexbox:

.column {
  display: flex;
  flex-direction: column;
}

💡 We choose flexbox here because we’ll change flex-direction for the .column-reverse columns later on.

Combined, the starter template looks like this:

See the Pen Alternate Column Scroll (CSS @scroll-timeline first + JS ScrollTimeline fallback + Polyfill) by Bramus (@bramus) on CodePen.

~

# Implementing Reverse Scrolling using CSS @scroll-timeline

# Adjusting the columns

In browsers that support CSS @scroll-timeline we shift the .column-reverse columns up by translating them by -100%. As they are now entirely off-screen, we counteract that by adding 100vh. That way their bottom edge touches the bottom edge of the viewport.

/* Shift entire column up, but not so much that it goes out of view */
.column-reverse {
  transform: translateY(calc(-100% + 100vh));
}

As we don’t want .columns to grow by that translation, we also prevent the content from bleeding out of it.

/* As we're about to shift content out of .columns, we need it to hide its overflow */
.columns {
  overflow-y: hidden;
}

Finally, as the columns will be scrolling in a reversed direction, we also reverse the order for the items inside each column. That way the first item will be at the bottom of the reversed column.

/* Flip item order in reversed columns */
.column-reverse {
  flex-direction: column-reverse;
}

💡 To detect whether a browser supports CSS @scroll-timeline we use @supports and feature check on the telltale animation-timeline CSS Property.

/* Scroll-Timeline Supported, Yay! */
@supports (animation-timeline: works) {
  …
}

Browsers that support @scroll-timeline also support animation-timeline.

~

# Setting up + Attaching the Animation and Scroll-Timeline

Our ScrollTimeline is the default Scroll-Timeline of scrolling through the document from top to bottom. To set it up we don’t need anything special.

/* Set up scroll-timeline */
@scroll-timeline scroll-in-document {
    source: auto; /* Default scroll-timeline: scrolling in the document */
}

The Animation itself starts at the translation we’ve already done (-100% + 100vh). The end position is a translation in the opposite direction: 100% down, but minus 100vh to keep it on-screen.

/* Set up Animation */
@keyframes adjust-position {
  /* Start position: shift entire column up, but not so that it goes out of view */
  from {
    transform: translateY(calc(-100% + 100vh));
  }
  /* End position: shift entire column down, but not so that it goes out of view */
  to {
    transform: translateY(calc(100% - 100vh));
  }
}

☝️ I know, you can omit the from here, but I’m including it here for educational purposes.

Using the animation-timeline we finally link up our ScrollTimeline to the .column-reverse elements:

/* Hook our animation with the timeline to our columns */
.column-reverse {
  animation: 1s adjust-position linear forwards;
  animation-timeline: scroll-in-document;
}

💭 Curious to learn more about CSS @scroll-timeline? Go read this article here on bram.us covering @scroll-timeline in more detail.

~

# All together now

Combined, our demo now looks like this:

See the Pen Reverse-Scrolling Columns with CSS Scroll-Timelinel (+ JS ScrollTimeline Polyfill Fallback) by Bramus (@bramus) on CodePen.

👨‍🔬 The demo above will only work in Chromium 89+ with the #experimental-web-platform-features flag enabled through chrome://flags. That’s because it’s the only browser that supports CSS @scroll-timeline at the time of writing.

~

# Implementing Reverse Scrolling with JS, for browsers that don’t speak CSS @scroll-timeline

# The Polyfill

In browsers that don’t support CSS @scroll-timeline, the Scroll-Timeline Polyfill by Robert Flack is loaded and used.

// Polyfill for browsers with no Scroll-Timeline support
import "https://flackr.github.io/scroll-timeline/dist/scroll-timeline.js";

The polyfill will register itself when needed.

🤔 Polyfill?

A polyfill is a piece of code (or plugin) that provides the technology that you, the developer, expect the browser to provide natively — What is a Polyfill?

~

# WAAPI + ScrollTimeline

The animation itself is a regular WAAPI animation and does exactly the same as the CSS Animation described above. The animation is, however, extended with a linked ScrollTimeline instance the polyfill provides us.

Above that, the entire code is wrapped inside a little snippet that feature detects CSS support for animation-timeline. When it’s not supported, the contained code will be executed.

// Fallback for browsers that don't support CSS ScrollTimeline
if (!CSS.supports("animation-timeline: foo")) {

  // As we're about to shift content out of .columns, we need it to hide its overflow
  document.querySelector(".columns").style.overflowY = "hidden";

  // Set up ScrollTimeline instance
  const timeline = new ScrollTimeline({
    scrollSource: document.documentElement,
    timeRange: 1,
    fill: "both"
  });

  // Loop all eligible columns
  document.querySelectorAll(".column-reverse").forEach(($column) => {
    // Flip item order in reverse columns
    $column.style.flexDirection = "column-reverse";

    // Hook Animation
    $column.animate(
      {
        transform: [
          "translateY(calc(-100% + 100vh))",
          "translateY(calc(100% - 100vh))"
        ]
      },
      {
        duration: 1,
        fill: "both",
        timeline
      }
    );
  });
}

💭 Curious to learn more about WAAPI + ScrollTimeline? Go read this article on CSS-Tricks that covers WAAPI + ScrollTimeline in more detail.

~

# All together now

With that, our final demo now becomes this:

See the Pen Reverse-Scrolling Columns with CSS Scroll-Timelinel (+ JS ScrollTimeline Polyfill Fallback) by Bramus (@bramus) on CodePen.

I know, there’s a FOUC here. I’ll leave it to you, reader, as an exercise to solve.

~

hwb() – a color notation for humans?

On the web we can define colors in several Color Spaces. By default we’ve always been using sRGB, but newer ones such as P3 are on the way. To describe a color in CSS (in the sRGB Color Space) we can use the functions rgb() and hsl() today already. Both are well supported, even in their space-separated form.

An additional function to describe colors — one that I quickly skimped over in Michelle’s roundup — is hwb(). Stefan Judis spills the details:

hwb stands for hue (a color angle), whiteness (a percentage) and blackness (a percentage)hwb(10deg 10% 10%). Pick a color and adjust its lightness by adding Black or White.

The entire concept and possible colors can be visualised in a triangle spanned by Black, White and the color of choice.

Stefan provides a handy interactive tool to understand this “easy color notation”, answering questions such as “What happens if you add both black and white?”.

hwb() – a color notation for humans? →

CSS in 2022


Photo by Jr Korpa on Unsplash

Things have been going hard for CSS in 2021. The CSS Working Group has cranked out a ton of work, polishing existing CSS features and specifying lots of new ones too — with experimental implementations already having landed in several browsers.

Apart from supporting new features, browser vendors also focussed on fixing the top 5 browser compatibility painpoints (#compat2021) to make our dev-lives easier.

With 2021 coming to an end, let’s take a look at which CSS language features we can expect to land in browsers in 2022.

~

~

# Before we dive in …

To be clear: The list you’re about to read contains my personal predictions. I don’t have any private/inside information, nor do I have a Crystal Ball. What I do have, however, are some public sources to base these predictions on: by closely monitoring the CSS Working Group Issue Tracker and by following along in the various Browser Vendor Issue Trackers, I have enough information for compiling this list.

As with all predictions, though: they could be wrong. I am, however, pretty confident that I’ll get most of it right in the end 🙂

Also note that I’m only covering language features that are entirely new or still lack widespread browser support. That’s why features such as logical values and properties or aspect-ratio or Space-Separated Functional Color Notations are not included in this list. Even though they’re fairly new or not commonly used, they already have proper browser support.

~

# The Hotlist (Cross-Browser Support)

I’m quite confident the following features will see support across all browsers sometime in 2022. Some features already have support in one or more browsers, with others to follow. Learning one of the following CSS features listed below in 2022 will pay off.

~

# Container Queries

Container Queries — part of css-contain-3 — allow authors to style elements according to the size or appearance of a container. For size-based container queries this is similar to a @media query, except that it will evaluate against the size of a parent container instead of the viewport.

For style-based container queries, you conditionally apply styles based on the calculated value of another CSS property.

main, aside {
  container: inline-size;
}

.media-object {
  display: grid;
  grid-template: 'img' auto 'content' auto / 100%;
}

@container (inline-size > 45em) {
  .media-object {
    grid-template: 'img content' auto / auto 1fr;
  }
}

More info: CSS Container Queries: A First Look + Demo →

Relevant Issues:

~

# Cascade Layers

With Cascade Layers, you can control cascade ordering for same-origin rules properly. You do this by layering your styles using the @layer at-rule. Layers appear before specificity and order of appearance in the cascade.

@import(reset.css) layer(reset); /* 1st layer */

@layer base { /* 2nd layer */
  form input {
    font-size: inherit; 
  }
}

@layer theme { /* 3rd layer */
  input {
    font-size: 2rem;
  }
}

Cascade layers will be supported in Chromium 99 and Firefox 97. They’re also supported in Safari TP 133 (behind a flag), and I expect them to ship in the first quarter of 2022.

More info: The Future of CSS: Cascade Layers (CSS @layer) →

Relevant Issues:

~

# Color Functions

The css-color-5 specification comes with a few niceties when it comes to working with color. It adds two new functions: color-mix(), color-contrast(), and extends existing ones with relative color syntax.

  • The color-mix() function allows you to mix two colors in a given color space.

    .text-primary-dark {
      color: color-mix(var(--theme-primary), black 10%);
    }
    .text-primary-darker {
      color: color-mix(var(--theme-primary), black 20%);
    }
  • The color-contrast() function allows you select the best color from a list of colors that meets or exceeds the contrast criteria when compared to a certain base color.

    /* Compares wheat against tan, sienna, and #d2691e */
    /* Sienna will be selected as it has a contstast of 4.273 against wheat, which exceeds the threshold of AA-large (3) */
    color-contrast(wheat vs tan, sienna, #d2691e to AA-large)
    
  • With the relative color syntax you can manipulate and convert any color to any format.

    :root {
      --color: #ff0000;
    }
    
    .selector {
      /* change the transparency */
      color: hsl(from var(--color) h s l / .5);
      
      /* change the hue */
      color: hsl(from var(--color) calc(h + 180deg) s l);
      
      /* change the saturation */
      color: hsl(from var(--color) h calc(s + 5%) l);
    }

These additions plug functionalities typically provided by CSS Preprocessors. Already supported in WebKit/Safari.

More Info:

Relevant Issues:

~

# New Viewport Units

When working with Viewport Units there’s this longstanding and extremely annoying bug in Safari on iOS where it does not play nice with the vh unit. Setting a container to 100vh will result in an element that’s a wee bit too tall: MobileSafari ignores parts of its UI when calculating 100vh.

Leaving the vh unit as it is, the CSS Working Group introduced several new viewport definitions and accompanying new viewport relative lengths in the css-values-4 specification:

  • svh/svw: 1% of the Small Viewport height/width
  • lvh/lvw: 1% of the Large Viewport height/width
  • dvh/dvw: 1% of the Dynamic Viewport height/width

Also available as logical variants, such as svi/svb

More Info: The Large, Small, and Dynamic Viewports →

Relevant Issues:

The same spec also introduces logs of new mathematical functions to use in calculations. Definitely worth checking out.

~

# :has()

The CSS :has() relational pseudo-class is part of selectors-4. It allows you to more finely select elements, as an element will only match if any of the extra selectors passed into :has() matches at least one element. It’s often dubbed “the parent selector” but it’s way more than that.

/* Matches <a> elements that contain an <img> child */
a:has(img) { … }

/* Matches <a> elements that directly contain an <img> child */
a:has(> img) { … }

/* Matches <section> elements that don’t contain any heading elements: */
section:not(:has(h1, h2, h3, h4, h5, h6))

/* Matches <h1> elements only if they have a <p> element directly following them */
h1:has(+ p) { … }

More Info: The CSS :has() selector is way more than a “Parent Selector” →

Relevant Issues:

~

# Overscroll Behaviour

With the CSS overscroll-behavior property you can override the default behavior when “overscrolling” a container. With it, you can prevent a full reload when a pull-to-refresh gesture is performed, disable rubber banding, contain scrolling within one layer, etc.

See the Pen overscroll-behavior: contain by Aaron Iker (@aaroniker) on CodePen.

Long supported in Firefox (version 36) and Chromium (version 63). Safari is — finally! — catching up.

More info: Customizing Pull-to-Refresh and Overflow Effects with CSS overscroll-behavior

Relevant Issues:

~

# Subgrid

When nesting Grids it’s difficult to line nested grid items up with the main grid. That’s where subgrid comes in: by setting grid-template-columns or grid-template-rows to subgrid, it will line up with the parent grid.

.grid {
  display: grid;
  grid-template-columns: repeat(9, 1fr);
  grid-template-rows: repeat(4, minmax(100px, auto));
}

.item {
  display: grid;
  grid-column: 2 / 7;
  grid-row: 2 / 4;
  grid-template-columns: subgrid;
  grid-template-rows: subgrid;
}

.subitem {
  grid-column: 3 / 6;
  grid-row: 1 / 3;
}

Already supported in Firefox. Chromium is a WIP.

More info: Practical CSS Subgrid Video Tutorials →

Relevant Issues:

~

# Accent Color

Part of css-ui-4 is accent-color

CSS accent-color from the CSS UI specification is here to tint elements with one line of CSS, saving you from customization efforts by providing a way to bring your brand into elements.
form {
  accent-color: hotpink;
}

Using it, you can even create some pixel art using checkboxes 🙃

Supported in Chromium 93+ and Firefox 92+.

More info: CSS accent-color

Relevant Issues:

~

# Media Query Ranges

Thanks to new additions in mediaqueries-4, certain media queries can be rewritten using Media Query Ranges, a syntax that uses ordinary mathematical comparison operators.

/* Old Way */
@media (max-width: 750px) {
	…
}
/* New Way */
@media (width <= 750px) {
	…
}

I didn’t pick up any signal that Safari is actively pursuing this, but let’s assume I’m wrong on this one …

More info: Media Queries Level 4: Media Query Range Contex →

Relevant Issues:

~

# The “Not yet” list (Experimental / Single-Browser Support)

I have a feeling that the following features won’t be supported in all browsers by the end of 2022. They most likely will be in some, mainly behind feature flags. You can invest your time in them if you want, but do understand that you’ll only benefit from using them in a few browsers until broader support lands (and everyone has updated their browsers).

~

# Nesting

Last summer the First Public Working Draft of css-nesting-1 got published, a module that introduces the ability to nest one style rule inside another.

🤔 Working Draft (WD)?

The Working Draft (WD) Maturity Level is the first official phase of the W3C Recommendation Track, and is considered the design phase of a W3C spec. In this phase the CSS Working Group will explore, revise and refine the contents of the module. The first published version of the WD is called the “First Public Working Draft”, which kicks off the WD phase.

From thereon a spec can become a Candidate Recommendation (CR) to finally land on being a Recommendation (REC). In between those three stages there are two transition stages: Last Call Working Draft (LCWD) and Proposed Recommendation (PR)

In visual form, the Recommendation Track looks something like this:

See An Inside View of the CSS Working Group at W3C for more details on all phases.

table.colortable {
  & td {
    text-align: center;
    &.c { text-transform: uppercase }
    &:first-child, &:first-child + td { border: 1px solid black }
  }
  & th {
    text-align: center;
    background: black;
    color: white;
  }
  @nest footer & {
    font-size: 0.8em;
  }
}

It’s a groundbreaking addition to CSS that I really wanted to include in the hotlist above. Unfortunately, I expect only Chromium (and maybe one other vendor) to gain support. Let’s hope I’m wrong on this one. Perhaps a preprocessor extension can open the way to actively using this one in 2022.

More info: The future of CSS: Nesting Selectors →

Relevant Issues:

~

# @scope

With css-cascade-5 (which introduced Cascade Layers) cut off, work on css-cascade-6 has already begun. It introduces Scoping in the Cascade, a way to scope styles to part of the DOM tree.

<div class="dark-theme">
  <a href="#">plum</a>
  <div class="light-theme">
    <a href="#">also plum???</a>
  </div>
</div>
/* When .light-theme and .dark-theme get nested, you may not get the expected result */
.light-theme a { color: purple; }
.dark-theme a { color: plum; }

/* By scoping, we can fix this, as the a elements will be styled by their nearest scope */
@scope (.light-scheme) {
  a { color: darkmagenta; }
}
@scope (.dark-scheme) {
  a { color: plum; }
}

You can also define a scoping limit (lower boundary) for the scope.

It is currently way too early to further dig into it. I expect Chromium to experiment with it by the end of 2022.

~

# @when / @else

A specification that got it’s First Public Working Draft published just before the end of the year — on Dec 21st — is css-conditional-5.

🤔 Working Draft (WD)?

The Working Draft (WD) Maturity Level is the first official phase of the W3C Recommendation Track, and is considered the design phase of a W3C spec. In this phase the CSS Working Group will explore, revise and refine the contents of the module. The first published version of the WD is called the “First Public Working Draft”, which kicks off the WD phase.

From thereon a spec can become a Candidate Recommendation (CR) to finally land on being a Recommendation (REC). In between those three stages there are two transition stages: Last Call Working Draft (LCWD) and Proposed Recommendation (PR)

In visual form, the Recommendation Track looks something like this:

See An Inside View of the CSS Working Group at W3C for more details on all phases.

It includes and extends the functionality of CSS Conditional 4, adding the generalized conditional rule @when and the chained conditional rule @else, as well as introducing font processing queries to the supports query syntax used in @supports rules.
@when media(width >= 400px) and media(pointer: fine) and supports(display: flex) {
  /* A */
} @else supports(caret-color: pink) and supports(background: double-rainbow()) {
  /* B */
} @else {
  /* C */
}

Great addition IYAM. No signal from any vendor though — but that might be mainly due to the holidays happening right now — so I’m guessing not all browsers will include this one by the end of 2022, ergo its place in the “Not yet” list.

More Info: Proposal for CSS @when

Relevant Issues:

~

# Status Quo

Sadly, I’m afraid the following features won’t move that much in 2022. Although these features are very useful by themselves, they need broader browser support before being truly usable.

~

# Scroll-Linked Animations

Thanks to the @scroll-timeline at-rule and animation-timeline property the scroll-animations-1 specification provides, you can link CSS Animations to the scroll offset of a scroll container. As you scroll up and down a container, the linked animation will advance and rewind accordingly.

/* (1) Define Keyframes */
@keyframes adjust-progressbar {
    from {
        transform: scaleX(0);
    }
    to {
        transform: scaleX(1);
    }
}

/* (2) Define a ScrollTimeline */
@scroll-timeline scroll-in-document {
  source: auto;
  orientation: block;
  scroll-offsets: 0, 100%;
}

/* (3) Attach the Animation + set the ScrollTimeline as the driver for the Animation */
#progressbar {
    animation: 1s linear forwards adjust-progressbar;
    animation-timeline: scroll-in-document;
}

Scroll-Linked Animations are currently only supported in Chromium behind a feature flag. Gecko/Firefox has already added syntax parsing support but no actual rendering yet. Although I’m a big fan of Scroll-Linked Animations and have written about them extensively, I’m afraid development will stall as the syntax is under discussion.

More info:

Relevant Issues:

~

# @property (😭)

The CSS Properties and Values API is part of “CSS Houdini”. Using @property this API provides you can register your CSS Custom Properties and give them a certain type (syntax), initial value, and control their inheritance-capability.

@property --my-color {
  syntax: '<color>';
  inherits: false;
  initial-value: #c0ffee;
}
🎩 Houdini, ain't that a magician?

Houdini is a set of low-level APIs that exposes parts of the CSS engine, giving developers the power to extend CSS by hooking into the styling and layout process of a browser’s rendering engine. Houdini is a group of APIs that give developers direct access to the CSS Object Model (CSSOM), enabling developers to write code the browser can parse as CSS, thereby creating new CSS features without waiting for them to be implemented natively in browsers.

It really is magic, hence it's name Houdini. I'd recommend this slidedeck and this video to get you started

Even though I really really REALLY wish for Houdini — and specifically the Properties and Values API part of it — to be available in all browsers, I’m afraid it’ll stay as it is … the relevant WebKit and Gecko bugs simply have not moved at all the past months (years!) 😭

More info: @property: giving superpowers to CSS variables →

Relevant Issues:

~

# What about Safari?

Safari has been taking a ton of sh*t last year and is often called “the new IE6”. I’ve said this myself before, but I no longer wholeheartedly agree with this.

The Safari team has been crushing it lately (Color Functions, :has(), Cascade Layers, …) and above that they’ve been actively hiring for several positions on their team. I wouldn’t be all too surprised if Safari “caught up” in 2022 …

☝️ That still doesn’t change anything to the WebKit monopoly on iOS though, something I’m no fan of. I don’t think the Safari folks are either and can only assume that this is a decision that’s being made above their heads.

~

# In closing

I know, it’s a huge list of what’s most likely to come to CSS in 2022. Which features are you specifically looking forward to? And did I miss any CSS features? Leave a comment or hit me up on Twitter to let me know 🙂

~

To help spread the contents of this post, feel free to retweet one of the announcement tweets:

~

🔥 Like what you see? Want to stay in the loop? Here's how:

Organic Blobs in CSS with border-radius

Nils and Mirko from 9elements create a handy tool to create blobs using border-radius.

When you use eight values specifying border-radius in CSS, you can create organic looking shapes.

Like so:

border-radius: 40% 60% 60% 40% / 70% 30% 70% 30%;

And as that’s CSS, you can also animate that easily:

See the Pen Untitled by Bramus (@bramus)on CodePen.

Hit the post for a detailed explanation on how these eight values work.

Fancy Border Radius Generator →
CSS Border-Radius Can Do That? →

The Fundamentals of CSS Layout

At Chrome Dev Summit 2021, Rachel Andrew hosted a workshop “The Fundamentals of CSS Layout”. If you’re looking to get started with CSS Grid and Flexbox, this is the video you’re looking for.

In this 90-minute workshop you’ll learn about the key CSS layout methods of flexbox and grid. You’ll learn how these layout methods enable responsive, content-aware designs, and how to choose the best layout method for the component you need to build.

“The Fundamentals of CSS Layout” Resources →

Create semi-transparent CSS background images by crossfading them with a transparent GIF

Nice work by Chris: when you crossfade an image — using the proprietary -webkit-cross-fade() — with a transparent gif, you can create semi-transparent background images.

.el {
  background-image: -webkit-cross-fade(
    url(image.jpg),
    url(data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7), /* transparent GIF, base64 encoded */
    50%
);

Clever! WebKit/Chromium based browsers only though (i.e. no Firefox)

Maybe there kinda is background-opacity? →