CSS Stuff I’m Excited After the Last CSSWG Meeting originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.
]]>has:
selector, then 2024 is going to be even more packed with even more ground-breaking additions. Whether a new feature like inline conditionals is just starting or long-term projects are wrapping up, 2024 is already filled with exciting developments — and we’re still in July!
I wanted to share what I think are some of the most interesting and significant features coming to CSS that were examined in the meeting. However, I don’t want you to take the following as an exact recap of the discussions. Instead, I want to bring up the broader topics coming to CSS that had a spotlight at the last meeting. In reality, the features examined have been cooking up for even years and the discussions are geared towards specific cases and new enhancements, rather than defining a whole specification; a work that would be impossible in one meeting.
You can see the exact issues discussed on the CSSWG meeting agenda.
if()
?Since CSS custom properties gained reliable support around 2016, there have been many attempts to apply certain styles depending on a custom property value without, of course, appealing to JavaScript. One of the earliest workarounds for conditional styles was posted by Roman Komarov back in 2016 in “Conditions for CSS Variables”. From there, many other hacks have been documented for making conditional declarations in CSS (including this extremely clever one by Ana Tudor here on CSS-Tricks). In fact, you can find a full list that discusses and compares those workarounds by CSSWG member Lea Verou in her recent article, “Inline conditionals in CSS, now?”.
What’s for sure is that the community has craved a conditional way to apply styles using custom properties. Nowadays, we have a specification for Style Queries that’s capable of the task, but they come with limitations not related to browser support. The biggest of those limitations? We can’t directly style the container that’s queried, so we need some sort of wrapper element around that wrapper in HTML.
<div class="news-container" style="--variant: info">
<p>Here is some good <strong>news</strong></p>
</div>
…in addition to writing the style query:
.news-container {
container-name: news-container;
}
@container news-container style(--variant: info) {
p {
color: blue;
border: 1px solid blue;
}
}
if()
might look likeOn the CSSWG side, there have been discussions about adding an if()
function as far back as 2018. It was of this year — yes, six years later — that the CSSWG resolved to begin working on if()
for CSS. As good as it may look, don’t expect to see if()
in a browser in at least two years! (That’s Lea’s unofficial estimate.) We’ll likely need to wait even longer for enough browser support to begin using it reliably in production. The spec draft is only barely getting started and many things have to pass a test first. For context, the CSS variables working draft began in 2012 and only received wide browser support in 2016.
Syntax-wise, if()
is probably going to borrow the ternary operator from JavaScript and other programming languages, structured like this:
if(a ? b : c)
…where a
is the custom property we are checking and b
are c
are the possible conditional return values. To check for styles, an inline style(--my-property: value)
would be used.
.forecast {
background-color: if(style(--weather: clouds) ? var(--clouds-color): var(--default-color));
}
Even if ?
isn’t used in CSS and :
has a different meaning everywhere else, I think this syntax is the one most people are familiar with, not to mention it also allows seamless conditional chaining.
.forecast {
background-color: if(
style(--weather: clouds) ? var(--clouds-color):
style(--weather: sunny) ? var(--sunny-color);
style( --weather: rain) ? var(--rain-color): var(--default-color)
);
}
if()
improvementsAlthough these probably won’t make it in the initial release, it’s interesting to see how if()
might change between now and sometime further in the future:
style()
query, but we may as well check for media features with an inline media()
query or if a user agent supports a specific property with an inline support()
..my-element {
width: if(media(width > 1200px) ? var(--size-l): var(--size-m));
}
if()
, e.g. just as we can make calculations without calc()
if we are inside a clamp()
or round()
function.Last year, the View Transition API gave us the power to create seamless transitions when navigating between web pages and states. No components or frameworks, no animation libraries — just vanilla HTML and CSS with a light sprinkle of JavaScript. The first implementation of View Transitions was baked into browsers a while back, but it was based on an experimental function defined by Chrome and was limited to transitions between two states (single-page view transitions) without support for transitioning between different pages (i.e., multi-page view transitions), which is what most of us developers are clamoring for. The possibilities for mimicking the behavior of native apps are exciting!
That’s why the CSS View Transitions Module Level 2 is so amazing and why it’s my favorite of all the CSS additions we’re covering in this article. Yes, the feature brings out-of-the-box seamless transitions between pages, but the real deal is it removes the need for a framework to achieve it. Instead of using a library — say React + some routing library — we can backtrack into plain CSS and JavaScript.
Of course, there are levels of complexity where the View Transition API may fall short, but it’s great for countless cases where we just want page transitions without the performance cost of dropping in a framework.
View transitions are triggered when we navigate between two pages from the same-origin. In this context, navigation might be clicking a link, submitting a form, or going back and forth with browser buttons. By contrast, something like using a search bar between same-origin pages won’t trigger a page transition.
Both pages — the one we’re navigating away from and the one we’re navigating to — need to opt into the transition using the @view-transition
at-rule and setting the navigation
property to auto
@view-transition {
navigation: auto;
}
When both pages opt into a transition, the browser takes a “snapshot” of both pages and smoothly fades the “before” page into the “after” page.
In that video, you can see how the old page fades into the new page, and it works thanks to an entire tree of new pseudo-elements that persist through the transition and use CSS animations to produce the effect. The browser will group snapshots of elements with a unique view-transition-name
property that sets a unique identifier on the transition that we can reference, and which is captured in the ::view-transition
pseudo-element holding all of the transitions on the page.
You can think of ::view-transition
as the :root
element for all page transitions, grouping all of the parts of a view transition on the same default animation.
::view-transition
├─ ::view-transition-group(name)
│ └─ ::view-transition-image-pair(name)
│ ├─ ::view-transition-old(name)
│ └─ ::view-transition-new(name)
├─ ::view-transition-group(name)
│ └─ ::view-transition-image-pair(name)
│ ├─ ::view-transition-old(name)
│ └─ ::view-transition-new(name)
└─ /* and so one... */
Notice that each transition lives in a ::view-transition-group
that holds a ::view-transition-image-pair
that, in turn, consists of the “old” and “new” page snapshots. We can have as many groups in there as we want, and they all contain an image pair with both snapshots.
Quick example: let’s use the ::view-transition
“root” as a parameter to select all of the transitions on the page and create a sliding animation between the old and new snapshots.
@keyframes slide-from-right {
from {
transform: translateX(100vw);
}
}
@keyframes slide-to-left {
to {
transform: translateX(-100vw);
}
}
::view-transition-old(root) {
animation: 300ms ease-in both slide-to-left;
}
::view-transition-new(root) {
animation: 300ms ease-out both slide-from-right;
}
If we navigate between pages, the entire old page slides out to the left while the entire new page slides in from the right. But we may want to prevent some elements on the page from participating in the transition, where they persist between pages while everything else moves from the “old” snapshot to the “new” one.
That’s where the view-transition-name
property is key because we can take snapshots of certain elements and put them in their own ::view-transition-group
apart from everything else so that it is treated individually.
nav {
view-transition-name: navigation;
/*
::view-transition
├─ ::view-transition-group(navigation)
│ └─ ::view-transition-image-pair(navigation)
│ ├─ ::view-transition-old(navigation)
│ └─ ::view-transition-new(navigation)
└─ other groups...
*/
}
You can find a live demo of it on GitHub. Just note that browser support is limited to Chromium browsers (i.e., Chrome, Edge, Opera) at the time I’m writing this.
There are many things we can look forward to with cross-document view transitions. For example, If we have several elements with a different view-transition-name
, we could give them a shared view-transition-class
to style their animations in one place — or even customize the view transitions further with JavaScript to check from which URL the page is transitioning and animate accordingly.
Positioning an element relative to another element in CSS seems like one of those no-brainer, straightforward things, but in reality requires mingling with inset properties (top
, bottom
, left
, right
) based on a series of magic numbers to get things just right. For example, getting a little tooltip that pops in at the left of an element when hovered might look something like this in HTML:
<p class="text">
Hover for a surprise
<span class="tooltip">Surprise! I'm a tooltip</span>
</p>
…and in CSS with current approaches:
.text {
position: relative;
}
.tooltip {
position: absolute;
display: none;
/* vertical center */
top: 50%;
transform: translateY(-50%);
/* move to the left */
right: 100%;
margin-right: 15px; */
}
.text:hover .tooltip {
display: block;
}
Having to change the element’s positioning and inset values isn’t the end of the world, but it sure feels like there should be an easier way. Besides, the tooltip in that last example is extremely fragile; if the screen is too small or our element is too far to the left, then the tooltip will hide or overflow beyond the edge of the screen.
CSS Anchor Positioning is yet another new feature that was discussed in the CSSWG meetings and it promises to make this sort of thing much, much easier.
The basic idea is that we establish two elements:
This way, we have a more declarative way to associate one element and position it relative to the anchored element.
To begin we need to create our anchor element using a new anchor-name
property.
Changing our markup a little:
<p>
<span class="anchor">Hover for a surprise</span>
<span class="tooltip">Surprise! I'm a tooltip</span>
</p>
We give it a unique dashed-indent
as its value (just like a custom property):
.anchor {
anchor-name: --tooltip;
}
Then we relate the .tooltip
to the .anchor
using the position-anchor
property with either fixed
or absolute
positioning.
.toolip {
position: fixed;
position-anchor: --tooltip;
}
The .tooltip
is currently positioned on top of the .anchor
, but we ought to move it somewhere else to prevent that. The easiest way to move the .tooltip
is using a new inset-area
property. Let’s imagine that the .anchor
is placed in the middle of a 3×3 grid and we can position the tooltip inside the grid by assigning it a row and column.
The inset-area
property takes two values for the .tooltip
‘s in a specific row and column on the grid. It counts with physical values, like left
, right
, top
and bottom
, as well logical values depending on the user’s writing mode, like start
and end
, in addition to a center
shared value. It also accepts values referencing x- and y-coordinates, like x-start
and y-end
. All these value types are ways of representing a space on the 3×3 grid.
For example, if we want the .tooltip
to be positioned relative to the top-right edge of the anchor, we can set the inset-area
property like this:
.toolip {
/* physical values */
inset-area: top right;
/* logical values */
inset-area: start end;
/* mix-n-match values! */
inset-area: top end;
}
Lastly, if we want our tooltip to span across two regions of the grid, we can use a span-
prefix. For example, span-top
will place the .tooltip
in the grid’s top and center regions. If instead we want to span across an entire direction, we can use the span-all
value.
One of the problems with our anchor-less example is that the tooltip can overflow outside the screen. We can solve this using another new property, this time called position-try-options
, in combination with a new inset-area()
function.
(Yes, there is inset-area
the property and inset-area()
the function. That’s one we’ll have to commit to memory!)
The position-try-options
property accepts a comma-separated list of fallback positions for the .tooltip
when it overflows outside the screen. We can provide a list of inset-area()
functions, each holding the same values that the inset-area
property would. Now, each time the tooltip goes out off-screen, the next declared position is “tried”, and if that position causes an overflow, the next declared position is tried, and so on.
.toolip {
inset-area: top left;
position-try-options: inset-area(top), inset-area(top right);
}
This is a pretty wild concept that will take some time to grok. CSSWG member Miriam Suzanne sat down to discuss and tinker with anchor positioning with James Stuckey Weber in a video that’s well worth watching.
Geoff Graham took notes on the video if you’re looking for a TL;DW.
There are still many aspects to anchor positioning we aren’t covering here for brevity, notably the new anchor()
function and @try-position
at-rule. The anchor()
function returns the computed position of the edge of an anchor, which provides more control over a tooltip’s inset properties. The @try-position
at-rule is for defining custom positions to set on the position-try-options
property.
My hunch is that using inset-area
will be plenty robust for the vast majority of use cases.
Earlier I said that this article wouldn’t be an exact retelling of the discussions that took place at the CSSWG meetings, but rather a broad representation of new specs coming to CSS that, due to their novelty, were bound to come up in those meetings. There are even some features that we simply hadn’t the time to review in this roundup that are still subject to debate (cough, masonry).
One thing is for sure: specs aren’t made in some vacuum over one or two meetings; it takes the joined effort of tens of amazing authors, developers, and user agents to bring to life what we use every day in our CSS work — not to mention the things we will use in the future.
I also had the opportunity to talk with some amazing developers from the CSSWG, and I found it interesting what their biggest takeaways were from the meetings. You might expect if()
is at the top of their lists since that’s what is buzzing in socials. But CSSWG member Emilio Cobos told me, for example, that the letter-spacing
property is essentially flawed and there isn’t a simple solution for fixing it that’s copasetic with how letter-spacing
is currently defined by CSS and used in browsers. That includes the fact that converting normal properties into shorthand properties can be dangerous to a codebase.
Every tiny detail we might think of as trivial is carefully analyzed for the sake of the web and for the love of it. And, like I mentioned earlier, this stuff is not happening in a closed vacuum. If you’re at all interested in the future of CSS — whether that simply keeping up with it or getting actively involved — then consider any of the following resources.
CSS Stuff I’m Excited After the Last CSSWG Meeting originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.
]]>::view-transition-image-pair
pseudo-element is part of the View Transitions API that lets us select the “before-and-after” snapshots in a view transition.
::view-transition-image-pair(root) {
animation-duration: 1s;
}
For context’s sake, ::view-transition-image-pair
is one pseudo-element in a tree of pseudo-elements the …
::view-transition-image-pair originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.
]]>::view-transition-image-pair
pseudo-element is part of the View Transitions API that lets us select the “before-and-after” snapshots in a view transition.
::view-transition-image-pair(root) {
animation-duration: 1s;
}
For context’s sake, ::view-transition-image-pair
is one pseudo-element in a tree of pseudo-elements the browser establishes when a view transition is registered on the page. The ::view-transition
pseudo-element is the tree’s “root” and ::view-transition-image-pair
is two levels deep, contained in the ::view-transition-group
pseudo-element.
::view-transition
├─ ::view-transition-group(name-1)
│ └─ ::view-transition-image-pair(name-1)
│ ├─ ::view-transition-old(name-1)
│ └─ ::view-transition-new(name-1)
├─ ::view-transition-group(name-2)
│ └─ ::view-transition-image-pair(name-2)
│ ├─ ::view-transition-old(name-2)
│ └─ ::view-transition-new(name-2)
│ /* and so one... */
This makes ::view-transition-image-pair
an ancestor of the root ::view-transition
and a direct child of the ::view-transition-group
. In turn, ::view-transition-image-pair
contains two child pseudo-elements of its own:
These two pseudos represent the “old” state before the view transition begins and the “new” state it transitions to, respectively. So, ::view-transition-image-pair
becomes a handy way to select and style both of those states together.
The specification calls this the pseudo “View Transition Image Pair Isolation” which means that it adds isolation
to a view transition’s old and new states. In other words, ::view-transition-image-pair
is set to isolation: isloate
by default. This establishes a stacking context that allows the view transition’s old and new states to sit on top of everything else to prevent it from being obscured by other elements.
::view-transition-image-pair(<pt-name-selector>) {
/* Styles */
}
The pseudo-element accepts a <pt-name-selector>
in its argument, which is equal to one of the following:
<custom-ident>
: Use this to select a specific transition image pair in the ::view-transition
pseudo tree. For example, if a particular element has a view-transition-name
of gallery
, then you would use ::view-transition-image-pair(gallery)
to select that transition group.root
: This value matches html::view-transition-image-pair(*)
which is a selector set up by the browser to match any view transition image pair that is not assigned to a specific view transition via the CSS view-transition-name
property.*
): Use this to select all view transition image pairs on a page.The specification defines the default styles for browsers to set on ::view-transition-image-pair
like this:
:root::view-transition-image-pair(*) {
position: absolute;
inset: 0;
animation-duration: inherit;
animation-fill-mode: inherit;
}
Notice that the default styles target all view transitions with the Universal Selector (*
). We can override these by selecting a specific view-transition-name
:
::view-transition-pair(gallery) {
animation-duration: 500ms;
}
The CSS ::view-transition-image-pair
pseudo-element is defined in the CSS View Transitions Module Level 1 specification. The specification is labeled a Candidate Recommendation Snapshot, meaning it’s been widely reviewed and intended to become an official W3C Recommendation, but it is still being tested in the wild.
The specification further states:
This document is intended to become a W3C Recommendation; it will remain a Candidate Recommendation at least until 5 December 2023 to gather additional feedback.
That date has passed as of this writing, so keep an eye on the document’s status as it becomes a recommended feature.
::view-transition-image-pair originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.
]]>::view-transition-image-new
pseudo-element is part of the View Transitions API that lets us select the “new” snapshot that is transitioned to in a view transition.
::view-transition-image-new(*) {
animation-duration: 700ms;
}
For context’s sake, ::view-transition-image-new
is one pseudo-element in a …
::view-transition-image-new originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.
]]>::view-transition-image-new
pseudo-element is part of the View Transitions API that lets us select the “new” snapshot that is transitioned to in a view transition.
::view-transition-image-new(*) {
animation-duration: 700ms;
}
For context’s sake, ::view-transition-image-new
is one pseudo-element in a tree of pseudo-elements the browser establishes when a view transition is registered on the page. The ::view-transition
pseudo-element is the tree’s “root” and ::view-transition-image-pair
is two levels deep, contained in the ::view-transition-group
pseudo-element.
And inside the ::view-transition-image-pair
is where we’ll find ::view-transition-image-new
:
::view-transition
├─ ::view-transition-group(name-1)
│ └─ ::view-transition-image-pair(name-1)
│ ├─ ::view-transition-old(name-1)
│ └─ ::view-transition-new(name-1)
├─ ::view-transition-group(name-2)
│ └─ ::view-transition-image-pair(name-2)
│ ├─ ::view-transition-old(name-2)
│ └─ ::view-transition-new(name-2)
│ /* and so one... */
This makes ::view-transition-image-new
a direct child of the ::view-transition-image-pair
. The image pair also contains the view traditions “old” snapshot, ::view-transition-image-old
.
These two pseudos represent the “old” state before the view transition begins and the “new” state it transitions to, respectively. So, ::view-transition-image-pair
becomes a handy way to select and style both of those states together.
The specification calls this the pseudo “View Transition New State Image” which is pretty on the nose considering that it is indeed an image of the view transition that the browser captures before the transition runs and then transitions to the new state.
::view-transition-image-new(<pt-name-selector>) {
/* Styles */
}
The pseudo-element accepts a <pt-name-selector>
in its argument, which is equal to one of the following:
<custom-ident>
: Use this to select a specific transition “new” image in the ::view-transition
pseudo tree. For example, if a particular element has a view-transition-name
of gallery
, then you would use ::view-transition-image-new(gallery)
to select that transition group.root
: This value matches :root::view-transition-new(*)
which is a selector set up by the browser to match any view transition “new” image that is not assigned to a specific view transition via the CSS view-transition-name
property.*
): Use this to select all “new” states of a view transition on a page.The specification defines the default styles for browsers to set on ::view-transition-image-new
like this:
:root::view-transition-old(*),
:root::view-transition-new(*) {
position: absolute;
inset-block-start: 0;
inline-size: 100%;
block-size: auto;
animation-duration: inherit;
animation-fill-mode: inherit;
animation-delay: inherit;
}
Notice that the default styles target all view transitions with the Universal Selector (*
). We can override these by selecting a specific view-transition-name
:
::view-transition-new(gallery) {
animation-duration: 500ms;
}
The CSS ::view-transition-image-new
pseudo-element is defined in the CSS View Transitions Module Level 1 specification. The specification is labeled a Candidate Recommendation Snapshot, meaning it’s been widely reviewed and intended to become an official W3C Recommendation, but it is still being tested in the wild.
The specification further states:
This document is intended to become a W3C Recommendation; it will remain a Candidate Recommendation at least until 5 December 2023 to gather additional feedback.
That date has passed as of this writing, so keep an eye on the document’s status as it becomes a recommended feature.
::view-transition-image-new originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.
]]>::view-transition-image-old
pseudo-element is part of the View Transitions API that lets us select the “before” snapshot in a view transition.
::view-transition-image-old(*) {
animation-duration: 700ms;
}
For context’s sake, ::view-transition-image-old
is one pseudo-element in a tree of pseudo-elements the …
::view-transition-image-old originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.
]]>::view-transition-image-old
pseudo-element is part of the View Transitions API that lets us select the “before” snapshot in a view transition.
::view-transition-image-old(*) {
animation-duration: 700ms;
}
For context’s sake, ::view-transition-image-old
is one pseudo-element in a tree of pseudo-elements the browser establishes when a view transition is registered on the page. The ::view-transition
pseudo-element is the tree’s “root” and ::view-transition-image-pair
is two levels deep, contained in the ::view-transition-group
pseudo-element.
And inside the ::view-transition-image-pair
is where we’ll find ::view-transition-image-old
:
::view-transition
├─ ::view-transition-group(name-1)
│ └─ ::view-transition-image-pair(name-1)
│ ├─ ::view-transition-old(name-1)
│ └─ ::view-transition-new(name-1)
├─ ::view-transition-group(name-2)
│ └─ ::view-transition-image-pair(name-2)
│ ├─ ::view-transition-old(name-2)
│ └─ ::view-transition-new(name-2)
│ /* and so one... */
This makes ::view-transition-image-old
a direct child of the ::view-transition-image-pair
. The image pair also contains the view traditions “new” snapshot, ::view-transition-image-new
.
These two pseudos represent the “old” state before the view transition begins and the “new” state it transitions to, respectively. So, ::view-transition-image-pair
becomes a handy way to select and style both of those states together.
The specification calls this the pseudo “View Transition Old State Image” which is pretty on the nose considering that it is indeed an image of the view transition that the browser captures before the transition runs and then transitions to the new state.
::view-transition-image-old(<pt-name-selector>) {
/* Styles */
}
The pseudo-element accepts a <pt-name-selector>
in its argument, which is equal to one of the following:
<custom-ident>
: Use this to select a specific transition “old” image in the ::view-transition
pseudo tree. For example, if a particular element has a view-transition-name
of gallery
, then you would use ::view-transition-image-old(gallery)
to select that transition group.root
: This value matches :root::view-transition-old(*)
which is a selector set up by the browser to match any view transition “old” image that is not assigned to a specific view transition via the CSS view-transition-name
property.*
): Use this to select all “old” states of a view transition on a page.The specification defines the default styles for browsers to set on ::view-transition-image-old
like this:
:root::view-transition-old(*),
:root::view-transition-new(*) {
position: absolute;
inset-block-start: 0;
inline-size: 100%;
block-size: auto;
animation-duration: inherit;
animation-fill-mode: inherit;
animation-delay: inherit;
}
Notice that the default styles target all view transitions with the Universal Selector (*
). We can override these by selecting a specific view-transition-name
:
::view-transition-old(gallery) {
animation-duration: 500ms;
}
The CSS ::view-transition-image-old
pseudo-element is defined in the CSS View Transitions Module Level 1 specification. The specification is labeled a Candidate Recommendation Snapshot, meaning it’s been widely reviewed and intended to become an official W3C Recommendation, but it is still being tested in the wild.
The specification further states:
This document is intended to become a W3C Recommendation; it will remain a Candidate Recommendation at least until 5 December 2023 to gather additional feedback.
That date has passed as of this writing, so keep an eye on the document’s status as it becomes a recommended feature.
::view-transition-image-old originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.
]]>::view-transition-group
pseudo-element selects one or more containers that group the pieces of an individual view transition.
::view-transition-group(transition-name) {
animation-timing-function: ease-in-out;
}
See that transition-name
identifier in the pseudo-element’s argument? That’s how we can select a particular view transition. View …
::view-transition-group originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.
]]>::view-transition-group
pseudo-element selects one or more containers that group the pieces of an individual view transition.
::view-transition-group(transition-name) {
animation-timing-function: ease-in-out;
}
See that transition-name
identifier in the pseudo-element’s argument? That’s how we can select a particular view transition. View transitions are grouped together in a single ::view-transition
pseudo-element, where each transition is contained in a ::view-transition-group
:
::view-transition
├─ ::view-transition-group(name-1)
├─ ::view-transition-group(name-2)
│ /* and so one... */
And inside each ::view-transition-group
is another pseudo-element that holds the “before-and-after” snapshots that the view transitions between.
::view-transition
├─ ::view-transition-group(name-1)
│ └─ ::view-transition-image-pair(name-1)
│ ├─ ::view-transition-old(name-1)
│ └─ ::view-transition-new(name-1)
├─ ::view-transition-group(name-2)
│ └─ ::view-transition-image-pair(name-2)
│ ├─ ::view-transition-old(name-2)
│ └─ ::view-transition-new(name-2)
│ /* and so one... */
That essentially makes ::view-transition-group
the “root” element of an individual view transition. It’s a wrapper for selecting the ::view-transition-image-pair
, typically for positioning that particular view transition or defining its animation properties. In fact, the specification calls it the “View Transition Named Subtree Root.”
::view-transition-group(<pt-name-selector>) {
/* Styles */
}
The pseudo-element accepts a <pt-name-selector>
in its argument, which is equal to one of the following:
<custom-ident>
: Use this to select a specific transition group in the ::view-transition
pseudo tree. For example, if a particular element has a view-transition-name
of gallery
, then you would use ::view-transition-group(gallery)
to select that transition group.root
: This value matches html::view-transition-group(*)
which is a selector set up by the browser (see “Default styling” below) to match any view transition that is not assigned to a specific view transition group via the CSS view-transition-name
property.*
): Use this to select all view transition groups on a page.Like many semantic HTML elements and pseudo-elements, browsers apply a set of default styles to ::view-transition-group
.
html::view-transition-group(*) {
position: absolute;
top: 0;
left: 0;
animation-duration: 0.25s;
animation-fill-mode: both;
}
So, if we want to position and configure the transition’s animation behavior, these are the styles we have to override in our own stylesheet.
It’s always a good idea to consider users with motion sensitivities any time we’re working with animations and transitions. We can use ::view-transition-group
to reduce the amount of motion using the prefers-reduced-motion
media query.
According to the default styles we learned about above, the browser applies an animation-duration
of 0.25s
. We can override that by selecting all of the view transition groups on the page by passing the universal selector in the pseudo-element’s argument and either removing or reducing that duration to accommodate motion sensitivities.
@media (prefers-reduced-motion: reduce) {
::view-transition-group(*): {
animation-duration: 0;
}
}
The ::view-transition-group
pseudo-element is defined in the View Transitions Module Level 1 specification. It’s labeled a Candidate Recommendation Snapshot, meaning it’s been widely reviewed and intended to become an official W3C Recommendation, but it is still being tested in the wild.
The specification further states:
This document is intended to become a W3C Recommendation; it will remain a Candidate Recommendation at least until 5 December 2023 to gather additional feedback.
That date has passed as of this writing, so keep an eye on the document’s status as it becomes a recommended feature.
If you need to support older browsers that do not support ::view-transition
, try using @supports
to check whether or not the browser recognizes the view-transition-name
property:
/* Apply these styles only if View Transitions are *NOT* supported */
@supports not (view-transition-name: none) {
/* Fallback Styles */
}
::view-transition-group originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.
]]>::view-transition
pseudo-element is the top-level — or “root” — containing all animated transitions defined as CSS view transitions on the page.
::view-transition {
position: fixed;
inset: 0;
}
Syntax
::view-transition {
/* Styles */
}
It’s worth noting …
::view-transition originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.
]]>::view-transition
pseudo-element is the top-level — or “root” — containing all animated transitions defined as CSS view transitions on the page.
::view-transition {
position: fixed;
inset: 0;
}
::view-transition {
/* Styles */
}
It’s worth noting that ::view-transition
holds all view transitions on the page (we’ll get to that in a bit), so you’re likely to see it used most often used unchained to a selector like the example above rather than, say, this:
/* 👎 */
blockquote::view-transition {
/* Styles */
}
Left as-is, ::view-transition
is scoped to the <html>
element:
html::view-transition {
/* Styles */
}
Like many semantic HTML elements and pseudo-elements, browsers apply a set of default styles to ::view-transition
.
::view-transition {
position: fixed;
inset: 0;
}
So, if we want to position ::view-transition
ourselves, these are the styles we have to override in our own stylesheet.
In other words, it’s effectively a wrapper that serves as the parent pseudo-element for others below it. The spec calls this a “tree-abiding pseudo-element” which means it contains other pseudos that can be used to style view transitions directly in CSS.
In this case, ::view-transition
contains all of the view transitions on the page, organized in groups.
::view-transition
├─ ::view-transition-group(name)
│ └─ ::view-transition-image-pair(name)
│ ├─ ::view-transition-old(name)
│ └─ ::view-transition-new(name)
├─ ::view-transition-group(name)
│ └─ ::view-transition-image-pair(name)
│ ├─ ::view-transition-old(name)
│ └─ ::view-transition-new(name)
│ /* and so one... */
This way, we can define styles that apply to all view transitions together with ::view-transition
and individually with ::view-transition-group
.
It’s a good idea to be familiar with the steps that a view transition takes for context on where this ::view-transition
tree fits in with everything.
document.startViewTransition()
in JavaScript.startViewTransition
is triggered to begin transitioning between views.::view-transition
tree is established, defining styles used in the transition.ViewTransition.finished
promise is made in JavaScript.It’s right there in Step 4 that we’re talking about.
You know how we can use the CSS z-index
property to layer elements one on top of another, like in a three-dimensional space?
We call this a “stacking context” and it happens when we declare a position
on an element. In this case, both .child
elements are in a stacking context where we’re using z-index
to determine which element is above the other in the stack. CSS uses this information to determine in what order it applies styles:
z-index: -1
position
value of static
.0
, typically positioned elements with a z-index
value of auto
.z-index
value of 1
or higher.That third layer is called the “top layer” since it’s right smack dab in between layers positive and negative layers. This is where the <html>
and <body>
sit.
Well, it turns out that ::view-transition
introduces a new stacking layer of its own, that is applied after everything else, i.e., it comes after the layer with the highest number:
z-index: -1
position
value of static
.0
, typically positioned elements with a z-index
value of auto
.z-index
value of 1
or higher.Here’s exactly how the specification describes it:
The
CSS View Transitions Module Level 1, 4.2. View Transition Painting Order::view-transition
pseudo-element generates a new stacking context, called the view transition layer, which paints after all other content of the document (including any content rendered in the top layer), after any filters and effects that are applied to such content. (It is not subject to such filters or effects, except insofar as they affect the rendered contents of the ::view-transition-old() and ::view-transition-new() pseudo-elements.)
What’s the big deal, you ask? This means that a view transition’s styles are painted after everything else, ensuring that they are never buried beneath other layers. This way, we never have to juggle between view transition styles and the styles of the elements on a page with z-index
to make sure they’re visible. But this only takes effect if (1) there are view transitions on the page and (2) when the view transition is running. Once the transition ends, its stacking context is gone.
The CSS ::view-transition
pseudo-element is defined in the View Transitions Module Level 1 specification. It’s labeled a Candidate Recommendation Snapshot which means it’s been widely reviewed and is intended to become an official W3C Recommendation, but is still being tested in the wild.
The specification further states:
This document is intended to become a W3C Recommendation; it will remain a Candidate Recommendation at least until 5 December 2023 to gather additional feedback.
That date has passed as of this writing, so keep an eye on the document’s status as it makes its way toward becoming a recommended feature.
If you need to support older browsers that do not support ::view-transition
, try using @supports
to check whether or not the browser recognizes the view-transition-name
property:
/* Apply these styles only if View Transitions are *NOT* supported */
@supports not (view-transition-name: none) {
/* Fallback Styles */
}
::view-transition originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.
]]>view-timeline-name
property allows us to identify and reference an element we want to animate based on its scroll position when it enters the visible area (i.e., scrollport) of a scroll container (also called the “source”).
.element {
view-timeline-name:
…
view-timeline-name originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.
]]>view-timeline-name
property allows us to identify and reference an element we want to animate based on its scroll position when it enters the visible area (i.e., scrollport) of a scroll container (also called the “source”).
.element {
view-timeline-name: --image-zoom;
}
It’s a declarative way to trigger a scroll animation; otherwise, the element (also called the “subject”) will begin its scroll-based animation when it enters the scrollport of its nearest ancestor that has one.
The view-timeline-name
property can also be declared using the view-timeline
property, which is a shorthand that combines view-timeline-name
and view-timeline-axis
in a single declaration.
.element {
view-timeline: --image-zoom y;
/* Equivalent to: */
view-timeline-name: --image-zoom;
view-timeline-axis: y;
}
view-timeline-name: [ none | <dashed-ident> ]#
none
none
or a list of CSS identifiers/* Keyword value: none */
view-timeline-name: none;
/* <dashed-ident> examples */
view-timeline-name: --my-timeline;
view-timeline-name: --myTimeline;
view-timeline-name: --my_timeline;
none
: The timeline does not have a name.<dashed-ident>
: A custom name given to a view progress timeline, like a <custom-ident>
, that declaratively identifies it and distinguishes it from other timelines. The value is called a “dashed” ident because it must be prefixed with two dashes (--
) exactly like we do for CSS custom properties, e.g. --image-zoom
.The difference may seem small, but a scroll progress timeline is an animation we want to run on an element visible in the current scrollport, i.e. its scroll position whether scrolling vertically (the y-axis) or horizontally (the x-axis). When the element reaches a scroll position, the animation triggers and completes at the end of the scroll container. In other words, it’s an animation timeline that progresses from 0%
to 100%
as the user scrolls, whereas a typical animation timeline often happens when the page has been rendered.
That’s different from a view progress timeline which is a segment or part of a scroll progress timeline. In other words, a view progress timeline is sort of like the starting ignition of a scroll progress timeline, triggering the scroll progress timeline once the element (i.e., the “subject”) is currently in view.
So, what we’re talking about is a scroll progress timeline that’s scoped to the element’s visibility in the current view. Once the element’s starting edge intersects with its nearest ancestor’s scrollport — which is a fancy way of saying “the visible part of a scroll container” — the animation is handed off to the scroll progress timeline and progresses by the user’s scrolling along whichever axis — x or y — is set.
The <body>
element can be considered a scroll container as long as it overflows what is currently in view. That means we can trigger or start an animation when the element we want to animate enters the visible area of the <body>
after which point, the animation progresses based on the element’s scroll position.
It’s worth noting that there are some specific words that the specification uses to describe the various pieces of a scroll progress timeline.
It’s cool that we have this sort of context, so we’ll continue using these terms along the way.
We don’t have to give a name to a view progress timeline. If we don’t it’s called an anonymous timeline and the subject we want to animate enters the progress timeline when it intersects with its nearest source. In other words, the animation is triggered when the element enters the visible part of the first scroll container it encounters.
Conversely, we can explicitly give the view progress timeline an identifying name with the view-timeline-name
property to establish a declarative timeline that can be called and referenced by name. It’s what we reach for when we want the subject’s animation to kick off when it enters a super-specific source’s visible area.
view()
Even if we decide not to name a view progress timeline explicitly with scroll-timeline-name
, it’s still possible to configure the view timeline behavior with the view()
function. The function doesn’t take a scroll-timeline-name
parameter, but it does instruct the browser what a subject should do when it intersects with its source.
.subject {
animation-timeline: view(y 10dvh);
}
So, even without an explicit view-transition-name
set on the subject element, we can still direct which view-timeline-axis
the animation runs on and fudging exactly when it starts it starts with a view-timeline-inset
parameter.
The view-timeline-name
property is defined in the Scroll-driven Animations Module Level 1 specification, which is currently in Editor’s Draft status at the time of this writing. That means the work is under discussion and could change between now and when the proposal becomes a formal Candidate Recommendation.
IE | Chrome | Edge | Firefox | Safari | Opera |
---|---|---|---|---|---|
No | 115 | 115 | 111 | No | 101 |
iOS Safari | Android Chrome | Android Firefox | Android Browser | Opera Mobile |
---|---|---|---|---|
No | 125 | No | 125 | 80 |
Source: caniuse (retrieved on May 30, 2024)
view-timeline-name originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.
]]>view-transition-name
property uniquely identifies an element that can be used in a view transition.
card {
view-transition-name: card-transition;
}
We give the element a name (i.e., <custom-ident
) that we reference or set the property to none
to …
view-transition-name originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.
]]>view-transition-name
property uniquely identifies an element that can be used in a view transition.
card {
view-transition-name: card-transition;
}
We give the element a name (i.e., <custom-ident>
) that we reference or set the property to none
to exclude the element from view transitions.
The specification is not a standard just yet but describes view-transition-name
as a way of “tagging individually transitioning subtrees” which is a super fancy way of saying we want a certain element to be captured in a view transition. When we give an element a view-transition-name
, we’re effectively giving it access to a set of pseudo-elements for creating a view transition.
view-transition-name: none | <custom-ident>;
none
/* <custom-ident> */
view-transition-name: banner;
view-transition-name: banner-transition;
view-transition-name: bannerTransition;
/* Keyword */
view-transition-name: none;
<custom-ident>
: A unique identifier used to define an element that can participate in a view transition, whether it is the “old” element or the “new” one that is transitioned to. We can’t use the words none
or auto
for the value, as they are reserved global keyword values.none
: Removes the element from taking part in a view transition.It’s worth knowing because that’s exactly how the specification describes an element with a view-transition-name
declared on it. The basic idea of view transitions is that we “capture” an existing state as an image and transition between that snapshot and a new element.
When we “capture” an element, we’re sort of tracking it during the lifetime of the view transition, during which the element takes multiple forms. Here’s the process that a captured element follows, as provided by the spec, which goes further by calling a captured element a “struct” or a set of items:
unrestricted double
, initially zero.transform
: a <transform-function>
, initially the identity transform function.writing-mode
: Null or a writing-mode
, initially null.direction
: Null or a direction
, initially null.text-orientation
: Null or a text-orientation
, initially null.mix-blend-mode
: Null or a mix-blend-mode
, initially null.backdrop-filter
: Null or a backdrop-filter
, initially null.color-scheme
: Null or a color-scheme
, initially null.Those are all the pieces of a captured element used in a view transition. We capture a set of information about an element’s current (or “old”) state and transition it into a new element.
We can name a view transition anything we want (none
and auto
being the only exceptions) but it has to be unique in the sense that it is only applied to one rendered element on the current page.
The key word there is “rendered” because we can technically repurpose the same view-transition-name
on multiple elements. For example, you might want the same transition on a set of images but wouldn’t want to go through the work of naming each and every one. We can instead declare view-transition-name
on a certain class:
.image-transition {
view-transition-name: image-zoom;
}
Then we can set up JavaScript that that sets a variable for each image in the set, adds the .image-transition
class to the images, then triggers the transition with some action — like a click handler — on the image. From there, the script waits for the transition to wrap up, then removes the .image-transition
class so it can be applied to another clicked image.
// Create a variable for img elements in a .gallery
const image = document.querySelectorAll(".gallery img");
// When an image is clicked...
image.onclick = () => {
// If view transitions are *not* supported
if (!document.startViewTransition) {
// Run a fallback function
doSomething(image);
// If view transitions are supported
} else {
// Add the .image-transition class to the clicked image
image.classList.add("image-transition");
// Start the transition and save its instance in a variable
const transition = document.startViewTransition(() => doSomething(image));
// Wait for the transition to finish.
await transition.finished;
// Remove the class when finished
image.classList.remove("image-transition");
}
};
Another approach is to set view-transition-name
to a <custom-ident>
on the clicked image, wait for the transition to run, then swap out the <custom-ident>
with the none
value instead.
// Create a variable for img elements in a .gallery
const image = document.querySelectorAll(".gallery img");
// When an image is clicked...
image.onclick = () => {
// First, check if view transitions are supported
if (!document.startViewTransition) {
// Fallback if View Transitions API is not supported.
doSomething(image);
} else {
// Set `view-transition-name` with a <custom-ident>
image.style.viewTransitionName = "image-zoom";
// Start the transition and save its instance in a variable
const transition = document.startViewTransition(() => doSomething(image));
// Wait for the transition to finish.
await transition.finished;
// Set the transition name to `none` when finished
image.style.viewTransitionName = "none";
}
};
The view-transition-name
property is just one of several view-*
properties that are used together to register and manage view transitions that are part of the View Transitions Module Level 1 specification, which is currently a Candidate Recommendation Snapshot meaning it can be used where available, but is in the process of collecting feedback before formal recommendation. In other words, view-transition-name
and other related properties are experimental and not part of the standards just yet.
It’s only Chrome and other Chromium-powered browsers at the moment. Keep an eye on the Caniuse pages for the most current support information for the view-transition-name
property and support for the none
value.
As far as browser support goes, we can use the view-transition-name
property to check whether or not the user’s browser supports the View Transitions API and provide a fallback for browsers that lack support. We could use a <custom-ident>
value to check support, but the none
value is probably a better bet since it is a consistent keyword and we definitely want that value supported if we’re working with view trasnsitions anyway.
/* If the View Transitions API is supported */
@supports (view-transition-name: none) {
}
/* If the View Transitions API is *not* supported */
@supports not (view-transition-name: none) {
/* Use a simple CSS animation if possible */
}
Similarly, we can check for support via JavaScript as well:
// If the View Transitions API is *not* supported.
if (!document.startViewTransition) {
doSomething());
// Otherwise, start the view transition
} else {
document.startViewTransition(() => doSomething());
}
view-transition-name originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.
]]>@view-transition {
navigation: auto;
}
That goes right in your stylesheet and it’s the most basic of basic view transitions. The effect is a cross-fade between two pages.
The Chrome team had initially released a meta tag we could …
Basic Multi-Page View Transition originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.
]]>@view-transition {
navigation: auto;
}
That goes right in your stylesheet and it’s the most basic of basic view transitions. The effect is a cross-fade between two pages.
The Chrome team had initially released a meta tag we could plop into the <head>
to opt into view transitions:
<meta name="view-transition" content="same-origin" />
That was a temporary way to access view transitions, but then the Chrome team shipped an update in May 2024 that enables multi-page view transitions by default and only requires us to opt into them with the CSS snippet above.
Basic Multi-Page View Transition originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.
]]>