Skip to content

Instantly share code, notes, and snippets.

@bramus
Last active January 28, 2026 17:05
Show Gist options
  • Select an option

  • Save bramus/2e463f180be00e89d4b66b972ab95713 to your computer and use it in GitHub Desktop.

Select an option

Save bramus/2e463f180be00e89d4b66b972ab95713 to your computer and use it in GitHub Desktop.
Route and Link Matching in CSS Exploration

Route and Link Matching in CSS Exploration

Collection of scenarios I’d like to do with this … with a potential syntax to do actually it.

Before we begin

All what follows builds on top of https://wicg.github.io/declarative-partial-updates/css-route-matching/ and assumes a routemap of the following (here defined in CSS, but that could as well be <script type=routemap>):

@routes {
  --home: urlpattern("/");
  --items_overview: urlpattern("/items{/:page_num}?");
  --items_detail: urlpattern("/items/:item_id");
  --categories_overview: urlpattern("/categories");
  --categories_overview: urlpattern("/categories/:category_name{/:page_num}?");
  --about: urlpattern("/about");
}

The Cross-Document View Transition opt-in is also present in the CSS:

@view-transition {
  navigation: auto;
}

The idea

  • Use the route matching from https://wicg.github.io/declarative-partial-updates/css-route-matching/
  • Introduce :navigation-trigger to indicate the element that initiated the navigation – see w3c/csswg-drafts#11801
  • Introduce :link-to() to do link matching
    • :link-to(<route-location>) = The href of the link (or the action of the form) matches the <route-location> (= <route-name> | <urlpattern()>)
    • :link-to(<route-location> with param_name: value) = The href of the link (or the action of the form) matches the <route-location> and the param_name in the href is equal to the given value.
      • param_name = a <custom-ident> that is the name of URL’s named params, e.g. page_id.
      • value = value to match against
        • Can be a direct value (e.g. 3)
        • Can be a navigation-param()
    • Also allowed is doing comparisons (numerics only)
      • :link-to(<route-location> with param_name < value)
      • :link-to(<route-location> with param_name > value)
      • :link-to(<route-location> with param_name = value)
  • Introduce navigation-param(<route-keyword>, param_name) to extract a route parameter value from the from or to or at route locations.
    • navigation-param(from, page_id) = extract the page_id from the from route location.
    • navigation-param(to, page_id) = extract the page_id from the to route location.
    • navigation-param(at, page_id) = extract the page_id from the at route location.
    • navigation-param(from, page_id, 1) = extract the page_id from the from route location, falling back to 1 if none is set.
  • Introduce with as a <route-keyword> to allow param matching on route locations. What follows after with, is a mathematical expression.
    • @navigation ((at: --items_overview) and (with: navigation-param(at, page_id, 1) = 7) { … } = When at the route items_overview with its param page_id being 7.
    • @navigation (with: navigation-param(from, page_id, 1) < navigation-param(to, page_id, 1) ) { … } = When the page_id in the from route is less than the page_id from the to route.

Scenarios

1. When navigating from items_overview to items_detail

… I want the thumbnail of the link that was clicked to transition into the big photo

One can find out which element triggered the navigation using the :navigation-trigger selector.

/* When navigating from --items_overview to --items_detail */
@navigation ((from: --items_overview) and (to: --items_detail)) {
    
    /* On the from page … */
  @navigation (at: --items_overview) {
      /* Capture the image of the link in the list that was clicked */
    #list a:navigation-trigger img {
        view-transition-name: photo;
    }
  }

  /* On the to page … */
  @navigation (at: --items_detail) {
      /* Capture the image in the #hero element */
    #hero {
        view-transition-name: photo;
    }
  }

}

(Curious --about how UA-back would work here? See scenario 4)

2. When navigating from items_overview to items_detail … and then hitting UA-back (so that it’s a navigation from items_detail to items_overview)

… I want the big photo to transition into the thumbnail in the list

For this we use link matching:

