A guided tour of cutting-edge CSS you can use now

Welcome to a tour of cutting-edge CSS you can use now. These examples were inspired by this article: 5 CSS snippets every front-end developer should know in 2024. Front-end web developer Teri Shelton, in collaboration with designer Andy Krikawa, put together these examples.

At the May 21, 2024 Front-End Tech Community of Practice, we'll demonstrate how they work and share some ideas on use cases. There's so much to cover: from subgrid to container queries and :has() to color-scheme... and more! It's going to be like a guided tour of favorite sites: stop, look at the view, snap a photo (screenshot!), ask a question, hop back on the bus.

Balance headlines & text

Balance headlines and small amounts of text automatically to have more balance across multiple lines (balance) and not have orphans (pretty).

Balance is only supported for 6 or fewer lines in Chromium and 10 or fewer lines in Firefox.

Adjust your window size to see how these examples wrap differently based on their text-wrap property.

This is a sample headline that is completely normal - nothing to see here

Cat ipsum dolor sit amet, meow meow you are my owner so here is a dead bird cats making all the muffins. Paw your face to wake you up in the morning.

This is a sample headline with balanced text wrap set to balance

Cat ipsum dolor sit amet, meow meow you are my owner so here is a dead bird cats making all the muffins. Paw your face to wake you up in the morning.

This is a sample headline with balanced text wrap set to pretty

Cat ipsum dolor sit amet, meow meow you are my owner so here is a dead bird cats making all the muffins. Paw your face to wake you up in the morning.

To enable these text-wrap options:

.balance {
	text-wrap: balance;
}

h1, h2, h3, h4, h5, h6 {
	text-wrap: balance;
}

.pretty {
	text-wrap: pretty;
}

Object fit

Move over background-image! Now we can use { object-fit: cover; } to get a similar result. It makes an image behave like a background image, but with the advantage of retaining the semantics of an img tag. We can also use { object-fit: scale-down; }. The image acts as a container for its own contents.

Images set inside some custom (non-Bootstrap) cards

top of ornate street lantern in foreground with sun shining through green leaves of trees in background

Custom card 1

I'm a custom card and the intrinsic size for the image I'm holding is 300x200.

sidewalk with large tall trees lining its sides on a sunny day

Custom card 2

I'm a custom card and the intrinsic size for the image I'm holding is 125x188.

pathway entrance with stone pillars and autumnal leaves on the trees lining the path

Custom card 3

I'm a custom card and the intrinsic size for the image I'm holding is 400x400.

img {
	/* nothing set...image will break outside of its containing element, if it needs to */
}

Images set inside custom cards, but better

top of ornate street lantern in foreground with sun shining through green leaves of trees in background

Custom card 1

I'm a custom card and the intrinsic size for the image I'm holding is 300x200.

sidewalk with large tall trees lining its sides on a sunny day

Custom card 2

I'm a custom card and the intrinsic size for the image I'm holding is 125x188.

pathway entrance with stone pillars and autumnal leaves on the trees lining the path

Custom card 3

I'm a custom card and the intrinsic size for the image I'm holding is 400x400.

img {
	object-fit: cover;
	width: 100%;
}

And taking it one step further

top of ornate street lantern in foreground with sun shining through green leaves of trees in background

Custom card 1

I'm a custom card and the intrinsic size for the image I'm holding is 300x200.

sidewalk with large tall trees lining its sides on a sunny day

Custom card 2

I'm a custom card and the intrinsic size for the image I'm holding is 125x188.

pathway entrance with stone pillars and autumnal leaves on the trees lining the path

Custom card 3

I'm a custom card and the intrinsic size for the image I'm holding is 400x400.

img {
	object-fit: cover;
	width: 100%;
	border-radius: 1em 1em 0 0;
	height: 50%;
	object-position: center bottom;
}

Aspect ratio

Aspect ratio can be an incredibly handy tool to resolve traditionally frustrating layout situations.

As you can see with the image examples below, aspect-ratio used alone may not be your best bet, but combined with object-fit and object-position, can guarantee you will always have the same layout.

Normal responsive image

Good boy Dubs, a happy and fluffy malamute, has a purple W bandana around his neck and his mouth open in a smile
img {
	width: 100%;
	height: auto;
}

16:9 aspect ratio

Good boy Dubs, a happy and fluffy malamute, has a purple W bandana around his neck and his mouth open in a smile
img {
	width: 100%;
	height: auto;
	aspect-ratio: 16 / 9;
}

1:1 aspect ratio

Good boy Dubs, a happy and fluffy malamute, has a purple W bandana around his neck and his mouth open in a smile
img {
	width: 100%;
	height: auto;
	aspect-ratio: 1 / 1;
}

Here are the same examples with object-fit and object-position also applied:

Normal responsive image

Good boy Dubs, a happy and fluffy malamute, has a purple W bandana around his neck and his mouth open in a smile
img {
	width: 100%;
	height: auto;
}

16:9 aspect ratio

