What's New with CSS?

What's New with CSS?

We acknowledge the traditional custodians of this land, the Kaurna people.

What's New with CSS?

The world around us is changing

The web we build is changing

CSS hasn’t changed at all in the last 20 years

Anonymous comment on YouTube

CSS has changed!

Native Nesting

	.parent {
		background: chartreuse;

		.child {
			background: lemonchiffon;
		}
	}		
	/* Renders out the same as */
	.parent {
		background: chartreuse;
	}

	.parent .child {
		background: lemonchiffon;
	}
	
	/* Lots of styles */
	.opinions-box {
		text-align: center;
	}

	.opinions-box a {
		text-decoration: none;
	}

	.opinions-box__text-input {
		font-size: 1.2em;
	}

	.opinions-box__text-input--is-inactive {
		opacity: 0.5;
	}
	
	/* Utilise existing selectors */
	.opinions-box {
		text-align: center;

		& a {
			text-decoration: none;
		}

		&__text-input {
			font-size: 1.2em;

			&--is-inactive {
				opacity: 0.5;
			}
		}
	}
	
	.container {
		& .feed {
			& .card {
				& .header {
					& .title {
						& a {
							& .icon {
								& svg {
									color: chartreuse;
								}
							}
						}
					}
				}
			}
		}
	}
	.parent {
		background: chartreuse;

		& .child {
			background: lemonchiffon;
		}
	}
	
.feed {
	.card {
		background: chartreuse;

		h2 {}

		ul {
			&.tags {}

			&.share {
				svg {
					color: rebeccapurple;
				}
			}
		}
	}
}
	.card {
		background: rebeccapurple;

		.feed & {
			background: chartreuse;
		}
	}
/* Is the same as */
.card {
	background: rebeccapurple;
}

.feed {
	.card {
		background: chartreuse;
	}
}

chrome icons
2023
edge icons
2023
safari icons
2023
firefox icons
2023
Native Nesting

Pseudo Selectors

*:has() {}

*:where() {}

*:not() {}

*:is() {}
<div class="block">
	<p>This list has <span class="count"></span> item<span class="plural">s</span></p>
	<ul>
		<li>Item 1</li>
		<li>Item 2</li>
		<li>Item 3</li>
	</ul>
</div>
<div class="block">
	<p>This list has <span class="count"></span> item<span class="plural">s</span></p>
	<ul>
		<li>Item 1</li>
		<li>Item 2</li>
		<li>Item 3</li>
		<li>Item 4</li>
		<li>Item 5</li>
	</ul>
</div>
	
.block {
	--items: '1';

	.count::before {
		content: var(--items);
	}

	&:has(ul li:nth-child(2)) {
		--items: '2';
	}

	&:not(:has(ul li:nth-child(2))) {
		.plural {
			display: none;
		}
	}
}
	&:has(ul li:nth-child(3)) {
		--items: '3';
	}

	&:has(ul li:nth-child(4)) {
		--items: '4';
	}

	&:has(ul li:nth-child(5)) {
		--items: '5';
	}

	/* And so on */		
<div class="field">
	<p class="error">Error: you must agree to the Terms and Conditions</p>
	<input type="checkbox" id="agree" name="agree" />
	<label for="agree">I agree to the terms and conditions</label>
</div>
	
.field {
	&:has(input[name="agree"]:checked) {
		.error {
			visibility: hidden;
		}
	}

	&:has(input[name="agree"]:not(:checked)) {
		outline: 5px solid var(--red);
		background: var(--red_light);
	}
}
firefox icons
2022
safari icons
2022
edge icons
2022
chrome icons
2023
:has()
firefox icons
2020
safari icons
2015
edge icons
2021
chrome icons
2021
:not()

Light & Dark Themes

	.box {
		background: light-dark(lemonchiffon, darkslateblue);
		color: light-dark(darkslateblue, lemonchiffon);
	}

	.dark_box {
		color-scheme: dark;
	}

	.light_box {
		color-scheme: light
	}
<div class="theme dark">
	<p>This section has a dark colour scheme</p>
</div>
<div class="theme light">
	<p>This section has a light colour scheme</p>
</div>
<div class="theme">
	<p>This section's colour scheme will be based on the system settings</p>
</div>
<div class="theme toggle">
	<p>This section's colour scheme will change based on the theme toggle</p>
</div>
	
	.theme {
		background: light-dark(lemonchiffon, var(--navy));
		color: light-dark(var(--navy), lemonchiffon);
		color-scheme: light dark;

		&.dark {
			color-scheme: dark;
		}

		&.light {
			color-scheme: light;
		}
	}
	.container {
		&:has(.toggle input[value="dark"]:checked) {
			.theme.toggle {
				color-scheme: dark;
			}
		}

		&:has(.toggle input[value="light"]:checked) {
			.theme.toggle {
				color-scheme: light;
			}
		}
	}
