We gotta talk about CSS! That’s my favorite thing! It’s always on the table for a good Chris’ Corner edition, but sometimes focusing on it entirely is best.

Klint Finley called it “The modern web’s underrated powerhouse” for GitHub’s publication The ReadME Project back in February, and I’m inclined to agree. Although the days of CSS being considered underrated are waning. I get the sense that plenty of people find it complicated or generally just don’t like it, but those same people still respect its power. There is also one big undeniable fact: every website uses it. All of ’em. And it controls nearly every aspect of how a website looks, which, ya know, if you care at all about how successfully your website does what it sets out to do, matters a whole heck of a lot.

Anyway, rather than listen to me prattle on about how good CSS is, let’s look at some interesting ideas pulled off in CSS, new things coming to CSS, dealing with browser support, and future ideas.

It used to be that if you used CSS to set the width and height of an image and the aspect ratio that those two numbers formed didn’t match the aspect ratio of the image, it would squish the image awkwardly. Almost certainly not the effect you want. But now we’ve got the object-fit property in CSS, and specifically the cover and contain values, which prevent the squishing. With cover, the image might crop, so you’re sending more image data than you needed to, perhaps, but you’ll be achieving the design you’re after.

With that as a foundation, let me let Henry Desroches take over in Using Focal Points, Aspect Ratio & Object-Fit To Crop Images Correctly. In conjunction with object-fit there is object-position which allows you to set an XY coordinate from where the image scales.

Try clicking different positions on the source image and see how the sized images change which part of the image they show. This is just good to know. If you’re ultimately cropping images, you don’t have to settle for the defaults.

You’d think “position this element over by this other element” would be easy peasy for CSS, but you’d be wrong. There has been no mechanism for that up until now. The new thing is called anchor positioning, and Jhey Tompkins has the scoop in Tether elements to each other with CSS anchor positioning.

The big obvious use case to all this for me is basically: tooltips powered by footnotes. Essentially I want bits of UI (a phrase, link, or [?] button) that can be hovered or otherwise interacted with to reveal a bit more information. But that information is elsewhere in the DOM. Wherever I want in the DOM that makes sense for my project. Finally, I’ll be able to do that (once is supported across all browsers or if I use the polyfill).

I had a little play with this myself, thanks to Jhey’s guidance in this post.

I didn’t get into all the scrolling stuff you have to think about or the @try style positioning, but I do think all that is very cool. Edge detection stuff is another thing that we’d have to lean on JavaScript to do normally, but never love having to do so.

As good as CSS has been lately with new features working across browsers, it’s still a thing we have to think about. Fortunately, CSS saw this coming long ago and has @supports rules that can help us conditionally apply CSS in supporting (or not) situations.

Stephanie Eckles has the most up-to-date information on all this in Testing Feature Support for Modern CSS.

An example I can think of is the kick-ass :has() selector. As I write, :has() isn’t supported in Firefox yet. So if we wanted to know that in CSS before we use it, we could do:

