Convert CSS counter()
values to integers
without JavaScript.
The limiting feature support required to use this utility is timeline-scope
.
- Converting any
counter()
value into aninteger
for use incalc()
. - Passing integer value of a counter UP to parents (Giving parents their child count as a --var).
- The integer for a counter value can be set from any
counter()
contextually and be scoped all the way up on:root
so the whole site's CSS can mathematically respond to any counter anywhere. - Counter values
0
to120
supported. - Since counters can count globally unlike
:nth-*
selectors, elements can now mathematically respond to their GLOBAL index (<= 120). - Style queries then empower decendants of the
counter value
's scope to respond in any way you can think of, beyond mathematical manipulation. - Need the
counter value
's scope itself to respond in non-mathematical ways? Run a mathematical comparitor then convert the resultingbit
into a Space Toggle using an animation like shown in the demo window here. - No JavaScript involved.
$ npm install css-counter-value
Then include /node_modules/css-counter-value/counter-value.css
From html:
<link rel="stylesheet" type="text/css" href="https://unpkg.com/css-counter-value@2/counter-value.css">
@import url(https://unpkg.com/css-counter-value@2/counter-value.css);
Any element on the page can have a maximum of 8
converted counters host
ed in its scope
, each counter occupying a variable 1
through 8
.
var(--counter-value\\1)
var(--counter-value\\2)
var(--counter-value\\3)
var(--counter-value\\4)
var(--counter-value\\5)
var(--counter-value\\6)
var(--counter-value\\7)
var(--counter-value\\8)
For example, a td
might host
its table-row counter
(1) and its table-col counter
(2).
Additionally, the table
might host
the value of the counters of the last td
, effectively putting two more counters in scope
for the rest of the table
.
This rapidly enables the table (or grid, lists, etc) to know how many rows and columns it has without writing complex selectors to account for every potential count.
In this scenario, there would still 4
additional counters-value
s that could be converted in scope
of any td
on the table
.
If you prefer to dive into live code, check out this codepen.
Given this table, or a similarly structured grid, nested lists, or any other DOM tree (noting the inline styles managing the counter states):
<table>
<thead>...</thead>
<tbody style="counter-reset: table-row 0;">
<tr style="counter-increment: table-row 1; counter-reset: table-col 0;">
<td style="counter-increment: table-col 1;">
Who am I, really?
</td>
<td style="counter-increment: table-col 1;">
Who am I, really?
</td>
</tr>
<tr style="counter-increment: table-row 1; counter-reset: table-col 0;">
<td style="counter-increment: table-col 1;">
Who am I, really?
</td>
<td style="counter-increment: table-col 1;">
Who am I, really?
</td>
</tr>
</tbody>
</table>
To convert a counter into an integer, add a directive
as a descendant of the elment being counted and css-counter-value
's built in CSS interpreter
will assign the output counter-value\\*
vars as directed.
By default, without a matching host
in a directive
's context, the directive
will assign the converted counter-value
to var 1
(of 8
) on its immediate parent
, making the parent element an implicit host for the variable its setting:
<table>
<thead>...</thead>
<tbody style="counter-reset: table-row 0;">
<tr style="counter-increment: table-row 1; counter-reset: table-col 0;">
<td style="counter-increment: table-col 1;">
Who am I, really?
</td>
<td style="counter-increment: table-col 1;">
<output data-counter-value style="--counter: table-col;"></output>
Know Thyself...?
</td>
</tr>
<tr style="counter-increment: table-row 1; counter-reset: table-col 0;">
<td style="counter-increment: table-col 1;">
Who am I, really?
</td>
<td style="counter-increment: table-col 1;">
<output data-counter-value style="--counter: table-col;"></output>
Know Thyself...?
</td>
</tr>
</tbody>
</table>
The two td
elements with the data-counter-value
directive
inside of them now hold a CSS var var(--counter-value\\1)
with an integer value of 2
.
We set the --counter
variable to [the name of the counter]
we wish to convert using the style
attribute inline, but this can be done from your CSS if you prefer.
To add the row number, we must choose a variable slot not already in scope
(other than 1
in this case) and add another directive
to the td
's. To do this, assign the desired number 1
to 8
to the data-counter-value
attribute and set --counter
to the table-row
counter like so:
<td style="counter-increment: table-col 1;">
<output data-counter-value style="--counter: table-col;"></output>
<output data-counter-value="2" style="--counter: table-row;"></output>
Know Thyself.
</td>
The two td
elements now implicity host a second CSS var var(--counter-value\\2)
with an integer value of 1
or 2
depending on which row is looking.
data-counter-value="7"
would mean "set var(--counter-value\\7)
's value to [whatever counter name's value is specified]"
If we want our table
to know how many rows
and cols
it contains, we add a explicit host
directive attribute values
to the table
and, in the final td
, two directives
that set those values based on the counter()
states in their td
element's scope
:
<table data-counter-value="host:3 host:4">
...
<tbody style="counter-reset: table-row 0;">
...
<tr style="counter-increment: table-row 1; counter-reset: table-col 0;">
..
<td style="counter-increment: table-col 1;">
<output data-counter-value style="--counter: table-col;"></output>
<output data-counter-value="2" style="--counter: table-row;"></output>
<output data-counter-value="3" style="--counter: table-col;"></output>
<output data-counter-value="4" style="--counter: table-row;"></output>
Know Thyself.
</td>
Assuming this is the last td
in the table
, the table
and every element inside of it will have access to var(--counter-value\\3)
(which contains and integer count of the total number of columns in the last row) and var(--counter-value\\4)
(which contains the integer total count of rows in the table).
Any of the 8
counter-value variables can be hosted on any ancestor, even :root
, and be set from any counter context within the host
element.
Note
Even though the table-row
counter itself is stored on the tbody
, the whole tabel
has access to its integer counter value now.
Every td
can use both of these total count vars without adding the additional directives
because the --counter-value\\3
and --counter-value\\4
vars are inherited from table
.
Caution
Any hosting element (matching data-counter-value*="host:"
) must not host
the same variable as an ancestor.
That is, for any given element's scope, it must not have more than one ancestor hosting the same counter-value variable 1
to 8
.
Caution
Within any given host
element's context (descendants), there must not be more than one directive
trying to set the same counter-value
variable.
Tip
If your counters for a specific counter-value
variable 1
to 8
are all 0
values, the most likely scenario is a violation of either of the previous two rules.
Any number of sibling elements can host all 8
counter-value
variables for their own contexts as long as they don't have ancestors or descendants also trying to host one of the 8
variables.
For example, these three scenarios all break for the same reason:
<div data-counter-value="host:1 host:2 host:3">
<output data-counter-value="2" style="--counter: global-div-counter;"></output>
1
<div>
<output data-counter-value style="--counter: global-div-counter;"></output>
<!-- note this ^ is setting var 1, not implicitly on the parent, but explicitly to the ancestor hosting var 1 since it exists -->
2
</div>
<div>
<output data-counter-value style="--counter: global-div-counter;"></output>
<!-- note this ^ is setting var 1 to the same host, which is a conflict -->
3
</div>
</div>
and, very similar:
<div data-counter-value="host:1 host:2 host:3">
<output data-counter-value style="--counter: global-div-counter;"></output>
<!-- note this ^ is setting var 1 to its parent because the parent explicitly hosts var 1 -->
1
<div>
<output data-counter-value style="--counter: global-div-counter;"></output>
<!-- note this ^ is setting var 1 to the ancestor hosting var 1 since one exists -->
2
</div>
</div>
and, the tricky one:
<div data-counter-value="host:2 host:3">
<output data-counter-value style="--counter: global-div-counter;"></output>
<!-- note this ^ is setting var 1 to its parent implicitly -->
1
<div>
<output data-counter-value style="--counter: global-div-counter;"></output>
<!-- note this ^ is going to try setting var1 implicitly to its parent but will fail because an ancestor is implicity already hosting it -->
2
</div>
</div>
The following example is completely acceptable - there's a wide range of wild, exciting behavior still possible. We can have all sorts of fun within the established boundaries of this relationship, if you want π
<html data-counter-value="host:8">
...
<body style="counter-reset: global-div-counter: 0;">
<style>div { counter-increment: global-div-counter 1; }</style>
<div data-counter-value="host:3">
<output data-counter-value="2" style="--counter: global-div-counter;"></output>
<!-- var 2, implicitly on parent -->
1
<div>
<output data-counter-value style="--counter: global-div-counter;"></output>
<!-- var 1, implicitly on parent -->
2
</div>
<div>
<output data-counter-value style="--counter: global-div-counter;"></output>
<!-- var 1, implicitly on parent -->
<output data-counter-value="3" style="--counter: global-div-counter;"></output>
<!-- var 3, explicitly on an ancestor -->
3
</div>
</div>
<div data-counter-value="host:3">
<output data-counter-value="2" style="--counter: global-div-counter;"></output>
<!-- var 2, implicitly on parent -->
4
<div>
<output data-counter-value style="--counter: global-div-counter;"></output>
<!-- var 1, implicitly on parent -->
5
</div>
<div>
<output data-counter-value style="--counter: global-div-counter;"></output>
<!-- var 1, implicitly on parent -->
<output data-counter-value="3" style="--counter: global-div-counter;"></output>
<!-- var 3, explicitly on an ancestor -->
6
</div>
</div>
<div data-counter-value="host:3">
<output data-counter-value="2" style="--counter: global-div-counter;"></output>
<!-- var 2, implicitly on parent -->
7
<div>
<output data-counter-value style="--counter: global-div-counter;"></output>
<!-- var 1, implicitly on parent -->
8
</div>
<div>
<output data-counter-value style="--counter: global-div-counter;"></output>
<!-- var 1, implicitly on parent -->
9
</div>
<div>
<output data-counter-value style="--counter: global-div-counter;"></output>
<!-- var 1, implicitly on parent -->
10
</div>
<div>
<output data-counter-value style="--counter: global-div-counter;"></output>
<!-- var 1, implicitly on parent -->
<output data-counter-value="3" style="--counter: global-div-counter;"></output>
<!-- var 3, explicitly on an ancestor -->
<output data-counter-value="8" style="--counter: global-div-counter;"></output>
<!-- var 8, explicitly on :root, giving the whole document access to a variable var(--counter-value\\8) === 11 -->
11
</div>
</div>
Oh, and if you're setting multiple counter-value
variables with the same counter source, you can just use one directive
instead.
<div>
<output data-counter-value style="--counter: global-div-counter;"></output>
<!-- var 1, implicitly on parent -->
<output data-counter-value="3" style="--counter: global-div-counter;"></output>
<!-- var 3, explicitly on an ancestor -->
<output data-counter-value="8" style="--counter: global-div-counter;"></output>
<!-- var 8, explicitly on :root, giving the whole document access to a variable var(--counter-value\\8) === 11 -->
11
</div>
becomes
<div>
<output data-counter-value="1 3 8" style="--counter: global-div-counter;"></output>
<!-- var 1, implicitly on parent -->
<!-- var 3, explicitly on an ancestor -->
<!-- var 8, explicitly on :root, giving the whole document access to a variable var(--counter-value\\8) === 11 -->
11
</div>
Have fun!
Please do reach out if you need help with any of this, have feature requests, want to share what you've created, or wish to learn more.
PropJockey.io | CodePen | DEV Blog | GitHub | Mastodon |
---|---|---|---|---|