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.
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.
form:has([required]:valid) {
background: lightgreen;
}
⚠️ Firefox 120- doesn't support it.
: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.
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.
<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;
}
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;
}
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.
.scroll-container {
display: flex;
scroll-snap-type: both mandatory;
}
.scroll-area {
scroll-snap-align: center;
width: 100%;
flex-shrink: 0;
}
Supports multiple visible items.
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;
}
}
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 functions: min(), max(), minmax(), clamp()
The browser uses the smallest (or largest with max()) of the listed values.
section {
width: min(100vw, 1280px);
}
clamp() takes minimum value, ideal value (used when it's between min and max), and maximum value.
p {
width: clamp(45ch, 50%, 75ch);
}
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;
}
Details/summary
Expandable content with CSS only
Title
OneTwo
Three
<details>
<summary>Title</summary>
Content
</details>
Title
OneTwo
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.
Dialog
Native modal window
<dialog>
Content
</dialog>
dialog.show();
dialog.close();
Newer Popover is like Dialog, but attached to an element.
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.