Skip to content

[css-values] enable the use of counter() inside calc() #1026

Open

Description

@psylok

Feature request.
It would be nice to be able to use the counter() function inside of calc() function.
That would enable new possibilities on layouts.

I copy here the link to a thread which proposed it last august.
https://lists.w3.org/Archives/Public/www-style/2016Aug/0073.html

Activity

  1. AmeliaBR commented on Feb 11, 2017

    @AmeliaBR
    Contributor

    Relevant section of the spec, which includes an inline issue discussing possible use cases. But getting an issue on GitHub will hopefully provide an easier forum for other people to mention their use cases and/or possible implementation issues.

  2. MelSumner commented on Feb 26, 2017

    @MelSumner

    Hi, ran into a case today where this would have helped, so posting a comment here.

    I'm setting up some CSS for an image/logo slider. While I know right now that there are 8 images, I don't know how many there will be in the future (nor do I wish to have to know). I'm using only CSS to animate it, and it would be useful to be able to say "for each one of the items that exist (, increment this property by x."
    I ended up using sass to do something like this:

    @for $i from 1 through 8 {  
        &:nth-of-type(#{$i}) {
            $delay: calc(5s * #{$i});
    	animation: slideIn 45s linear $delay infinite;
        }
    }
    
  3. changed the title [-][css-functions] enable the use of counter() inside calc()[/-] [+][css-values] enable the use of counter() inside calc()[/+] on Mar 30, 2017
  4. Loirooriol commented on Oct 15, 2017

    @Loirooriol
    Contributor

    The problem with this proposal is that counter() returns a <string>, but calc() does not work with strings. For example, counter(id, upper-latin) might be 'A'. How exactly is calc() supposed to know that this means 1?

    So I think we need some way to get the numeric value of a counter, either by adding some parameter to counter() or a new function. This should be allowed to appear anywhere an integer is expected, including (but not necessarily in) calc(). I wrote my thoughts in #1871.

  5. Crissov commented on Oct 16, 2017

    @Crissov
    Contributor

    JFTR, I would hate to see int() or str2num() in CSS.

  6. tabatkins commented on Oct 18, 2017

    @tabatkins
    Member

    Yeah, an explicit parsing function is probably bad. No real need for it. But having something that can retrieve a counter value as a number (rather than going to the extra effort of formatting it as a string) would work. (Same as how attr() has functionality to parse the attr value as a number or dimension.)

  7. upsuper commented on Nov 2, 2017

    @upsuper
    Member

    Wouldn't allowing counter() in calc() either ruin any attempt to parallelize styling, or make us need to delay calculating of every numeric value to used-value time?

  8. Loirooriol commented on Nov 2, 2017

    @Loirooriol
    Contributor

    @upsuper Fair point, counter values are inherited from the immediately preceding element in document order instead of from the parent element as usual. So maybe it would be better to add a sibling-index() function as Tab Atkins proposed in #1869 (comment); it would be less powerful but I think it would cover most usecases.

    However, note that the CSS Lists draft currently allows counter() anywhere a <string> is expected, doesn't this have the same problem? If it's possible to use a counter value as a string, it should also be possible to use it as an integer.

  9. upsuper commented on Nov 3, 2017

    @upsuper
    Member

    However, note that the CSS Lists draft currently allows counter() anywhere a <string> is expected, doesn't this have the same problem? If it's possible to use a counter value as a string, it should also be possible to use it as an integer.

    Firstly, there are a lot fewer places where <string> is accepted, and they are usually relatively less complicated than <length> and its friends when it comes to layout, so expanding them in used-value time (like what is currently done for content property) may not be as bad.

    Also, allowing counter() anywhere <string> is allowed is something new. counter() functions are listed as independent item of content property in CSS2. And I'm not aware of any browser who has implemented that anywhere outside content. That means, its feasibility may also be questioned, and my argument above can potentially be apply to that as well.

  10. upsuper commented on Nov 3, 2017

    @upsuper
    Member

    Firstly, there are a lot fewer places where <string> is accepted, and they are usually relatively less complicated than <length> and its friends when it comes to layout, so expanding them in used-value time (like what is currently done for content property) may not be as bad.

    It may not be as bad, but it could still be very bad. One issue comes to my mind is that <family-name> can be a <string>, and font-family is inherited, so you may really want to make counter() be expanded in computed-value time (rather than in used-value time like what we do now for content), otherwise it can lead to some funny behavior. This is some case which seems to be neither useful nor easy to implement, which would be a native consequence if we allow counter() be in anywhere <string> is allowed.

    I guess we should start this topic in a separate issue, now.

  11. kizu commented on Jan 15, 2018

    @kizu
    Member

    I would really, really love to have counter's value available inside calc(). The syntax I'd propose could be something like counter-value() that would return the counter's value as an integer. Other than the use-cases for sibling-index, this could be used for a lot more cases (not only experimental ones). While I understand that that can be non-trivial to implement, the possibilities it would unlock would be tremendous.

  12. LeaVerou commented on Apr 29, 2018

    @LeaVerou
    Member

    Two more use cases:

    • Staggered animations by using it in animation-delay
    • Reversing the z-index order by using it in z-index

    Regarding syntax, I like @tabatkins's proposal for a new formatting argument, if an explicit syntax is required.
    Although IMO ideally it would be nice if we could auto cast number-like strings to numbers in calc() akin to how many programming languages do it (including JS). It's not like strings actually do anything in calc(), so there's no disambiguation problem.

  13. jonjohnjohnson commented on Apr 29, 2018

    @jonjohnjohnson

    My sad excuse of a utility without this feature...

    ...
    .count--3 { --count: 3 }
    .count--4 { --count: 4 }
    .count--5 { --count: 5 }
    ...
    .count > :nth-child(1) { --count-current: 1 }
    .count > :nth-child(2) { --count-current: 2 }
    .count > :nth-child(3) { --count-current: 3 }
    ...
    .el {
      animation-delay: calc((var(--count, 0) - var(--count-current, 0)) * 0.1s);
    } 
  14. 39 remaining items

  15. alystair commented on Nov 12, 2024

    @alystair

    @tomasdev See #8981, "We cannot make values dependent on what kind of selector they were applied with". However, in this case you could use animation-delay: calc(sibling-index() * 20ms); which is already in the spec.

    Thanks for the update re: sibling-index() @Loirooriol! This would permit me to achieve my intended use showcased in my 2022 comment above. Hope to see it implemented in the future. When used along with sibling-count() it could generate a sort of seed based randomness for artistic use (confetti, etc)... although I hope it works on #foo::before{content:''} elements!

  16. Loirooriol commented on Nov 12, 2024

    @Loirooriol
    Contributor

    @alystair For randomness, see https://drafts.csswg.org/css-values-5/#randomness

    For sibling-*() on pseudo-elements, see #9573

  17. LeaVerou commented on Nov 13, 2024

    @LeaVerou
    Member

    sibling-index() is nice but counter() is more powerful, as it doesn't just count siblings (and can be incremented with any increment). I would much rather see something that works like counter() but produces a number.

  18. petergoes commented on Mar 2, 2025

    @petergoes

    Quite recently we got an upgrade to the attr() function where it now can receive a type() function.
    Could the counter() function get a similar upgrade so we could do something like this?

    counter(counterName type(<number>))

    Reading through the comments of this issue, I understand that sometimes you can end up in a loop which is undesirable.
    As an alternative, could the calc function get an upgrade so we could do something like this:

    animation-delay: calc( counter(item) type(<number>) * 0.5s );

    Here the returned value of counter(item) is converted to a <number> by the calc function.

    Personally, I would prefer the first option.

  19. cletusw commented on May 30, 2025

    @cletusw

    Chrome intends to ship sibling-index() in the next stable version (138)!

    https://groups.google.com/a/chromium.org/g/blink-dev/c/opsqx1cBPyc/m/pJaRwh7UAAAJ

  20. 5e-Cleric commented on Jun 2, 2025

    @5e-Cleric

    Chrome intends to ship sibling-index() in the next stable version (138)!

    https://groups.google.com/a/chromium.org/g/blink-dev/c/opsqx1cBPyc/m/pJaRwh7UAAAJ

    The feature is still some ways of reaching its full potential. Whenever the style queries start processing the values of custom properties, we could have something like this with the new ternary operator if():

    <body>
        <div></div>
        <div></div>
        <div></div>
        <div></div>
        <div></div>
        <div></div>
    </body>
    body {
    
       div {
          --number:sibling-index();
          --min: 5;
          --max: 7;
          --value: clamp(0, min(
                      calc((var(--number) - var(--min)) * 1000),
                      calc((var(--max) - var(--number)) * 1000)
                   ), 1);
    
          background:if(style(--value: 1): blue; else: red);
          
       }
    
    }

    And you could style ranges of pages "relatively" easily.

    This doesn't work because right now style() queries do not compute the custom property, just evaluate the value as a string, so they evaluate the clamp as a string vs the integer as a string.

    I hope this is changed in the future.

  21. Loirooriol commented on Jun 3, 2025

    @Loirooriol
    Contributor

    @5e-Cleric This already works in Chromium Canary, just register the property as <number>: https://software.hixie.ch/utilities/js/live-dom-viewer/saved/13828

  22. 5e-Cleric commented on Jun 3, 2025

    @5e-Cleric

    Damn, spent 2h yesterday figuring this out and trying different combinations, i was using counters before i found sibling-index() and custom properties were messing assignment somehow, so i figured they would not allow for the interpretation!

    Thanks for that, then, it is indeed a great addition with lots of potential.

    (This example in particular was thought because i create documents with markdown+css, and i wanted an easy programatic way to get sections styled)

  23. Rezi commented on Jul 8, 2025

    @Rezi

    I made a hacky solution to get counter value into a variable. Here is an explaining article (including codepen) https://dev.to/rezi/css-counting-magic-converting-counter-values-to-variables-e9m
    It would be great if this can be done in an easy way.

  24. dragontheory commented on May 7, 2026

    @dragontheory

    Smell test

    Just had this conversation . . .

    Dev: "CSS is broken."
    Me: "Yeah, funny meme."
    Dev: "No, seriously. LIs don't even work. Numbers show, but that's it. Can't do [expletive] with them. Same with counters. Half baked."
    Me: "It's purposeful".
    Dev: ". . . F'ing hate CSS."

    Fail

    The browser already computes counter values for every list item. To render ::marker. To fill counter() in content:. To honor <ol start>, <ol reversed>, <li value>, @counter-style, scoped counter-reset. The user sees the values on screen.

    CSS can't read them.

    That's the whole problem. The engine knows. The language refuses to surface what the engine knows.

    li { --i: counter-value(list-item); }

    Returns <integer>. Usable in calc(), custom properties, @container style(). Same shape as sibling-index(), but counter-aware.

    @tabatkins endorsed this exact shape: typed retrieval, parallel to the attr() type() extension. @LeaVerou said it directly: "I would much rather see something that works like counter() but produces a number."

    "We have sibling-index()"

    sibling-index() ignores <ol start>, <ol reversed>, <li value>, custom @counter-style, nested counter scopes, and author-defined increments. It answers a different question.

    Authors who use semantic HTML correctly, declaring start, value, reversed, get their semantics honored visually, but not programmatically. The browser respects what they declared, but CSS doesn't.

    If sibling-index() were the answer, the Chromium @property hack wouldn't exist. Authors chose the hack anyway, because it does what sibling-index() can't.

    This would break parallel style computation. @upsuper, 2017:

    1. sibling-index() shipped. Same structural shape, tree-derived integer at style time. Engines parallelized what they could and serialized the rest.
    2. :has() shipped. Twenty years of "this breaks invalidation" turned out to be bounded. Engines built new strategies.
    3. The Chromium @property hack works. Register a custom property as <number>; counter values flow through. If parallelization were broken, neither would the hack.

    Already answered. @kizu's counter-value-scope covers any remaining concern: opt-in to the slower path per counter. Authors who don't use it pay nothing.

    Paged-media counters (page, pages, target-counter()). @faceless2's analysis have real layout-feedback loops. Defer them. Different problem.

    Document-flow counters don't have that shape. Tree + counter rules, resolved before layout. Same dependency direction as sibling-index().

    "Few authors need this"

    This dismissal has been the response for nine years. Yet:

    • Demand (frustration) keeps accumulating.
    • Workarounds have multiplied: :nth-child() chains, JavaScript walkers, data-* mirrors, the @property hack.
    • Implementations have demonstrated the capability.
    • And CSS advocates like myself have little to justify it.

    An author community writing blog posts for an undocumented hack is not a niche. The question is not whether authors need this. The question is how much longer the standard will require us to prove it.

  25. Crissov commented on May 7, 2026

    @Crissov
    Contributor

    See #12488:

    So parse(number counter(...)) would indeed convert a counter value into an integer.

  26. Loirooriol commented on May 8, 2026

    @Loirooriol
    Contributor

    @dragontheory All your links just point to this issue, would be useful to link to the specific comments you are addressing.

  27. dragontheory commented on May 8, 2026

    @dragontheory

    @dragontheory All your links just point to this issue, would be useful to link to the specific comments you are addressing.

    Thank you for reading my comment. Links fixed.

    Now that there is some distance from my conversation with the fellow dev, I see that my comment here could have been less emotional and reactionary. I apologize. Thank you all for doing what you do and enabling native modern CSS to be what it is today. CSS is truly awesome!

  28. Gonzalo-D-Sales commented on Jul 3, 2026

    @Gonzalo-D-Sales

    Hello! I apologize if this is not 100% related, but I have found a genuine use case for enabling counter() to work inside of if() functions.

    I develop themes for an Electron application (Obsidian) where the DOM is generated by JavaScript and cannot be modified. There are containers that visually have no children (all children are display: none) but are not structurally empty, since they contain a wrapper element that itself has no visible descendants. The only way to conditionally style the container right now is by using :has(), which is costly and already frequently used in the application's own stylesheet. A counter-based approach allows me to detect "visually empty" containers without using :has().

    I observed that counter-increment does not increment on an element with display: none. This means a counter scoped to a parent and incremented by its children effectively counts only visible children, which sibling-count() does not since it counts all DOM siblings regardless of their display state. Counters correctly track this information, and they render accurately when called in content. But there is currently no way to access their result outside of content, or crucially, to use the result to branch via if() or @container style queries.

    Now as for implementing such a change, I believe there is a good precedent for it. From my understanding, sibling-count() already returns an integer value for use in calc() and conditional statements. Either a hypothetical counter-value() function, or allowing counter() to resolve to an <integer> when used outside of content, would enable visible-child-aware styling that is intuitive and performant compared to using :has().

    This is what I have attempted so far:

    1. counter-reset: children on a parent container
    2. counter-increment: children on each child element
    3. Storing the result in a variable --count: counter(children)
    4. Styling the parent based on this result if(style(--count: 0): /* hide parent */)

    The first three work, but as expected, I run into a roadblock at the last point. The variable stores the unresolved counter() tokens, not the resolved value. From my limited understanding, the only context where counter() currently resolves to a usable value is when used in content (where it resolves to a string). Therefore, my if() styling does not work.

    There is an additional complication, that being that the updated counter value only shows up on an ::after pseudo-element of the parent and not the parent itself. This means that I cannot access the updated counter() value from the parent element I actually want to style, only from its pseudo-element which appears after all the children element in the DOM.

    What this means is that the only options for conditionally styling the parent, even if I am able to access counter()'s value outside of content, is to use the pseudo-element to hide or cover the parent, or by grabbing the pseudo-element's content using a hypothetical content(after) statement. This has been considered already in the past: "The CSS generated content module introduces six yet-to-be implemented CSS functions including content(), string(), and leader(), and the three [<target>](https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Properties/content#target) functions target-counter(), target-counters(), and target-text()." in https://developer.mozilla.org/en-US/docs/Web/CSS/Guides/Generated_content#functions.

    On one last note, I have tried to read and understand the rationale against implementing these changes, due to the risk of circular dependencies. However, I believe the use case I suggested is simple and isolated enough to mostly sidestep the issue. After all, there is no use of calc() in my use case, and all of the child elements are already display: none so the counter reads 0 on the first layout pass. Hiding the parent element (and thus removing all its children from the rendering tree) based on this result does not change the counter, since the counter already resolved to 0 to begin with.

  29. Loirooriol commented on Jul 3, 2026

    @Loirooriol
    Contributor

    @Gonzalo-D-Sales What you are proposing is inherently circular in general.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions