Variable Order

Look (Im­por­tant! This is just an ex­per­i­ment, made in or­der to find what is pos­si­ble to do at all in CSS, this method is not meant for pro­duc­tion, as it is bad for a11y.) at this table and note that you can click on its head­ers to sort the table by cor­re­spond­ing columns (and click again to change the as­cend­ing/​de­scend­ing or­der).

Conditions for CSS Variables 2016-10-21 10 368
Controlling the Specificity 2016-12-21 2 431
Counters and Stones 2018-01-04 4 463
Flexible Overflow 2017-11-28 6 585
Keyboard-Only Focus 2017-06-27 4 597
Label-to-Input States 2017-05-31 2 829

This is done just with HTML and CSS, no JavaScript in­volved.

But how does this work?

Inline CSS Variables

I was re­search­ing CSS vari­ables (If you'd want to learn more about CSS vari­ables in gen­eral, I highly rec­om­mend you to watch Lea Verou's talk about them, she ar­tic­u­lates re­ally nicely a lot of nu­ances of their us­age.) rather ac­tively for the last 3 months, and ini­tially I wanted to write a long ar­ti­cle about just one as­pect —CSS vari­ables in­side in­line styles. But there were just so many things to write about, and I found a lot of gen­eral use-cases of vari­ables (and not just for in­line ones), that I de­cided to split all my re­search into smaller ar­ticles.

This is an ar­ti­cle about one of the ex­am­ples from my re­search of CSS vari­ables in in­line styles, and as you could see from the ex­am­ple at the start —it is about sort­ing stuff.

The main fea­ture that makes that pos­si­ble is that we can use CSS vari­ables right in­side the style at­tribute in HTML. And while it could be pos­si­ble to de­fine all the val­ues for ap­pro­pri­ate class names or nth-child-found el­e­ments, there is no need to do this when we can just add them to the cor­re­spond­ing HTML tags.

The Sorting

The code (Up­date: one big prob­lem with this method is that it can make UI less ac­ces­si­ble due to re­order­ing only vi­su­ally. Thanks to Thierry Ko­bi­entz and Jen Sim­mons for bring­ing this up.) for our im­ple­men­ta­tion of table sort­ing is not re­ally com­pli­cated.

The only thing that we need to do in HTML (we use a reg­u­lar HTML table for this, which later re-style with flex and grid) is an in­line style for its rows which we sort:

<tr
  class="table-row"
  style="
    --order-by-published: 161221;
    --order-by-views: 2431;
  ">
  <th class="table-cell">
    <a href="http://kizu.ru/en/fun/controlling-the-specificity/">Controlling the Specificity</a>
  </th>
  <td class="table-cell">2016-12-21</td>
  <td class="table-cell">2 431</td>
</tr>

You can see that the val­ues for those vari­ables (You can no­tice that we don't have a vari­able to sort by name —we rely on DOM or­der for this.) there are just the date in YYM­MDD for­mat (Not with YYYY, as in that case Edge would have a bug there, so it seems that we shouldn't use num­bers that big for calc() and or­der.) (so it be­comes an in­te­ger) and the value for view.

For CSS, if I'll omit all the stuff that han­dles its pre­sen­ta­tion and UI for the table, the code be­hind the sort­ing it­self be­comes re­ally small:

.table-body {
  display: flex;
  flex-direction: column;
}

.table-row {
  order: calc(var(--order) * var(--sort-order, -1));
}

#sort-by-published:checked ~ .table > .table-body > .table-row {
  --order: var(--order-by-published);
}

#sort-by-views:checked ~ .table > .table-body > .table-row {
  --order: var(--order-by-views);
}

#sort-ascending:checked + .table {
  --sort-order: 1;
}

#sort-by-name:checked ~ #sort-ascending:checked + .table > .table-body {
  flex-direction: column-reverse;
}