Good boy Dubs, a happy and fluffy malamute, has a purple W bandana around his neck and his mouth open in a smile
img {
	width: 100%;
	height: auto;
	object-fit: cover;
	object-position: center;
	aspect-ratio: 16 / 9;
}

1:1 aspect ratio

Good boy Dubs, a happy and fluffy malamute, has a purple W bandana around his neck and his mouth open in a smile
img {
	width: 100%;
	height: auto;
	object-fit: cover;
	object-position: center;
	aspect-ratio: 1 / 1;
}

Getting into video, iframes in particular, is where things have traditionally been annoying and frustrating to wrangle. With aspect-ratio, you can ensure your video is always the same aspect ratio regardless if the window or screen size.

Normal YouTube embed - fixed width & height

<iframe width="560" height="315"
src="https://www.youtube.com/embed/3Mcm4ko6Mgw"
title="YouTube video player"></iframe>

YouTube embed with fixed width and aspect ratio 1/1

<iframe width="500"
src="https://www.youtube.com/embed/3Mcm4ko6Mgw"
title="YouTube video player"></iframe>
iframe {
	aspect-ratio: 1 / 1;
}

YouTube embed with aspect ratio 16/9

<iframe src="https://www.youtube.com/embed/3Mcm4ko6Mgw"
title="YouTube video player"></iframe>
iframe {
	width: 100%;
	height: auto;
	aspect-ratio: 16 / 9;
}

Margin inline

margin-inline is a shorthand for setting the inline margin (aka, the left and right margin for horizontal writing modes). To demonstrate the benefits, I've thrown in a few more logical properties in these examples. Logical properties were new to me!

The long form way

placeholder image

This is a customized card that's left aligned. It has an image avatar, some text and a solid left border.

placeholder image

This is a customized card that's right aligned. The card has direction: rtl; set for its contents. It has a solid right border.

For the first example above I added some styles to the .card wrapper:

.card {
	padding-left: 1.5em;
	padding-right: 1em;
	border-left: 1.5em solid #4b2e83;
}
img {
	margin-right: 1em;
}

Then, for second example (assuming direction: rtl; is set), some styles needed to be reset and others added:

.card {
	padding-right: 1.5em;
	padding-left: 1em;
	border-left: 0;
	border-right: 1.5em solid #4b2e83;
}
img {
	margin-right: 0;
	margin-left: 1em;
}

Now with logical properties

placeholder image

This is a customized card that's left aligned. It has an image avatar, some text and a solid left border.

placeholder image

This is a customized card that's right aligned. The card has direction: rtl; set for its contents. This has a solid right border.

For both the examples directy above, this is all that was needed:

.card {
	padding-inline-start: 1.5em;
	padding-inline-end: 1em;
	border-inline-start: 1.5em solid #4b2e83;
}
img {
	margin-inline-end: 1em;
}

Text underline offset

We now have text-underline-offset which controls the distance of the underline from the text (which is coming from text-decoration: underline).

Some "older" ways

  • See video of Dubs - do nothing and take the default, which is:
    {
    	text-decoration: underline;
    }
    
  • See video of Dubs - remove the default and use border-bottom
    {
    	text-decoration: none;
    	border-bottom: 1px solid
    }
  • See video of Dubs - remove the default and use a box-shadow and put padding around the link:
    {
    	text-decoration: none;
    	box-shadow: 0 3px;
    	padding: 4px;
    }

What the cool kids are using now

  • See video of Dubs - decide how far from the text you want the underline like:
    {
    	text-underline-offset: .25em;
    }
  • See video of Dubs - pair it with text-decoration-thickness, so:
    {
    	text-underline-offset: .25em;
    	text-decoration-thickness: 0.2em;
    }
  • See video of Dubs - go far with the offset and thicken up the border (probably not the best idea?) with
    {
    	text-underline-offset: .5em;
    	text-decoration-thickness: 0.3em;
    }

Outline offset

Adding outline-offset to an element pushes the outline away by the value you give the property. Using a negative value pulls the outline into the element.

The general idea

This has a positive offset This has a negative offset

Links in a paragraph

This first link has no outline-offset on focus! Here's some more text so we can see it in action among other links that have outline-offset on focus. And were we to keep tabbing through a paragraph - here's a link to another place.

Buttons

Here are some things we can do with buttons and adding outline-offset on focus.

Scroll margin top/bottom

Scroll margin (scroll-margin) sets the scroll margins of an element inside its container. Each child element of a container has a snap position. scroll-margin lets us tinker that position as if the child element has a margin.

Child elements stacked vertically

1
2
3
4
5
6

What's going on here, in a nutshell:

.scroll-container {
	.box-1 {scroll-margin: 0;}
	.box-2 {scroll-margin: 1em;}
	.box-3 {scroll-margin: 2em;}
	.box-4 {scroll-margin: 0;}
	.box-5 {scroll-margin: 4em;}
	.box-6 {scroll-margin: 0;}
}

On this page

These elements don't have a scroll-margin.

These two elements do have a scroll-margin of 8em.

Here's where scroll-margin is in play:

#object-fit,
#text-wrap-balance {
	scroll-margin-top: 5em;
}