firefox icons
2023
safari icons
2024
edge icons
2024
chrome icons
2024
:light-dark()
firefox icons
2022
safari icons
2019
edge icons
2020
chrome icons
2020
color-scheme

Vertical Alignment

	.box {
		display: flex;
		justify-content: center;
		align-items: center;
	}	
<div class="box old">
	I am centred
</div>
<div class="box new">
	I am also centred, but with one line of CSS
</div>
	
	.old {
		display: flex;
		align-items: center;
	}

	.new {
		align-content: center;
	}
firefox icons
2024
safari icons
2024
edge icons
2024
chrome icons
2024
align-content

CSS Subgrid

	<div class="container">
		<div class="card">
			<img src="/img/image.jpg" alt="" /> 
			<h2>Building a Custom Link Shortener</h2> 
			<time>11 Jul 2024</time>
			<ul class="tags">
				<li>dev</li>
				<li>tools</li>
				<li>netlify</li>
			</ul>
			<ul class="share">
				<li><svg>...</svg></li>
				<li><svg>...</svg></li>
			</ul>
		</div>

		<div class="card">
			<img src="/img/image.jpg" alt="" />  
			<h2><a href="/accessibility-for-everyone" target="_blank">Accessibility for Everyone (and by Everyone)</a></h2> 
			<time>23 Feb 2024</time>
			<ul class="tags">...</ul>
			<ul class="share">...</ul>
		</div>
	</div>
.container {
	display: grid;
	grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
}
	.card {
		display: grid;
		grid-template-columns: repeat(2, 1fr);
		gap: 10px;
		grid-template-areas: 
			'image image'
			'date .'
			'title title'
			'tags socials';
	}
	.card {
		img {
			grid-area: image;
		}

		h2 {
			grid-area: title;
		}

		time {
			grid-area: date;
		}

		.tags {
			grid-area: tags;
		}

		.share {
			grid-area: socials;
		}
	}
	.container {
		grid-auto-rows: 200px auto 1fr auto;
		// Image, Date, Title, Tags+Socials
	}

	.card {
		grid-template-rows: subgrid;
		grid-row: span 4;
	}
firefox icons
2019
safari icons
2022
edge icons
2023
chrome icons
2023
Subgrid

Stylable Selects

<label for="field_status">Lego Set Status</label>

<selectlist id="field_status" name="status">
	<button type="selectlist">
		<span class="sr-only">Select Tags</span>
		<selectedoption></selectedoption>
	</button>
	<listbox>
		<div class="options-container">
			<option value="" hidden>
				<span>Select Status</span>
			</option>
			<option value="assembled">
				<img src="img/assembled.jpg" alt="" />
				<span class="text">Assembled</span>
			</option>
			/* More options */
			<option value="unreleased">
				<img src="/img/new.jpg" alt="" />
				<span class="text">Unreleased</span>
			</option>
		</div>
	</listbox>
</selectlist>			
	selectlist {
		button {
			&[type="selectlist"] {
				padding: 0.5em;
				border-radius: 10px;
				margin: 0;
			}
		}

		.options-container {
			display: flex;
			flex-wrap: wrap;
		}
	}
	option,
	selectedoption {
		display: grid;
		border-radius: 10px;
		cursor: pointer;
		gap: 2px;
	}

	option {
		&:hover,
		&:focus {
			background: purple;
			color: var(--white);
		}

		&[hidden] {
			display: none;
		}
	}
<selectlist id="context" name="context">
	<button type="selectlist">
		<selectedoption></selectedoption>
		<span class="sr-only"></span>
	</button>
	<listbox>
		<option value="" hidden><span>Select Action</span></option>
		<option value="commit">
			<svg>...</svg>
			<span>Commit</span>
		</option>
		<option value="merge">
			<svg>...</svg>
			<span>Merge</span>
		</option>
		<!-- More options here -->
	</listbox>
</selectlist>
firefox icons
safari icons
edge icons
🚩2022
chrome icons
🚩2022
selectlist

CSS is changing

  • Native Nesting
  • :has
  • :is
  • :not
  • :where
  • color-scheme
  • light-dark()
  • align-content
  • subgrid
  • :user-invalid
  • :user-valid
  • color-mix()
  • text-spacing-trim
  • field-sizing
  • position-anchor
  • inset-area
  • Scroll snap
  • animation-timeline
  • text-wrap: balance
  • aspect-ratio
  • word-break: autophrase
  • @layer
  • @scope
  • @property
  • @media (width >= 400px)
  • @container

Try something new!

Thank You 👏

Questions?
kapers.dev/feedback