You want a slide-left when the user goes from home to about, and a slide-right on the way back. You have multi-page View Transitions in your corner. So you write a pagereveal listener, read location, match URL patterns, set types in script, and remember to set the outbound side in pageswap. Behaviour lives in JS. The stylesheet only gets to react to whatever names you managed to pin on the DOM in time.
Bramus's write-up on the new CSS Route and Navigation Matching proposal (draft name css-navigation-1, co-authored with Noam Rosenthal and David Baron) flips that. URLs become named things the stylesheet already knows about, and the transition rules read like any other conditional CSS.
Naming routes with @route
@route gives a URL pattern a name. The name is a custom identifier (double-dash prefixed) that other rules can point at:
@route --home { pathname: url-pattern('/'); }
@route --about { pathname: url-pattern('/about'); }
@route --detail { pathname: url-pattern('/detail/:id'); }
The matching engine inside url-pattern() is path-to-regexp, the same syntax the JavaScript URLPattern API already uses, so /detail/:id means what you'd expect. The proposal lists protocol, hostname, port, pathname, search and hash as descriptors, so a single @route can constrain more than the path.
Querying the current move
@navigation is where you ask "which navigation is happening right now?" using from: and to: conditions against your named routes:
@navigation (from: --home) and (to: --about) {
@view-transition {
navigation: auto;
types: slide-all-to-left;
}
}
@navigation (from: --about) and (to: --home) {
@view-transition {
navigation: auto;
types: slide-all-to-right;
}
}
Read that as a CSS-native replacement for the pageswap/pagereveal branch. No JS listener, no manual location.pathname matching, no imperative call to set the transition types. The types are declared next to the routes that trigger them.
Two more keywords widen the net. between matches both directions of a pair, and at narrows to one side of that pair (the outgoing side or the incoming side), so you can bracket an entire round trip in one block:
@navigation (between: --home --detail) {
@navigation (at: --home) {
:nav-source img {
view-transition-name: image;
}
}
@navigation (at: --detail) {
img#hero {
view-transition-name: image;
}
}
}
Two selectors that finally see the navigation
The block above leans on :nav-source, a pseudo-class that matches the element which initiated the outgoing navigation. It's modelled on NavigateEvent.sourceElement. That's the piece the declarative approach has been missing: the clicked thumbnail can pick up its own view-transition-name at the exact moment the transition is being captured, without a script traversing the DOM to hand-tag it first.
The companion is :link-to(), a functional selector that matches links by where they'd go:
:link-to(--detail) {
color: hotpink;
}
A route name goes in, and every <a> pointing at a URL that matches that route gets styled. Bramus flags that parameter-scoped matching (only --detail routes where id equals 6, for example) is still being worked on.
Where this sits
None of the above ships in a browser yet. It's a draft spec on the way to the CSS Working Group F2F meeting in Berlin the week after the post, following the initial CSSWG introduction in January and a developer feedback session at CSS Day in June. The interesting bit for anyone building multi-page apps is the shape it commits to: URLs become part of the same declarative surface as media queries, container queries and @view-transition. The pageswap/pagereveal handlers stay in the platform, but the common cases stop needing them.
Two things worth trying in your head before you agree with the direction. First, custom-ident routes are cheap to add (--home, --detail, --auth) but they're global, and route-name collisions across a large codebase will feel like z-index bugs unless the working group nails a scoping story. Second, the moment :link-to() lands, "style outbound links to the checkout differently" stops being a data-attribute exercise. That's a small win with a lot of surface area.
Top comments (0)