Modern CSS for 2024

CSS today has advanced features that make browsers powerful, our work easier and JavaScript dependency minimal.

Listed by Radoslav Sharapanov

Masks and filters

CSS masking works like masking in image editors, i.e. obscuring an element's parts falling behind transparent parts of the mask.

Original image

Custom colour

<input type="color" value="#802f9d" aria-label="Select custom colour">

Element masked by custom colour and image


-webkit-mask: var(--logo) center no-repeat;
-webkit-mask-size: contain;
mask: var(--logo) center no-repeat;
mask-size: contain;
  • Useful for theming logos and icons.
  • ⚠️ Prefixed for Chrome 119-.

Another demo: hover on the logos in the footer.

CSS masks at MDN.

Colour filtering

An image can become a single solid color by a combination of CSS filters, generated by css-color-filter-generator.

#802f9d

filter: brightness(0) saturate(100%) invert(19%) 
sepia(46%) saturate(3511%) hue-rotate(269deg) 
brightness(99%) contrast(89%);

Parent selector

Set properties of an element, selected by the presence, or state, of its descendants or next siblings.

:has() opens a world of opportunities. The simplest example below shows how a form changes its properties (background colour) if it contains a checked checkbox.

Please check below

form:has([required]:valid) {
	background: lightgreen;
}

⚠️ Firefox 120- doesn't support it.

:has() at MDN.

:is(), :where() selectors

The :is() CSS pseudo-class function takes a selector list as its argument, and selects any element that can be selected by one of the selectors in that list. This is useful for writing large selectors in a more compact form, no Sass required.

article p, section p, aside p {
	color: #444;
}

↓
:is(article, section, aside) p {
	color: #444;
}

:where() is like :is() with specificity 0.

:is() at MDN.

CSS Grid update: Animations and Subgrid

Grid revolutionised the way we lay content on the web and just keeps improving. Some of its important new features are layout transitions and subgrid.

Animate to auto dimensions

This long standing CSS-only problem is now solved thanks to a grid transitioning row/column size from 0 to 1fr.


One
Two
Three


<div class="expander">
	<div>
		Content
	</div>
</div>
.expander {
	display: grid;
	grid-template-rows: 0fr;
	transition: grid-template-rows .25s;
}

.expander > div {
	min-height: 0;
	overflow: hidden; // Scrollbar flashing during animation
}

:checked ~ .expander {
	grid-template-rows: 1fr;
}

More examples.

Subgrid

A grid child of a grid parent can now become subgrid. When setting the value subgrid on the child's grid-template-columns, grid-template-rows or both, instead of creating a new track listing, the nested grid uses its parent's tracks.

In this example, the images are lined up regardless of the headline size, because they are always positioned on the parent's second row.

Single line

Two
lines

And
lastly,
a three line title

.subgrid-demo {
	display: grid;
	grid-template-columns: repeat(3, 1fr);
	gap: 1rem;
}
.subgrid-demo > * {
	display: grid;
	grid-template-rows: subgrid;
	grid-row: span 2;
}

Subgrid at MDN.

Scroll snapping

CSS scroll snapping is a way to snap content to its parent after a user has finished scrolling. It’s like a magnet that pulls the content to the middle/start/end of its container.

1→
←2→
←3→
←4
.scroll-container {
  display: flex;
  scroll-snap-type: both mandatory;	
}
.scroll-area {
  scroll-snap-align: center;
  width: 100%;
  flex-shrink: 0;
}

Supports multiple visible items.

More demos.

Scroll snap at MDN.

Container queries

Container queries help apply styles to an element based on the size of its container. They are a new alternative to media queries, which apply styles to elements based on viewport size or other device characteristics.

The following CSS stacks a row of cards when their container (not the browser window) is less than 500px wide.

<div class="card-container">
	<div class="card">
		...
	</div>
</div>
.card-container {
	container-type: inline-size;
}

.card {
	display: flex;
}				
		
@container (width < 500px) {
  .card {
	flex-direction: row;
  }
}

Container queries at MDN.

Logical properties

To follow writing direction (Arabic being right-to-left), instead of screen direction, use logical properties:

Border/margin/padding + top, bottom, left, right → block-start, block-end, inline-start, inline-end.

Absolute-positioning with top, right, bottom, left → inset-block-start, inset-inline-end, inset-block-end, inset-inline-start.

An image with text after it and margin-right

An image with text after it and margin-inline-end

The :dir(rtl) pseudo class detects RTL writing direction in the current element. Supported by Firefox, Safari, and Chrome 120.

Logical properties at MDN.

Logical functions: min(), max(), minmax(), clamp()

The browser uses the smallest (or largest with max()) of the listed values.

section {
	width: min(100vw, 1280px);
}

min() at MDN.

clamp() takes minimum value, ideal value (used when it's between min and max), and maximum value.

p {
  width: clamp(45ch, 50%, 75ch);
}

clamp() at MDN.

No need for calc() inside the above functions.

minmax() for CSS Grid

minmax() defines a size range greater than or equal to min and less than or equal to max.

p {
  grid-template-columns: minmax(20px, auto) 1fr 1fr;
}

minmax() at MDN.

Details/summary

Expandable content with CSS only

Title One
Two
Three

<details>
	<summary>Title</summary>
	Content
</details>


Title One
Two
Three
summary {
	&::-webkit-details-marker,
	&::marker {
		display: none;
		content: "";
	}
}
  • Uses the open attribute.
  • Can be animated with JS or CSS Grid transition.
  • The summary style can be fully customised.
  • ⚠️ Can't be forced by CSS to always be open in another context, e.g. Desktop.

details at MDN.

Dialog

Native modal window

Dialog content


<dialog>
	Content
</dialog>
dialog.show();
dialog.close();

Newer Popover is like Dialog, but attached to an element.

More demos.

dialog at MDN.

But wait, there is more

  • [oldie] Description lists for semantic key/value pairing: <dl> <dt> <dd>. Demo chart.
  • [oldie] <progress> bar has been around forever and replaces any complex custom HTML.
  • The inert attribute makes an element non-interactive.
  • Declarative Shadow DOM can encapsulate styles without JavaScript.
  • Scroll timeline advances CSS animations by scroll offset, instead of time.
  • Layers can be used to define the order of precedence of multiple cascade layers.

Thanks for supporting the native web and browsing css.rado.bg.