This code cov­ers sort­ing by three pos­si­ble columns, and a global mod­i­fier to in­verse the di­rec­tion of the sort­ing. So, what hap­pens there?

  1. The ob­vi­ous key prop­erty for our so­lu­tion is an or­der. When used in­side flex or grid con­tent, it de­fines the place­ment of an el­e­ment in the flow.

  2. For most of our columns, we would use a spe­cial vari­able that de­fines the di­rec­tion of sort­ing: as­cend­ing or de­scend­ing, this vari­able is --sort-or­der with a de­fault value of -1, which makes el­e­ments with the big­ger value to ap­pear ear­lier in the flow than those with lower val­ues. And when needed we can set it to 1 to in­verse the order.

  3. Then, we would de­fine the or­der prop­erty us­ing a cal­cu­la­tion, in which we would use an­other vari­able along­side our di­rec­tion: calc(var(--or­der) * var(--sort-or­der, -1)).

  4. And by de­fault, this vari­able is not set, so the value of this calc would fall­back to ini­tial. And this would make the con­tent to ap­pear in the or­der it is pre­sent in HTML: this way we don't need to in­tro­duce an­other vari­able and can use the DOM or­der for this.

  5. Then, when needed (in our case —when we tog­gle an ap­pro­pri­ate ra­dio but­ton, but in re­al­ity this could be done by tog­gling class names or set­ting the vari­able by any other con­di­tion), we set the --or­der vari­able used above to the one we want to use right now —so when we need to sort by one field, we use the vari­able for it, and for an­other field we use the sec­ond vari­able. This way if we'd need to add a fourth field which we could use for sort­ing, we would need to in­tro­duce just one new CSS rule.

  6. And now as we say some­thing like “use vari­able --or­der-by-pub­lished as a value for --or­der for each row”, the value would come from the in­line style that we de­fined in HTML and all the el­e­ments would be au­to­mat­i­cally sorted ac­cord­ingly. Yay!

  7. And lastly, we need to han­dle an in­verted di­rec­tion case for our de­fault DOM or­der. As we don't have vari­ables for it which we could in­vert in­side calc, we need to do some­thing else —and I'm do­ing this by chang­ing the flex-di­rec­tion prop­erty to col­umn-re­verse. This works only for flex, and if we would have a grid, as far as I know, we would need to use a vari­able for our de­fault sort­ing as well. But here we can cheat a bit.

Caveats

  • This so­lu­tion works the best for any­thing that can be rep­re­sented as an in­te­ger. In all the other cases we would need to first, on the HTML gen­er­a­tion step, to some­how rep­re­sent our value in in­te­ger. For some val­ues its pos­si­ble to do, for oth­ers —much harder. It is also pos­si­ble to pre-sort our data per each field and in­stead of val­ues, use their in­dexes for each field. But then the sort­ing it­self still could be done just by very sim­ple CSS. And don't for­get that we al­ways have the de­fault DOM or­der for non-in­te­ger val­ues: in my table, it is used for sort­ing the names in al­pha­bet­i­cal order.

  • This so­lu­tion is not re­ally a so­lu­tion, but an ex­per­i­ment, and can be bad for ac­ces­si­bil­ity —right now its not pos­si­ble to re­order stuff in a way it would be ac­ces­si­ble with key­board (with­out con­fus­ing tele­port­ing fo­cus) or screen read­ers. And while for them we can to­tally gate the sort­ing fea­ture out, so it would be an en­hance­ment for those who won't have prob­lems with it, its bet­ter not to do so out­side of ex­per­i­ments, and rely on JS so­lu­tions for sort­ing in­stead.

  • We can't an­i­mate this sort­ing, as it uses the de­fault lay­out mech­a­nism and as long as the or­der is not an­i­mat­able in CSS (or, more pre­cisely, is an­i­mated in a dis­crete way), we can't do much. The ideal would be if the or­der could be an­i­mated by us­ing the tran­si­tion from its start­ing di­men­sions to its fi­nal ones, but we don't even have tran­si­tions for auto width & height, so we can't ex­pect the same for or­der any­time soon (or maybe ever).

Conclusion

I think this ex­am­ple shows how pow­er­ful CSS vari­ables can be when used as a part of data: we can pre-fill some of the val­ues which we won't use right away as some vari­ables, and then later use them when needed. This can be used to sort lists, ta­bles and any other stuff, and even to sort by mul­ti­ple fields (though this would re­quire a bit more code and couldn't be ap­plied in every case, I rec­om­mend you to try and im­ple­ment this your­self, and all of this can be done much bet­ter in JS). There are prob­lems with a11y, so this is not a so­lu­tion for pro­duc­tion, but I re­ally hope in fu­ture we would get a way to re­order things in CSS with­out a11y problems.

This was the first ar­ti­cle from my re­cent re­search of CSS vari­ables, stay tuned and try to sort your stuff us­ing just CSS and HTML in the meantime!