Closed jakearchibald closed 11 months ago
The current proposed API is using a meta tag for this, similar to other Document global settings like viewport meta tag.
But doing this in CSS would be better, for the reasons Jake mentioned like conditionally doing this based on prefers-reduced-motion.
Would it be useful for this to be more fine-grained? I.e. allow transitions from certain pages/domains but not others?
Yes, that absolutely should be a goal. My preference would be to allow pages to declare themselves to be of a particular type, which is just an ident. Then you can express that you're happy to perform a transition from "article" to "profile" or whatever.
Although, if we go with URLs, https://wicg.github.io/urlpattern/ will be handy.
We chatted with @tabatkins and he's happy with the general shape of the syntax here.
We would need to define what the CSSOM looks like.
It might be worth making @cross-document-transition allow;
a shorthand for something like:
@cross-document-transition {
state: allow;
}
…which would allow us to add other descriptors in future.
In the new page, the opt-in would need to be present before first render. This works out, since CSS in the head is render-blocking.
Perhaps it can be a pseudo-state of the ::view-transition
rather than a new top-level rule?
e.g. ::view-transition:cross-document
, and then you can disable the transition in CSS yourself
::root:has(::view-transition:cross-document) {
view-transition-name: "none" !important;
}
Not a fan of this :has()
approach. Feels rather hacky.
Iterating on the at-rule – which makes more sense to me – here’s two variants on that:
If more configuration things like this are planned over time (e.g. global custom properties), maybe instead of a new at-rule @cross-document-transition
, it could be a descriptor (property?) in @document
? I.e. @document { cross-document-transition: allow; }
Maybe, instead of a new at-rule, it would be treated as a media condition? I.e. @media (prefers-reduced-motion and cross-document-transition: allow) { … }
Oh, wait … that second one doesn’t make sense on its own, as that one doesn’t set anything – it only detects stuff 🤦♂️
The :has
thing is just part of an example, It can also be
@media (prefers-reduced-motion) {
::view-transition:cross-document {
display: none;
}
}
This allows more fine-grained control over how things behave if this is a cross-document transition rather than totally disabling the transition.
For totally disabling the transitions based on media queries, I'd suggest adding a media
attribute to the meta tag, like we have for preloads, or as a parameter to the content attribute, e.g. <meta name="view-transition" content="same-origin;media=(prefers-reduce-motion:no-preference)">
or splitting the opt-in to several meta-tags:
<meta name="view-transition:referrer" content="same-origin">
<meta name="view-transition:media" content="(prefers-reduce-motion:no-preference)">
I'm offering these alternatives because I don't see how using CSS to declare something that's completely global plays to what CSS is good at, and it's also not very consistent with other things CSS does.
@font-face
and @property
are also global declarations in CSS.
@font-face
and@property
are also global declarations in CSS.
They give meaning to all kind of names, but they don't affect the style directly.
Thinking about this again.
I see the problem the opt-in tries to solve as mismatch between same-origin transitions. It's not a security problem, but rather an issue of unintended visual effects by having e.g. an SPA transition in both documents lead to a spurious MPA transition between the two documents.
IMO any kind of boolean opt-in doesn't solve this, either in HTML or CSS. You could opt-in to some MPA transitions within your origin, and still reach a case where some pages are uncoordinated with you and cause an unintended MPA transition.
Instead, I propose something a bit more flexible and : the :root
of both documents has to share some sort of name.
It can be the view-transition-name
(perhaps if it's not "root", but that might be a bit too implicit), or a new property like a page-transition-name
. Only if the names match, the view transition would be enabled.
The purpose in doing something like that is to ensure a match in a flexible way, that's still ergonomic and consistent with how SPA navigations declare matching.
Instead, I propose something a bit more flexible and : the :root of both documents has to share some sort of name. It can be the view-transition-name (perhaps if it's not "root", but that might be a bit too implicit), or a new property like a page-transition-name. Only if the names match, the view transition would be enabled.
The problem with this is that UA CSS implicitly adds a view-transition-name
to the root element here. So we can't rely on authors setting it as an opt-in. Also, we can't ask for the root of both docs sharing the same name, some transitions require the root of one document to match with a DOM element on the second document, example.
If we're introducing something new (like page-transition-name
), then we should avoid overloading it with the same purpose that view-transition-name
is for. We also want the opt-in syntax to be extendible to declaratively opt-in to a subset of navigations (same-origin, same-site, URL regex, same-document) which doesn't fit well with reusing what view-transition-name
is for.
Instead, I propose something a bit more flexible and : the :root of both documents has to share some sort of name.
It can be the view-transition-name (perhaps if it's not "root", but that might be a bit too implicit), or a new property like a page-transition-name. Only if the names match, the view transition would be enabled.
The problem with this is that UA CSS implicitly adds a
view-transition-name
to the root element here. So we can't rely on authors setting it as an opt-in. Also, we can't ask for the root of both docs sharing the same name, some transitions require the root of one document to match with a DOM element on the second document, example.If we're introducing something new (like
page-transition-name
), then we should avoid overloading it with the same purpose thatview-transition-name
is for. We also want the opt-in syntax to be extendible to declaratively opt-in to a subset of navigations (same-origin, same-site, URL regex, same-document) which doesn't fit well with reusing whatview-transition-name
is for.
Agreed, but we can flesh this out. The main concept is that this opt in should be by name matching rather than Boolean.
Regarding cross-origin, I think it's a much bigger discussion. This might end up being something with security implications, in which case we would want to have a meta thing, or add a new css property. I don't think that should stop us from having a flexible opt-in for same-origin transitions.
Regarding cross-origin, I think it's a much bigger discussion.
I don't think we'll be able to ship this for all cross-origin...ever. I specifically meant the same-site case, the security/privacy implications there are not as strict as different domains?
The main concept is that this opt in should be by name matching rather than Boolean.
The fundamental issue with either, a boolean or name matching, is that the syntax is not extendible to even same-origin cases like a subset of pages. For example:
/ * Opt-in for all same-origin navigations */
@view-transition same-origin;
/* Opt-in for same-origin navigations only to URLs which match this pattern */
@view-transition urlPattern(...);
I don't see how name matching or a boolean can be extended to cover this use-case. We've talked about an option like a media query which matches based on old/new URL that potentially could :
@media (old-url: urlPattern(...)) {
:root { view-transition-name: foo }
}
But that's harder to do since now we have to define the exact time in the old/new Document's lifecycle when this media query activates. I think it's the same problem as defining events to be dispatched on the 2 Documents that you've thought out.
Regarding cross-origin, I think it's a much bigger discussion.
I don't think we'll be able to ship this for all cross-origin...ever.
I agree :)
I specifically meant the same-site case, the security/privacy implications there are not as strict as different domains?
Yup, but also in the same-site cross-origin case there are some security implications that we don't have at all in same-origin.
The main concept is that this opt in should be by name matching rather than Boolean.
The fundamental issue with either, a boolean or name matching, is that the syntax is not extendible to even same-origin cases like a subset of pages. For example:
/ * Opt-in for all same-origin navigations */ @view-transition same-origin; /* Opt-in for same-origin navigations only to URLs which match this pattern */ @view-transition urlPattern(...);
The way I think about it is that there are two issues, and they are totally separate:
For (1), I think having something flexible like matching a root name/namespace attribute in CSS for the transition, without a meta tag or any boolean opt-in, would work really well and would be consistent with the spirit of how SPA transitions work.
For (2), to solve this in CSS you'd have to coerce CSS to do several things that it's not used to doing... for example, you might have @view-transition same-origin
in your inline css, and then include some cross-origin stylesheet that would override it with @view-transition same-site
or @view-transition urlpattern(...)
, which would require fiddling with cascading rules. I think we would have to solve (2) on the HTML level, or even CSP, and my main concern there would be that it would become a side-channel for cross-origin pages to pass privacy-related information to each other, which an opt-in wouldn't solve.
@noamr
@font-face
and@property
are also global declarations in CSS.They give meaning to all kind of names, but they don't affect the style directly.
The proposed opt-in doesn't affect style either. It opts into a view transition on cross-document navigation (similar to how calling startViewTransition
is the opt-in for SPA).
an issue of unintended visual effects by having e.g. an SPA transition in both documents lead to a spurious MPA transition between the two documents.
Can you go into more detail about that? See https://github.com/w3c/csswg-drafts/issues/8677 - I don't think it'll be unusual for the same animation declarations to be used in both SPA and MPA transitions.
There's a doc internally that compares SPA transition features with MPA, where a lot of this is discussed (although it should be across various issues now), @khushalsagar can give you access.
IMO any kind of boolean opt-in doesn't solve this
This proposal allows for more than a simple boolean https://github.com/w3c/csswg-drafts/issues/8048#issuecomment-1495777539. Also, the conditionals can be complex thanks to media queries (see the other issues on url matching).
Instead, I propose something a bit more flexible and : the
:root
of both documents has to share some sort of name. It can be theview-transition-name
(perhaps if it's not "root", but that might be a bit too implicit), or a new property like apage-transition-name
. Only if the names match, the view transition would be enabled.
Two problems with this: You have to do special hard-to-reason-about things to use the same transitions for both SPA and MPA, and it creates a CSS property that only works on one element (or a property that works very differently on one particular element). That's the whole reason a global switch was proposed rather than something per-element.
- making sure that cross-document view transitions are intentional and the internal transition names match what the author has meant
I think a key to this is ensuring that SPA and MPA transitions across the same two pages are as similar as possible. The difference being that one opts in with JS (by calling startViewTransition
), and the other opts-in via some other simple means.
to solve this in CSS you'd have to coerce CSS to do several things that it's not used to doing... for example, you might have
@view-transition same-origin
in your inline css, and then include some cross-origin stylesheet that would override it with@view-transition same-site
or@view-transition urlpattern(...)
, which would require fiddling with cascading rules.
This is already an issue with @keyframes, @font-face, @property etc etc, so we can follow the same rules.
I think we would have to solve (2) on the HTML level, or even CSP, and my main concern there would be that it would become a side-channel for cross-origin pages to pass privacy-related information to each other, which an opt-in wouldn't solve.
Can you provide details of the attack you're trying to prevent? Third party CSS isn't safe.
@noamr
@font-face
and@property
are also global declarations in CSS.They give meaning to all kind of names, but they don't affect the style directly.
The proposed opt-in doesn't affect style either. It opts into a view transition on navigation (like calling
startViewTransition
).an issue of unintended visual effects by having e.g. an SPA transition in both documents lead to a spurious MPA transition between the two documents.
Can you go into more detail about that? See #8677 - I don't think it'll be unusual for the same animation declarations to be used in both SPA and MPA transitions.
Let's say example.com/bank
and example.com/insurance
both work on their SPA transitions independently, and then find that going from one to the other created an unintended MPA transition with random weird associations between elements that happen to have the same transition names. Let's say we go with a "boolean with future extensions" option. That would solve the initial problem if they're both SPAs.
But what if they're MPAs? example.com/bank/transaction
has a transition with example.com/bank/summary
, and example.com/insurance/view
has a transition with example.com/insurance/home
. Suddenly the transitions from bank<->insurance would work or none would work. A boolean is an all-or-nothing for same-origin MPA transitions, which is sometimes what you want and sometimes it isn't.
We can solve this either by a URL pattern, or by a namespace, which is the same thing in a way, but there's something more cleaner with names that's consistent with the existing view-transition-name.
There's a doc internally that compares SPA transition features with MPA, where a lot of this is discussed (although it should be across various issues now), @khushalsagar can give you access.
IMO any kind of boolean opt-in doesn't solve this
This proposal allows for more than a simple boolean #8048 (comment). Also, the conditionals can be complex thanks to media queries (see the other issues on url matching).
It's a simple boolean with "we might add more things to it in the future". I'm saying, let's use name-matching now instead of "boolean plus future unknown things".
Instead, I propose something a bit more flexible and : the
:root
of both documents has to share some sort of name. It can be theview-transition-name
(perhaps if it's not "root", but that might be a bit too implicit), or a new property like apage-transition-name
. Only if the names match, the view transition would be enabled.Two problems with this: You have to do special hard-to-reason-about things to use the same transitions for both SPA and MPA
I don't understand this problem. You have to give your whole transition a global name/namespace, and make sure both documents know about it. This is similar to giving elements that transition to each other matching names. Where is the "hard to reason" part?
it creates a CSS property that only works on one element (or a property that works very differently on one particular element). That's the whole reason a global switch was proposed rather than something per-element.
It creates one element in the same way ::view-transition
is a pseudo-element of only one element. If in the future we enable several view transitions to work simultaneously, this can be more flexible.
- making sure that cross-document view transitions are intentional and the internal transition names match what the author has meant
I think a key to this is ensuring that SPA and MPA transitions across the same two pages are as similar as possible. The difference being that one opts in with JS (by calling
startViewTransition
), and the other opts-in via some other simple means.to solve this in CSS you'd have to coerce CSS to do several things that it's not used to doing... for example, you might have
@view-transition same-origin
in your inline css, and then include some cross-origin stylesheet that would override it with@view-transition same-site
or@view-transition urlpattern(...)
, which would require fiddling with cascading rules.This is already an issue with @Keyframes, @font-face, @Property etc etc, so we can follow the same rules.
All of these are kind of "definitions" - having a named thing (font, animation, property) and declaring what it does. For example, you can't put those inside a media-query.
I think we would have to solve (2) on the HTML level, or even CSP, and my main concern there would be that it would become a side-channel for cross-origin pages to pass privacy-related information to each other, which an opt-in wouldn't solve.
Can you provide details of the attack you're trying to prevent? Third party CSS isn't safe.
The whole cross-origin problem has not been discussed yet so this is more of an initial thought. The problem space is of passing user information in a side-channel, like a make-shift same-site cookie that's not guarded by the regular cookie protections. My main point is that cross-origin is a different problem space than maintaining author intention when switching document.
One thing I don't understand is - if this is not about maintaining author intention, why do we need an opt in for same-origin in the first place?
It's a simple boolean with "we might add more things to it in the future". I'm saying, let's use name-matching now instead of "boolean plus future unknown things".
Media queries already exist, so they're not some magic future thing we don't know about. Also, I think a way to change transitions depending on the incoming and outgoing page should be part of the initial launch of the MPA feature.
With SPA, we ask developers to use class names on the root. The MPA solution should be similar to that, or the MPA solution should be available to SPA too. Note that these media queries don't just enable opt-in/out, they also allow for different transitions to be defined depending on the source and destination. The name-matching part doesn't allow for that, and I don't see what use it would be when there are more extensible solutions on the table.
Have you seen https://github.com/w3c/csswg-drafts/issues/8679? It seems similar to your name-matching proposal, but doesn't require a CSS property that only works on one element.
Two problems with this: You have to do special hard-to-reason-about things to use the same transitions for both SPA and MPA
I don't understand this problem. You have to give your whole transition a global name/namespace, and make sure both documents know about it.
It's global as much as the rest of CSS is - you can use media queries to say when particular rules apply.
This is similar to giving elements that transition to each other matching names. Where is the "hard to reason" part?
You're proposing a CSS property that will exist on every element, but only ever means something on one element. That seems confusing. Or, you're proposing that view-transition-name
has an extra feature attached for the root element only, and for MPA transitions only, and means you need to do something special if you want to share transitions between your MPA and SPA (by forcing a name other than root
for the root). This also seems confusing.
it creates a CSS property that only works on one element (or a property that works very differently on one particular element). That's the whole reason a global switch was proposed rather than something per-element.
It creates one element in the same way
::view-transition
is a pseudo-element of only one element. If in the future we enable several view transitions to work simultaneously, this can be more flexible.
Maybe I'm misunderstanding what you're proposing, because that doesn't seem like a meaningful comparison.
I thought you were proposing a CSS property, page-transition-name
, that would exist on every element (because that's how CSS properties work), but only ever apply to the root element.
::view-transition
was designed from the start to be on other elements in future. It's one of the key reasons we went for pseudo-elements rather than shadow DOM. I don't think the simultaneous-transitions argument applies for page transitions, as there can only be one page transition at once due to the scope of navigations.
This is already an issue with @Keyframes, @font-face, @Property etc etc, so we can follow the same rules.
All of these are kind of "definitions" - having a named thing (font, animation, property) and declaring what it does. For example, you can't put those inside a media-query.
Sure, but I don't think that difference matters in this context. We still have an order to apply the rules from those other features.
One thing I don't understand is - if this is not about maintaining author intention, why do we need an opt in for same-origin in the first place?
What gave you the impression that author intention wasn't being maintained? That's the whole reason for the opt-in.
With SPA, the developer opts in via startViewTransition
. They can make this opt-in conditionally with the knowledge of the before 'page' and after 'page', along with user preference. They use class names to customise the transition for those specific states.
With MPA, the developer opts in via the opt-in proposed in this issue. Via media queries they can make this opt-in conditionally. With https://github.com/w3c/csswg-drafts/issues/8683, they can make the before and after page part of this condition. The same feature allows them to customise the transition for specific states. Existing media queries let them react to user preference.
If https://github.com/w3c/csswg-drafts/issues/8683 is enabled for SPA too, developers will be able to use the same animation styles for both SPA and MPA, which is one of the key goals.
It's a simple boolean with "we might add more things to it in the future". I'm saying, let's use name-matching now instead of "boolean plus future unknown things".
Media queries already exist, so they're not some magic future thing we don't know about. Also, I think a way to change transitions depending on the incoming and outgoing page should be part of the initial launch of the MPA feature.
With SPA, we ask developers to use class names on the root. The MPA solution should be similar to that, or the MPA solution should be available to SPA too. Note that these media queries don't just enable opt-in/out, they also allow for different transitions to be defined depending on the source and destination. The name-matching part doesn't allow for that, and I don't see what use it would be when there are more extensible solutions on the table.
Have you seen https://github.com/w3c/csswg-drafts/issues/8679? It seems similar to your name-matching proposal, but doesn't require a CSS property that only works on one element.
Two problems with this: You have to do special hard-to-reason-about things to use the same transitions for both SPA and MPA
I don't understand this problem. You have to give your whole transition a global name/namespace, and make sure both documents know about it.
It's global as much as the rest of CSS is - you can use media queries to say when particular rules apply.
This is similar to giving elements that transition to each other matching names. Where is the "hard to reason" part?
You're proposing a CSS property that will exist on every element, but only ever means something on one element. That seems confusing. Or, you're proposing that
view-transition-name
has an extra feature attached for the root element only, and for MPA transitions only, and means you need to do something special if you want to share transitions between your MPA and SPA (by forcing a name other thanroot
for the root). This also seems confusing.it creates a CSS property that only works on one element (or a property that works very differently on one particular element). That's the whole reason a global switch was proposed rather than something per-element.
It creates one element in the same way
::view-transition
is a pseudo-element of only one element. If in the future we enable several view transitions to work simultaneously, this can be more flexible.Maybe I'm misunderstanding what you're proposing, because that doesn't seem like a meaningful comparison.
I thought you were proposing a CSS property,
page-transition-name
, that would exist on every element (because that's how CSS properties work), but only ever apply to the root element.
::view-transition
was designed from the start to be on other elements in future. It's one of the key reasons we went for pseudo-elements rather than shadow DOM. I don't think the simultaneous-transitions argument applies for page transitions, as there can only be one page transition at once due to the scope of navigations.This is already an issue with @Keyframes, @font-face, @Property etc etc, so we can follow the same rules.
All of these are kind of "definitions" - having a named thing (font, animation, property) and declaring what it does. For example, you can't put those inside a media-query.
Sure, but I don't think that difference matters in this context. We still have an order to apply the rules from those other features.
One thing I don't understand is - if this is not about maintaining author intention, why do we need an opt in for same-origin in the first place?
What gave you the impression that author intention wasn't being maintained? That's the whole reason for the opt-in.
With SPA, the developer opts in via
startViewTransition
. They can make this opt-in conditionally with the knowledge of the before 'page' and after 'page', along with user preference. They use class names to customise the transition for those specific states.With MPA, the developer opts in via the opt-in proposed in this issue. Via media queries they can make this opt-in conditionally. With https://github.com/w3c/csswg-drafts/issues/8683, they can make the before and after page part of this condition. The same feature allows them to customise the transition for specific states. Existing media queries let them react to user preference.
If https://github.com/w3c/csswg-drafts/issues/8683 is enabled for SPA too, developers will be able to use the same animation styles for both SPA and MPA, which is one of the key goals.
Ok I think the main difference here is about whether it's a root property or a bespoke @ rule. I can see pros and cons to both but it's not my main issue to begin with...
I can see how this could be something like:
@allow-view-transitions: none | same-document | same-site | urlpattern(...)
With same-document
as default and perhaps extendable to names/versions/separate to/from in the future. This would also make it less of an MPA-specific feature.
Hmm, would @allow-view-transitions: same-document
trigger view transitions for same-document navigations? That's been discussed before, and although there are maybe edge cases where you might want that, generally you don't want that, because you can already add things like smooth scrolling.
Hmm, would
@allow-view-transitions: same-document
trigger view transitions for same-document navigations? That's been discussed before, and although there are maybe edge cases where you might want that, generally you don't want that, because you can already add things like smooth scrolling.
No, @allow-view-transitions: same-document
would be the default, and @allow-view-transitions: none
would be equivalent to ::view-transitions { display: none }
.
Anyway, I'm working on some more comprehensive thing around the opt-in and added CSS (it's currently spread across 3 issues).
Regarding naming, I want to find something here that would be extendable to same-document declarative transitions (#8300) and to same-site cross-origin, if we ever want to do either.
Having an allow/deny thing would make that verbose, but perhaps OK?:
@cross-document-view-transtions: allow;
@cross-origin-view-transitions: allow;
@same-document-view-transtions: allow;
I think the essence of what we're "allowing" here is automatic view transitions, as in, transitions that occur without calling startViewTransition
programatically.
So perhaps:
@auto-view-transitions {
same-origin: allow;
same-site: allow;
same-document: allow;
}
or something like:
@auto-view-transitions: same-origin same-document same-site;
@allow-view-transitions: same-document
would be the default, and@allow-view-transitions: none
would be equivalent to::view-transitions { display: none }
.
Bearing in mind that ::view-transitions { display: none }
only hides transitions, it doesn't prevent them, do you really mean that?
@auto-view-transitions { same-origin: allow; same-site: allow; same-document: allow; }
or something like:
@auto-view-transitions: same-origin same-document same-site;
This feels generally in the right direction. CSSWG folks who are across multiple specs will be able to help with the naming & syntax.
It might be worth thinking about what a 'manual' cross-document view transition would look like. As in, some kind of just-in-time opt-in. And, whether this syntax makes sense in that case, particularly in the new page that drives the animation.
@allow-view-transitions: same-document
would be the default, and@allow-view-transitions: none
would be equivalent to::view-transitions { display: none }
.Bearing in mind that
::view-transitions { display: none }
only hides transitions, it doesn't prevent them, do you really mean that?
It skips the animation, doesn't just hide it.
@auto-view-transitions { same-origin: allow; same-site: allow; same-document: allow; }
or something like:
@auto-view-transitions: same-origin same-document same-site;
This feels generally in the right direction. CSSWG folks who are across multiple specs will be able to help with the naming & syntax.
It might be worth thinking about what a 'manual' cross-document view transition would look like. As in, some kind of just-in-time opt-in. And, whether this syntax makes sense in that case, particularly in the new page that drives the animation.
It skips the animation, doesn't just hide it.
It doesn't. All the capturing still happens. transition.ready
still fulfils. If we ever exposed a boolean to indicate wether the transition was skipped, or played to completion, it would say it played to completion, unless this was weirdly special-cased, which it currently isn't.
An opt-out would not perform a capture. transition.ready
would reject, since the transition doesn't reach the 'ready' state etc etc.
It skips the animation, doesn't just hide it.
It doesn't. All the capturing still happens.
transition.ready
still fulfils. If we ever exposed a boolean to indicate wether the transition was skipped, or played to completion, it would say it played to completion, unless this was weirdly special-cased, which it currently isn't.An opt-out would not perform a capture.
transition.ready
would reject, since the transition doesn't reach the 'ready' state etc etc.
I meant that the animation is skipped, not exactly "hidden". But anyway, I don't want to nitpick on this, not pursuing this way of opting out.
Fwiw, I'm not nitpicking, since it has a large impact on the behaviour of the API you're designing.
Fwiw, I'm not nitpicking, since it has a large impact on the behaviour of the API you're designing.
I was referring to my own nitpicking :)
I like the suggestion of a few configuration switches through some at-rule.
Looking a bit broader than just View Transitions, I’m starting to wonder if we need a @config
at-rule to – in the future – also include other configuration data such as preferred scrollbar types, viewport unit resize behaviors, etc. view-transitions
could be a descriptor in this at-rule.
For the purpose of the F2F, summarizing current thinking:
To maintain consistency with other CSS features, the idea is to have a new rule, currently called @auto-view-transitions
, which can contain declarations that indicate to the UA the conditions in which view-transitions are to be automatically/declaratively performed (as opposed to programmatically as in vt-1).
Initially, this rule would only have one possible declaration: same-origin: enabled | disabled
(disabled by default). If enabled in both sides, a same-origin navigation would trigger an automatic view transition.
See current spec draft.
Note that this value is read at exactly one point in both documents:
In the future we would want this opt-in to respond to media queries, e.g. for prefers-reduced-motion
.
Main purpose of the discussion is:
same-origin
as the property name tells the right story, and if not, to find an alternativeIs the intent still to allow this rule to be nested within a media query? That seems important to avoid opting in when prefers-reduced-motion
. There needs to be some rule to decide which value 'wins' if there are multiple definitions. Source order is one option, but it's worth thinking about how this interacts with @layer
.
Is the intent still to allow this rule to be nested within a media query? That seems important to avoid opting in when
prefers-reduced-motion
. There needs to be some rule to decide which value 'wins' if there are multiple definitions. Source order is one option, but it's worth thinking about how this interacts with@layer
.
Yes, the intent is to either nest it in media-queries or support media matching with a different syntax that supports some specificity as you said. But it's not there in the first draft, suggesting to add that incrementally.
There needs to be some rule to decide which value 'wins' if there are multiple definitions. Source order is one option, but it's worth thinking about how this interacts with @layer.
+1 to something other than source order. Ideally I would've liked something analogous to specificity in CSS selectors but media queries don't work that way.
Source order is one option, but it's worth thinking about how this interacts with
@layer
.
My jetlag might be taking the upper hand here, but I don’t immediately see that one would want to use layers to tweak this behavior. Do you have a specific example in mind? If so, what would be your suggestion?
Typically at-rules that “define stuff” – e.g. @property
– get hoisted up to the global level, with the last declaration winning.
Right, but we want these to work in media queries, and media queries can exist in @layer
(I think??)
The CSS Working Group just discussed [css-view-transitions-2] Declarative opt-in for cross-document navigations
, and agreed to the following:
RESOLVED: Use an at-rule (of some kind) to control cross-document VT transitions, syntax tbd
Following up on the F2F, this is the current suggestion including "future" features, based on the feedback from the f2f:
@view-transitions {
/* default: none. Same-document is a potential future feature. */
navigation-trigger: cross-document | same-document | any | none;
/* If present on both sides, has to match, and affects transition-names/media-query */
/* pending decision on view-transition-groups etc. See #8960 */
group-name: foobar;
/* If present on either, has to match. TBD whether we actually need this. */
version: 1234;
}
To match navigations, the rule would have extra pseudo-classes, equivalent to page rules, e.g.:
/* exact syntax to qualify navigations TBD */
@view-transitions :navigating(from --articles) {
group-name: from-articles;
}
In addition, the rule can be nested inside any media-query:
@media (prefers-reduced-motion: no-preference) {
@view-transitions {
navigation-trigger: cross-document;
}
}
To support same-site in the future, the idea is that we would want the developer to be explicit about which origins are allowed rather than a blanket same-site
.
For the current set of features, this is how it would look like when you want to enable MPA transitions:
@view-transitions {
navigation-trigger: cross-document;
}
I'm not sure what group-name
is. Is there a description of view-transition-groups
anywhere?
I'm not sure what
group-name
is. Is there a description ofview-transition-groups
anywhere?
It came up at the F2F as a potential solution when we were discussing #8960 and a lot of its details are TBD. See resolution. I didn't want to get deep into this here, and when we have a better understanding of how we tackle #8960 we should update this. I put it here because of @fantasai's request that we should imagine the future things the at-rule could do and see how they fit in when we implement the basic case.
The main concept is that it's a string you pass to startViewTransition
or via the opt-in, and becomes a namespace of sorts to which transition names are enabled and how the pseudo-elements are styled, potentially using it in a media query.
In the context of the rule, since the rule replaces startViewTransition
in the cross-document world, this descriptor (with whatever name it ends up with) would be what translates the navigation parameters (old URL, new URL, navigation type) to whatever groups together different elements to a transition.
Going forward I'd like to keep the discussion around that in #8960, and update here if it affects the declarative opt-in.
I put it here because of @fantasai's request that we should imagine the future things the at-rule could do and see how they fit in when we implement the basic case.
I think that's a reasonable thing to do. Similar to how we sketched out nested & scoped transitions, to prevent us designing ourselves in a corner.
What should we do with the at-rule inside shadow trees? Presumably it shouldn't opt-in the document but it also doesn't seem to have any meaning inside a tree - should we restrict it to only the root document? Not sure if there's any precedent with other rules.
+@tabatkins who may have an opinion based on https://drafts.csswg.org/css-scoping/
should we restrict it to only the root document?
I'm assuming "root document" doesn't imply main frame. We do need the opt-in for Documents in iframes. Other than that makes sense to ignore the rule in shadow trees.
This is a summary of the offline discussions for this issue and the current proposal.
The base idea is still to have a new at-rule called @view-transition
which decides whether a navigation will execute a ViewTransition.
The simplest API would be a single property with a binary value:
@view-transition {
transitions: allow | disallow;
}
But authors need to configure the opt-in, and the transition animations, based on the type of navigation. The following summarizes all such cases based on 2 parameters explained below.
Case | Needs opt-in | Needs customization |
---|---|---|
same doc navigation | ✅ | ❌ |
cross doc, same origin navigation | ✅ | ❌ |
same site navigation | ✅ | ❌ |
script initiated transition | ✅ | ❌ |
old/new url selection | ✅ | ✅ |
history navigation: back | ❌ | ✅ |
history navigation: forward | ❌ | ✅ |
reloads | ✅ | ✅ |
replace | ❌ | ❓ |
push | ❌ | ❓ |
Authors are likely to customize the transition animation (which elements have a view-transition-name
, style rules which apply to VT pseudo-elements) based on the case. We need to allow authors to control any CSS for these cases, which implies that it can also configure the opt-in.
A media-query seems like the best fit. For example, the following CSS for back/forward history traversal:
/* Back navigation has the current page slide right to reveal the back entry. */
@media (navigation: back) {
::view-transition-old(root) {
animation: slide-to-right;
z-index: 1;
}
}
/* Forward navigation has the forward entry sliding from right to cover the old page */
@media (navigation: forward) {
::view-transition-new(root) {
animation: slide-from-right;
}
}
However, using a media-query implies that authors need to order their CSS correctly. For example, the following won’t work.
@media (prefers-reduced-motion) {
@view-transition {
transition: disallow;
}
}
@view-transition {
transition: allow;
}
We considered solving it by adding specificity if the declaration is gated by a media query. But since this is how media queries work by design, and authors are used to the pattern, it doesn’t seem necessary.
Authors are likely to enable or disable transitions based on the case. If a case only needs to configure the opt-in then it's simpler to add it to the opt-in syntax itself.
@view-transition {
transition-trigger: [ same-document-navigation || cross-document-same-origin-navigation || same-site-navigation || script] | none;
}
transition-trigger
identifies the source of the DOM change. The following tokens are part of this proposal (bikeshedding welcome). The initial value for transition-trigger
will be script
.
cross-document-same-origin-navigation
: A navigation where the Document changes, URLs from both Documents are same-origin and there are no cross-origin redirects.
script: The transition was initiated by calling document.startViewTransition
. This is added to provide a declarative way to disable script initiated transitions in some cases. For example:
@media (prefers-reduced-motion) {
@view-transitions {
transition-trigger: none;
}
}
The following tokens will be added in the future:
same-document-navigation
: A navigation where the Document is unchanged. This excludes cases with a 204 network response or download links, where the navigation would've been cross-document if committed. For example, pushState
from script or a hash change. This is to enable transitions for same-document navigations declaratively (instead of calling startViewTransition
from script).
It’s unclear whether we can support all cases since the browser needs to be able to render a frame with pre-navigation state. Since pushState
is a synchronous API, there is no way to signal to script when the DOM can be updated.
same-site-navigation: A navigation where the Document changes, URLs from both Documents are same-site and there are no cross-site redirects.
There are security concerns here that need more exploration.
This option is the proposed resolution.
Option 1 implies that transitions will be enabled for reloads by default. Authors will have to use the following CSS to disable them:
@media (navigation: reload) {
@view-transitions {
transition-trigger: none;
}
}
This goes against the resolution at https://github.com/w3c/csswg-drafts/issues/8784#issuecomment-1640701514 that reloads should not trigger a transition by default. Having something off-by-default is not feasible if the case is a media-query, it has to be a part of the at-rule. So the solutions are:
Revisit https://github.com/w3c/csswg-drafts/issues/8784 to have reloads trigger transitions by default. The CSS to disable them is trivial.
Add a bespoke reload
property to the @view-transition
syntax which is default off:
@view-transition {
transition-trigger: [same-document-navigation || cross-document-same-origin-navigation || cross-site-navigation || script] | none;
reload: allow | disallow;
}
The following are issues which intersect with the new syntax but details should be discussed on the linked issues.
The following media-style queries (I'm not sure what the @-rule should be called, and whether it should be @media
proper) will be added for the “needs customization” cases above:
There is related discussion on https://github.com/w3c/csswg-drafts/issues/8925.
Specify a transition-name
for the use-cases described in https://github.com/w3c/csswg-drafts/issues/8960#issuecomment-1640742768. It's a way to specify a type of the transition and be able to set styles based on that type, in some specialized way.
The transition-name
can be a new parameter in the @view-transition
rule. The browser takes the value from the at-rule when the transition starts (same time as when the opt-in is checked) and then uses it for the duration of that transition.
Option 1 implies that transitions will be enabled for reloads by default.
You mean cross-document-same-origin-navigation
implies that? What if that explicitly excluded reloads? Then we can add a reload
keyword to transition-trigger
. A separate reload
descriptor doesn't seem useful as reloads are meant to be a type of transition trigger. And some combinations of transition-trigger
and reload
like transition-trigger: none; reload: allow;
don't make much sense.
So the whole syntax would then look like this:
@view-transition {
transition-trigger: [ same-document-navigation || cross-document-same-origin-navigation || same-site-navigation || script || reload ] | none;
}
Sebastian
You mean cross-document-same-origin-navigation implies that? What if that explicitly excluded reloads? Then we can add a reload keyword to transition-trigger.
That's an interesting idea!
One aspect, which I now realize wouldn't work for option 2 either, is that we wanted to ensure that all cases in transition-trigger
are mutually exclusive. Except short-hands, for example a keyword like same-origin
which expands to same-document-navigation, cross-document-same-origin-navigation
.
The above allows developers to enable/disable all possible combinations. All navigation cases (same-document-navigation
, cross-document-same-origin-navigation
, same-site-navigation
) can technically be triggered by a reload. So with the current syntax authors can't enable transitions only for reloads which are cross-document navigations. But I can't think of a case where authors would want different behaviour for cross-document vs same-document reloads.
Another thing to consider is we later on add reload
to a media-query (because authors want to customize the transition differently for it). It would be a bit awkward to have reload
in both the opt-in syntax and media-query but doable.
Option 1 implies that transitions will be enabled for reloads by default.
You mean
cross-document-same-origin-navigation
implies that? What if that explicitly excluded reloads? Then we can add areload
keyword totransition-trigger
. A separatereload
descriptor doesn't seem useful as reloads are meant to be a type of transition trigger. And some combinations oftransition-trigger
andreload
liketransition-trigger: none; reload: allow;
don't make much sense.So the whole syntax would then look like this:
@view-transition { transition-trigger: [ same-document-navigation || cross-document-same-origin-navigation || same-site-navigation || script || reload ] | none;
reload is mutually exclusive from cross-document vs. same-document. E.g. you can have a same-document reload with the navigation API.
I see a navigation as divided into 3:
is cross document
in the spec.The way I see it, the first two might affect which transition is triggered and not just whether it is triggered, while the last one shouldn't. e.g. you probably don't want a different transition based on whether this was a same-document or a cross-document navigation: only based on the type and origin of the navigation.
So to enable reloads in the future I see things this way: we would have a pseudo-class to the rule which qualifies the navigation (type+origin), but does not qualify based on mechanism:
/* UA stylesheet */
@view-transitions :reload {
navigation-trigger: none;
}
@view-transitions {
navigation-trigger: none;
}
/* user stylesheet: this would enable cross-document VT but *not* reload, due to specificity */
@view-transitions {
navigation-trigger: cross-document;
}
/* user stylesheet: this would enable cross-document VT on reload */
@view-transitions :reload {
navigation-trigger: cross-document;
}
This of course needs more work but the general idea is that whatever qualifies the navigation and can affect which transition is triggered and not just whether it's triggered, goes outside the brackets and not into the navigation-trigger
rule. Happy to hear alternatives.
@noamr sounds like you're suggesting using pseudo-classes instead of media queries for the "Needs customization" cases (which includes reload). Since pseudo-classes come with specificity, as opposed to media-queries, UA stylesheet can have a rule to disable reloads which can be overridden by the author. Just to put it again:
UA CSS
@view-transitions:reload {
navigation-trigger: none;
}
Author CSS
@view-transitions {
navigation-trigger: cross-document-same-origin-navigation script;
}
The UA rule will apply when there is a reload because it has higher specificity. But the same with media-queries won't work:
UA CSS
@media (navigation: reload) {
@view-transitions {
navigation-trigger: none;
}
}
Author CSS
@view-transitions {
navigation-trigger: cross-document-same-origin-navigation script;
}
I think pseudo-classes can work but IIUC they are meant to be for state which is per-element or a subset of elements. I haven't seen an example where a pseudo-class would activate for the whole Document.
Our primary motivation for using pseudo-classes is also specificity. Let's see if the WG thinks that's the right reason to use pseudo-classes instead of media-queries.
@noamr sounds like you're suggesting using pseudo-classes instead of media queries for the "Needs customization" cases (which includes reload). Since pseudo-classes come with specificity, as opposed to media-queries, UA stylesheet can have a rule to disable reloads which can be overridden by the author. Just to put it again:
UA CSS
@view-transitions:reload { navigation-trigger: none; }
Author CSS
@view-transitions { navigation-trigger: cross-document-same-origin-navigation script; }
The UA rule will apply when there is a reload because it has higher specificity. But the same with media-queries won't work:
UA CSS
@media (navigation: reload) { @view-transitions { navigation-trigger: none; } }
Author CSS
@view-transitions { navigation-trigger: cross-document-same-origin-navigation script; }
I'd probably just say navigation-trigger: cross-document
or trigger: cross-document-navigation
, and leave the same-origin part to URL specialization, e.g.:
@view-transitions :cross-origin {
navigation-trigger: cross-document;
}
I think pseudo-classes can work but IIUC they are meant to be for state which is per-element or a subset of elements. I haven't seen an example where a pseudo-class would activate for the whole Document.
The @page
CSS rule uses pseudo-classes in this way. Was using that for inspiration.
Our primary motivation for using pseudo-classes is also specificity. Let's see if the WG thinks that's the right reason to use pseudo-classes instead of media-queries.
Great, let's see! Note that for the purpose of the opt-in it doesn't really matter if it's in a media-query or in the rule, as long as it's outside the braces :)
I'd probably just say navigation-trigger: cross-document or trigger: cross-document-navigation, and leave the same-origin part to URL specialization
Hmmm, I have a preference for having different tokens for same-origin
vs same-site
in the opt-in. Because there is a security aspect to opt-ing in for same-site, it's better for that to be explicit. Rather than a cross-document
token which implicitly opt-ins for same-site URLs as well. Also makes it easier to add same-site support later.
The @page CSS rule uses pseudo-classes in this way. Was using that for inspiration.
Great! Indeed, I see an example here. I actually like it better because of specificity. Let me summarize Option 3 then.
This is assuming we use pseudo-classes instead of media queries for "Needs Customization" cases. These pseudo-classes can be used with any selector and the @view-transitions
rule. The initial value for transition-trigger
is still script
except we have the following style rule in UA CSS.
@view-transitions :reload {
transition-trigger: script;
}
Other navigation types will also be using similar pseudo-classes.
/* UA stylesheet */ @view-transitions :reload { navigation-trigger: none; } @view-transitions { navigation-trigger: none; } /* user stylesheet: this would enable cross-document VT but *not* reload, due to specificity */ @view-transitions { navigation-trigger: cross-document; } /* user stylesheet: this would enable cross-document VT on reload */ @view-transitions :reload { navigation-trigger: cross-document; }
I find this syntax is very confusing. It looks like state is added, yet the descriptors inside it apply when the state is no longer active? Tacking those states onto the @view-transition
rule seems like the wrong place to do so.
Take :reload
for example:
The first rule in the block above for example can be interpreted in various ways:
I assume the third is what is meant, but I find the proposed syntax not to reflect this.
Rewinding back to what was proposed and refined here: the idea of the @view-transitions
rule is only to configure things, not to respond to things.
You can still respond to various ways of how the view transition was invoked, but that’s not the responsibility of the @view-transitions
rule. To respond to the type of navigations, something separate could cover that, e.g. an @media
rule:
/* 1. VT Configuration */
/* Allow these types of triggers */
@view-transitions {
transition-trigger: cross-document-same-origin-navigation reload script;
}
/* Disable VTs alltogether when the user does not want it */
@media (prefers-reduced-motion: reduce) {
@view-transitions {
transition-trigger: none;
}
}
/* 2. Tweak the VTs in response to the type of navigation */
/* User has reloaded the page */
@media(navigation-type: reload) {
::view-transition-old(root) {
display: none;
}
::view-transition-new(root) {
animation-name: fade-in;
}
}
/* Apply a certain type of animation when navigation from the article detail page to the articles overview page */
@media (navigation: from --article to --articles) {
::view-transition-old(content) {
animation-name: slide-out-to-right;
}
::view-transition-new(content) {
animation-name: slide-in-from-left;
}
}
/* Etc. */
Cross-document transitions need some way for both the old & new page to opt-in to a transition.
The SPA API uses JS for this, but it would be nice if cross-document transitions didn't depend on JS.
It feels like this should be in CSS, so it can be influenced by media queries, particularly
prefers-reduced-motion
.The tricky part here is it's a "global" setting, so it can't be a CSS property.
Proposal:
However, I'm just making stuff up here. Is there any prior art here for setting 'global' things?