@supports selector(:has(a)) {
  /* styles when :has() is supported */

It is useful to think about what your plan is for detection, though. Like does it matter if Firefox doesn’t support :has() for what you are trying to do? Are you prepared to do it some other way if it’s not? As an example, I was talking to Eric Meyer the other day, and somehow table row/column highlighting came up, and he made a demo with :has().

Now you’d have to decide: how important is that effect? As written, in Firefox, just the cell you hover over highlights, so you could decide that’s fine, and then you don’t need any feature detection at all; you just let it fail. Alternatively, you could decide to use a @supports query in CSS and highlight the entire row if :has() isn’t supported, which is a similar effect (if not quite as cool).

Another decision you could make is to do the test in JavaScript and use a JavaScript-powered fallback if necessary. Good news, Stephanie has a JavaScript testing tool just for this. It uses a number of different techniques to report if a feature is supported or not. Ultimately you could use it for our :has() test like:

<script type="module">
  import * as SupportsCSS from "https://cdn.skypack.dev/supports-css@0.1.5";
  const tests = ["has"];
  SupportsCSS.init({ tests });

  if (!SupportsCSSTests.results.has) {
      "<style>tr:hover { background: rgba(255,0,0,0.33); }</style>"

Ya know, just while we’re talking about feature detection in CSS, a typography-specific feature tester crossed my desk the other day, font-tech and font-format:

@supports font-tech(palettes) {
  .palette {
    display: block;

@supports font-format(woff2) {
  div {
    display: block;

Just interesting to me; I’ve never seen those specific functions before.

Wait, wait, I gotta do one more about feature detection. There are some things that you cannot detect in CSS, and is kind of a pain in the butt to detect in JavaScript also. Ahmad Shadeed made this point in Do we need CSS flex-wrap detection? The point has been made again and again that we need certain state detections like a :stuck selector for position: sticky; elements, and it sounds likely we’ll get that. But “is wrapped or not” is another form of state, I’d say. Ahmad’s use case was like… if a line in a flexbox layout wraps, it’s saying: “there isn’t room for these on one line”. But where that is is totally arbitrary based on content and element size. But if we knew exactly when that break was, we could, for example, use that exact moment to break a line of navigation into a hamburger rather than guessing at some magic number size. Strong point, I think.

I have an unnatural affinity for the “yellow fade technique”. It’s this idea that works with the :target selector in CSS and on-page anchor links. Imagine a table of contents where you click a link, and it jumps the page down (or scrolls, I suppose if you use scroll-behavior: smooth). How do you know exactly where the page is trying to take you to and draw your attention toward? The element is probably at the top of the page, but it might not be if the page is too short. The yellow fade technique just makes it way more clear. When that jump happens, that element becomes the :target, and you apply a background-color animation to it (light yellow!) that draws attention to it. It’s just nice, I think. There are some classic examples on CodePen, naturally.

The idea can also be applied to an element being added to the DOM, drawing attention to the fact that it just appeared. That’s what Bramus Van Damme does in The Yellow Fade Technique with Modern CSS using @starting-style.

Erm… @starting-style, what’s that? It’s basically built for the yellow fade technique. 😍.

div {
  transition: background-color 0.5s;
  background-color: transparent;

  @starting-style {
    background-color: yellow;

With that, you don’t need a once-running @keyframes which ends up being more verbose and harder to understand. This way the transition essentially runs once on <div>s being added to the DOM. So I guess it’s not as perfect for the table of contents use-case, but it’s still pretty cool. Now I’m thinking about other DOM-entrance animations, like list items that slide in, or modals that fade in.

Sometimes we get new CSS stuff, and it’s just great right on the surface. Oh, the oklch() color function? Neat, it can do vibrant P3 colors. Neat, it has perceptually uniform lightness. Neat, the gradient interpolation is arguably nicer for some color pairings.

But then, over time, it turns out it’s how the new CSS feature is combined and interacts with other features that makes that feature shine even more. Aww, that was sweet, wasn’t it? We’re all better with a little help from our friends.

That’s what I was thinking about reading Adam Argyle’s A color-contrast() strategy for complimentary translucent backgrounds. If you haven’t seen color-contrast() yet, it’s a function that will pick the most visually contrasting color from a list of colors against a given color. That given color is probably a --custom-property meaning it can change and thus you don’t know ahead of time what the most contrasting color is. That’s awesome! But Adam takes it a little bit further. He wants to kick up the contrast even more by putting a slightly transparent black or white color behind the text.

html {
  --bg: hsl(var(--hue) 50% 50%);
h1 {
  --text: color-contrast(var(--bg) vs black, white);
  color: var(--text);
  background: hsl(0 0% 0% / 40%);
@supports (background: hsl(from red h s l)) {
  h1 {
    background: oklch(from color-contrast(var(--text) vs black,white) l c h / 40%);

So cool. So individually, color-contrast() is neat, OKLCH is neat, @supports is neat, and the relative color syntax is neat, but combined, they really shine.