/* When navigating from --items_detail to --items_overview */
@navigation ((from: --items_detail) and (to: --items_overview)) {
    
    /* On the from page … */
  @navigation (at: --items_detail) {
      /* Capture the image in the #hero element */
    #hero {
        view-transition-name: photo;
    }
  }

  /* On the to page … */
  @navigation (at: --items_overview) {
      /* Find the image inside the link that
       links to the route `items_detail`
       with the `item_id` in that link
       matching the from route’s `item_id` param.

       Now go capture that image as the photo for the view transition.
    */
    a:link-to(--items_detail with item_id: navigation-param(from, item_id)) img {
        view-transition-name: photo;
    }
  }

}

3. When navigating from items_detail to items_overview

… I want the big photo to transition into the thumbnail in the list

This is already covered by scenario 2.

4. When navigating from items_detail to items_overview … and then hitting UA-back (so that it’s a navigation from items_overview to items_detail)

… I want the thumbnail of the link that was clicked to transition into the big photo

There is no :navigation-trigger to use on the items_overview page to make sure the big photo gets captured, but that can be solved with :link-to. Taking the code from scenario 1, the updated code becomes:

/* When navigating from --items_overview to --items_detail */
@navigation ((from: --items_overview) and (to: --items_detail)) {
    
    /* On the from page … */
  @navigation (at: --items_overview) {
      /* Capture the image in the link that was clicked */
    #list a:navigation-trigger img {
        view-transition-name: photo;
    }
    
    /* When there was nothing clicked, try and get the image
       inside the link to `items_detail`
       with the `item_id` in that link
       matching the to route’s `item_id` param.
    */
    #list:not(:has(a:navigation-trigger) {
        a:link-to(--items_detail with item_id: navigation-param(to, item_id)) img {
          view-transition-name: photo;
      }
    }
  }

  /* On the to page … */
  @navigation (at: --items_detail) {
      /* Capture the image in the #hero element */
    #hero {
        view-transition-name: photo;
    }
  }

}

5. When navigating from <any page> to/from the --about page

… I want a VT to run, but only have it capture the main elements of the site{#…-i-want-a-vt-to-run,-but-only-have-it-capture-the-main-elements-of-the-site}

Nothing fancy here, just a matter of more detailed @navigation matching:

/* When navigating to or from --about */
@navigation ((to: --about) or (from: --about)) {
   
    /* On the --about page … */
  @navigation (at: --about) {
      header {
        view-transition-name: header;
    }
    main {
        view-transition-name: main;
    }
  }

  /* On the not-about page … */
  @navigation not (at: --about) {
    header {
      view-transition-name: header;
    }
    main {
      view-transition-name: main;
    }
  }

}

Or simplified:

/* When navigating to or from --about */
@navigation ((to: --about) or (from: --about)) {
    /* Capture these elements on both pages for the View Transition */
  header {
    view-transition-name: header;
  }
  main {
    view-transition-name: main;
  }
}

Or using an indirection:

/* When navigating to or from --about */
@navigation ((to: --about) or (from: --about)) {
    /* Set the VT Type to simple-fade */
  @view-transition {
    navigation: auto;
    types: simple-fade;
  }
}

/* When a VT is active and its type is simple-fade … */
html:active-view-transition-type(simple-fade) {
  /* Capture these elements on both pages for the View Transition */
  header {
    view-transition-name: header;
  }
  main {
    view-transition-name: main;
  }
}

6. When navigating from a pagination page x to pagination page y

… I want the whole site to slide to the left

This is a matter of comparing URL parameters across pages

/* When navigating from --items_overview to --items_overview */
@navigation ((from: --items_overview) and (to: --items_overview)) {
    
    /* The from route’s page_id is smaller than the to route’s page_id: slide everything to the left */
  @navigation (with: navigation-param(from, page_id, 1) < navigation-param(to, page_id, 1)) {
    @view-transition {
      navigation: auto;
      types: slide-left;
    }
  }

  /* The from route’s page_id is greater than the to route’s page_id: slide everything to the right */
  @navigation (with: navigation-param(from, page_id, 1) > navigation-param(to, page_id, 1)) {
    @view-transition {
      navigation: auto;
      types: slide-right;
    }
  }
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment