The Future of CSS: Construct <custom-ident> and <dashed-ident> values with ident()

CSS uses a lot of so called idents to name things – think of the values that you put in for view-transition-name, view-timeline-name, container-name, etc.

Constructing these unique values for a lot of elements in one go is often a lot of repetitive work, in which you find yourself repeating selectors and *-name declarations for each and every element. Got 100 elements? That’s 100 declarations (as part of 100 rules) please. Ugh.

Hot off the press is a new CSS Working Group resolution to adopt a solution I proposed to solving this problem: the ident() function.

~

⚠️ This post is about a future CSS feature. You can’t use it … yet.

This feature only has a resolution saying that it should be part of the css-values-5 specification. It still needs to be formally specified and does not exist in any browser yet. This post is more of an explainer, outlining the problem space + solution, to show you what you will (~ should) be able to do with in the future.

UPDATE 2024.01.14 The Pull Request that adds this to the spec got merged. You can now find ident() in the css-values-5 specification. Browser support remains unchanged: this is not in any browser yet.

~

Table of Contents

~

# The Problem

In many CSS features, you need to give elements a certain name so that you can later refer to those elements. Think of container-name, view-transition-name, view-timeline-name, scroll-timeline-name, etc.

Depending on the property these names are a <custom-ident> or a <dashed-ident>. These names need to be unique (within the feature that’s being used). In case of View Transitions and Scroll-Driven Animations this uniqueness can become burden for authors.

Take this Scroll-Driven Animations example, where I set a unique view-timeline-name – a <dashed-ident< for the occasion – per targeted element and also refer to that timeline later on:

.parent {
  timeline-scope: all;
}

main {
  div:nth-child(1) { view-timeline-name: --tl-1; }
  div:nth-child(2) { view-timeline-name: --tl-2; }
  div:nth-child(3) { view-timeline-name: --tl-3; }
}

nav {
  li:nth-child(1) { animation-timeline: --tl-1; }
  li:nth-child(2) { animation-timeline: --tl-2; }
  li:nth-child(3) { animation-timeline: --tl-3; }
}

Same with View Transitions, where I’ve seen this code in the wild:

&:nth-child(1) {
  view-transition-name: opt-1;
  & > label {
    view-transition-name: opt-1-label;
  }
  & > input {
    view-transition-name: opt-1-input;
  }
}
&:nth-child(2) {
  view-transition-name: opt-2;
  & > label {
    view-transition-name: opt-2-label;
  }
  & > input {
    view-transition-name: opt-2-input;
  }
}
&:nth-child(3) {
  view-transition-name: opt-3;
  & > label {
    view-transition-name: opt-3-label;
  }
  & > input {
    view-transition-name: opt-3-input;
  }
}

While both examples are limited to only 3 items, you can easily tell that this becomes a burden when there are more items at play.

~

# The Solution: ident()

To make things easier when it comes to naming elements en masse, I proposed the ident() function to the CSS Working Group. It’s a function to dynamically construct <custom-ident> and <dashed-ident> values.

<ident()> = ident( <ident-arg>+ )
<ident-arg> = <string> | <integer> | <ident>

The function accepts an arbitrary number of space-separated arguments, but needs at least 1. The arguments are of the type <string>, <integer> or another <ident>. The result of the function is an <ident> that consists of the passed in arguments concatenated together.

For example, with sibling-index(), the first example shared earlier can be rewritten as follows:

.parent {
  timeline-scope: all;
}

nav li {
  animation-timeline: ident("--tl-" sibling-index()); /* --tl-1, --tl-2, … */
}

main div {
  view-timeline-name: ident("--tl-" sibling-index()); /* --tl-1, --tl-2, … */
}

This code auto-scales, regardless of how many elements are being used in the markup.

~

# More Examples

The ident() function shines when you combine it with other functions/features. I’ve already mentioned sibling-index() and another such one is the attr() function which you can use to get values of HTML attributes into CSS.

For example:

.item { 
  view-timeline-name: ident("--item-" attr(id) "-tl");
}

label { 
  animation-name: ident("--item-" attr(for) "-tl");
}

With .item elements that have ids like beach, house, and bike; the resulting view-timeline-names would be --item-beach-tl, --item-house-tl, and --item-bike-tl respectively.

Or here’s a more advanced example that first reads the id attribute from a .card and stores it into a custom property. Because custom properties compute before they inherit, the children can refer to that custom property just fine.