Accent color

The accent-color property currently works on input elements for checkbox, radio, and range, and the progress element.

Note the background color on the progress and range elements is set automatically based on the color specified.

Default

Accent color specified

Accent color specified

To enable accent color:

.accent1 {
	accent-color: pink;
}
.accent2 {
	accent-color: #4b2e83;
}

Fit content width

Instead of display: inline-block; to have an element's width match its content's size, you can use width: fit-content to achieve the same. This leaves the display property open for other use. The computed box size adjusts to the dimensions created when using fit-content.

The wrapper fills the available space.

The wrapper fits the content because we're using width: fit-content.

CSS Nesting

Basically, like nesting in Sass or LESS, but parsed by the browser instead of compiled a preprocessor. If you are familiar with Sass, this will be almost identical.

Here's an example from this site's stylesheet:

body {
	#accent-color {
		.accent1 {
			accent-color: pink;
		}

		.accent2 {
			accent-color: var(--wp--preset--color--husky-purple);
		}
	}
}

The browser essentially converts this to:

body #accent-color .accent1 {
	accent-color: pink;
}

body #accent-color .accent2 {
	accent-color: var(--wp--preset--color--husky-purple);
}

Also check out what it looks like in the dev tools with the nesting.

:has()

From MDN: "The functional :has() CSS pseudo-class represents an element if any of the relative selectors that are passed as an argument match at least one element when anchored against this element. This pseudo-class presents a way of selecting a parent element or a previous sibling element with respect to a reference element by taking a relative selector list as an argument."

A parent selector

In this example, we are adding a top margin only if the card has an image. This creates a more uniform style for when the card doesn't have an image. We're using an exaggerated margin on the image cards to easily see a difference.

Good boy Dubs, a happy and fluffy malamute, has a purple W bandana around his neck and his mouth open in a smile
Card title

Some quick example text to build on the card title and make up the bulk of the card's content.

Go somewhere
Good boy Dubs, a happy and fluffy malamute, has a purple W bandana around his neck and his mouth open in a smile
Card title

Some quick example text to build on the card title and make up the bulk of the card's content.

Go somewhere
Card title

Some quick example text to build on the card title and make up the bulk of the card's content.

Go somewhere
.card:has(img) {
	h2, h3, h4, h5, h6 {
		margin-top: 2rem;
	}
}

Change parent and child based on parent contents

In this example we'll apply different styling to an image based on whether it has a caption.

Example without caption

Cupcake ipsum dolor sit amet cotton candy powder liquorice shortbread. Croissant macaroon dessert danish caramels donut caramels. Candy canes apple pie gummi bears tootsie roll cookie.

cherry blossoms at UW

Liquorice bear claw shortbread toffee muffin. Brownie pudding pudding candy cake. Bonbon powder macaroon soufflé lollipop. Bonbon topping chocolate bar chocolate bar tart oat cake marshmallow.

Example with caption

Cupcake ipsum dolor sit amet cotton candy powder liquorice shortbread. Croissant macaroon dessert danish caramels donut caramels. Candy canes apple pie gummi bears tootsie roll cookie.

cherry blossoms at UW
Cherry blossoms on the Quad at UW

Liquorice bear claw shortbread toffee muffin. Brownie pudding pudding candy cake. Bonbon powder macaroon soufflé lollipop. Bonbon topping chocolate bar chocolate bar tart oat cake marshmallow.

/* All images */
.text-image {
	border: 1px solid var(--wp--preset--color--spirit-pink);
	padding: 1rem;

	img {
		width: 100%;
		height: auto;
		margin-bottom: 1rem;
	}
}

/* Images with figcaptions, style the container and change the image styling */
.text-image:has(figcaption) {
	border: 1px solid var(--wp--preset--color--spirit-teal);
	background-color: #efefef;

	figure {
		border: 1px solid var(--wp--preset--color--spirit-candle-green);
		padding: 1rem;
		background-color: white;

		img {
			margin-bottom: 0.5rem;
		}

		figcaption {
			font-style: italic;
			font-weight: bold;
		}
	}
}

Show an error message outside your form if something happens inside your form, using only CSS

This example shows how you can toggle content based on form actions without using JavaScript.

Check the "Agree to terms and conditions" box below to see the example error message.

Hello! Only show me if the box is checked!

Looks good!
Looks good!
@
Please choose a username.
Please provide a valid city.
Please select a valid state.
Please provide a valid zip.
You must agree before submitting.

First, let's see our markup:

<div class="form-example">
		<div class="error alert alert-danger">
			<p>Hello! Only show me if the box is checked!</p>
		</div>
		<form class="row mt-5 mb-5 g-3 needs-validation" novalidate>
			...
		</form>
	</div>
</div>

And here is the CSS to make this work:

.form-example:has(input:checked) {
	.error {
		display: block;
	}
}
.form-example:not(:has(input:checked)) {
	.error {
		display: none;
	}
}

💡 Tip: additional functional pseudo-classes

:is(), :where(), and :not() are the other functional pseudo-classes. They can be used with :has() and each other. Have you used these yet?