Dynamic Sorting in Pure CSS

I've talked before about how it's possible to filter a set of elements, in response to user requests, using only HTML + CSS. But did you know that you can also sort a set of elements. And it's pretty easy!

Sortable animals

Here are some animals for you -

Sort animals by:

  • ๐Ÿ Goat IntelligenceNot so smart Aggression6 Speed6 Strength6 Size7 Beauty4
  • ๐Ÿฆ‹ Butterfly IntelligenceStoopid Aggression2 SpeedSlow Strength1 Size2 Beauty14
  • ๐Ÿ• Dog IntelligenceSuperb Aggression7 SpeedPretty fast Strength10 Size6 Beauty8
  • ๐Ÿข Tortoise IntelligenceStoopid Aggression1 SpeedIncredibly slow Strength5 Size4 Beauty4
  • ๐Ÿ„ Cow IntelligenceOkay Aggression3 SpeedA bit slow Strength10 Size13 Beauty4
  • ๐Ÿ‘ Sheep IntelligenceNot so smart Aggression2 Speed6 Strength6 Size7 Beauty4
  • ๐Ÿˆ Cat IntelligenceSuperb Aggression7 SpeedModerately fast Strength5 Size5 Beauty8
  • ๐ŸŽ Horse IntelligenceGreat Aggression8 SpeedSuper fast Strength11 Size15 Beauty13
  • ๐Ÿ Snake IntelligenceGood Aggression10 SpeedPretty fast Strength14 Size5 Beauty4

How it works

This example leverages two CSS technologies: variables, and the order: property. The list of animals gets display: flex; (display: grid; would also work), and each animal gets n integer representation of each of its attributes as CSS variables, e.g.:

<li style="--int: 5; --agg: 6; --spd: 6; --str: 6; --siz: 7; --bty: 4;">
  <span>๐Ÿ</span>
  <span>Goat</span>
  <!-- other stuff to display about goats -->
</li>
<li style="--int: 3; --agg: 2; --spd: 3; --str: 1; --siz: 2; --bty: 14;">
  <span>๐Ÿฆ‹</span>
  <span>Butterfly</span>
  <!-- other stuff to display about butterflies -->
</li>
<!-- other animals... -->

Then, we can use the parent:has(select option[value="..."]:checked) pattern to set the order: of each animal based on whether or not a parent container (e.g. the <body>) has an appropriate <select>'s <option> chosen. That might look like this, for example:

/* If the <... class="sortable-wrapper"> contains a <select id="my-select">
 * whose <option value="int"> is selected, then any <li>s within the
 * <... class="sortable-wrapper">... */
.sortable-wrapper:has(#my-select option[value="int"]:checked) li {
  /* ...are ordered by their --int: variable. */
  order: var(--int);
}

/* If the <... class="sortable-wrapper"> contains a <select id="my-select">
 * whose <option value="agg"> is selected, then any <li>s within the
 * <... class="sortable-wrapper">... */
.sortable-wrapper:has(#my-select option[value="agg"]:checked) li {
  /* ...are ordered by their --agg: variable. */
  order: var(--agg);
}

If any two items share the same variable value, the "tie-breaker" is their order within the HTML source code. If any items lack an value for a sort, they're treated as if they had order: 0;. This could be used for a "default" sort that puts the animals (or whatever) into alphabetical order, for example.

The direction checkbox uses the same :has(...:checked) pattern, but swaps the flex-direction: column to flex-direction: column-reverse. Simple enough!

The whole thing works entirely without JavaScript, making it suitable for environments where JavaScript might not be available... or just for performance and resilience. Even without CSS support available, it degrades gracefully into a static list, incapable of being sorted. And it can be combined with the filtering I wrote about earlier to provide a rich filter-and-sort interface, although for complex datasets you might like to use a static site generator or other compilation process to generate the HTML code.

Reactions

No time to comment? Send an emoji with just one click!

0 comments

    Leave a comment

    Reply on your own site