.card[data-view-transition-id] {
  --id: attr(data-view-transition-id); /* E.g. card1, card2, card3, … */

  view-transition-name: var(--id);
  view-transition-class: card;

  h1 {
    view-transition-name: ident(var(--id) "-title"); /* E.g. card1-title, card2-title, card3-title, … */
    view-transition-class: card-title;
  }

  .content {
    view-transition-name: ident(var(--id) "-content"); /* E.g. card1-content, card2-content, card3-content, … */
    view-transition-class: card-content;
  }
}

~

# FAQ

As part of the discussion at the CSS WG today, I prepared a short list of Frequently Asked Questions:

# Why do we need a function? Can’t one directly glue things together, e.g. view-transition-name: var(--id) "title";?

Parsing purposes. Think of shorthands, such as scroll-timeline, where it would be hard to detect the longhands it consists of.

Without ident() it’s not clear which parts make up the ident:

  • scroll-timeline: inline "tl-" var(--id)
  • scroll-timeline: "tl-" var(--id) inline

With ident() it’s clear what goes together:

  • scroll-timeline: inline ident("tl-" var(--id))
  • scroll-timeline: ident("tl-" var(--id)) inline

~

# Why not use the redesigned attr() for this, which can parse to idents? E.g. attr(foo type(<custom-ident>))?

The ident() function goes beyond atrr() as it allows you to glue pieces of string together, e.g. ident("view-" attr(data-vt-id)) or ident("view-" attr(data-type) "-" attr(data-sequence)).

The pieces that need to be glued can come from other elements as well, by storing those into custom properties.

~

# What about constructing <dashed-ident>s?

Prepend "--" at the start, e.g. ident("--item-tl-" attr(data-itemnum))

~

# Doesn’t the attr() function need type(<custom-ident>) added to it (which now makes your examples look shorter)?

No, when no <attr-type> is given, the <attr-name> gets parsed to a CSS string (see spec). Because ident() is designed to accept <string> values as well, it works just fine.

~

# Why not rely on something like …-name: auto to auto-generate idents?

The problem with …-name: auto solutions is that:

  • These only work for that specific feature, whereas ident() can be used for all features that use idents.
  • They generate an ident that is not observable by you. This means that you can’t refer to that generated ident later on. For features like Containers, Scroll Timelines, etc. you need to be able to refer to that ident from somewhere else in your markup.
  • They are only meaningful on the targeted element itself. With ident() you can store the value in a Custom Property to pass values down to children.
  • The don’t work when multiple elements need to use the same ident (at different times of the page’s lifecycle). See an aspirational demo like https://codepen.io/bramus/pen/PogVZwb in which the title-item-1 generated name is conditionally applied to two different elements.

The proposed ident() does not have these limitations; It transcends per-feature solutions like …-name: auto.

~

# Browser Support

💡 Although this post was originally published in December 2024, the list below is constantly being updated. Last update: Dec 18, 2024.

This feature is not supported in any browser. To follow along with the progress – if any – you can follow these browser issues:

Chromium (Blink)

❌ No Support

CrBug #384930424

Firefox (Gecko)

❌ No Support

Issue #1942078

Safari (WebKit)

❌ No Support

Issue #284895

Don’t expect any movement on these soon, though. This feature came into existence just today and still needs to be formally specified (as part of css-values-5). This can take a long time.

The embed below will color green when ident() support is enabled in your browser.

See the Pen CSS ident() Support test by Bramus (@bramus) on CodePen.

~

# Spread the word

Feel free to repost one of my posts on social media to give them more reach, or link to the blogpost yourself one way or another 🙂

~

Published by Bramus!

Bramus is a frontend web developer from Belgium, working as a Chrome Developer Relations Engineer at Google. From the moment he discovered view-source at the age of 14 (way back in 1997), he fell in love with the web and has been tinkering with it ever since (more …)

Unless noted otherwise, the contents of this post are licensed under the Creative Commons Attribution 4.0 License and code samples are licensed under the MIT License

Join the Conversation

1 Comment

  1. That’s a neat proposal. Just tested anchor positioning and the thing that I immediately wanted to do when creating a tooltip was `[show-tooltip] { anchor-name: attr(show-tooltip); }`.

    With ident I guess it would be something like `[show-tooltip]{ anchor-name: ident(“–tooltip-” attr(show-tooltip)); } .tooltip { position-anchor: ident(“–tooltip-” attr(tooltip-name)); }` which would let you declare tooltips like `Click hereI’m a tooltip`.

Leave a comment

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.