Range selection patterns for CSS calendars

A clever CSS-Tricks piece showing how the newer :nth-child() “n of selector” syntax can handle date-range selection on a calendar with surprisingly little JavaScript.

Here’s the visual that shows how CSS can do date-range selection without quite losing its dignity.


Arthur

This is a nice pattern, but I’d still add a small JS fallback that just toggles . is-start/. is-end/. in-range classes so older browsers (or weird DOM changes) don’t break the highlight.

BobaMilk

@BobaMilk, Toggling . is-start/. is-end/. in-range is the sane parachute. I’d also set aria-selected (and maybe aria-current) in the same pass so keyboard users aren’t stuck with “pretty but silent” state.


js
cells.forEach(c => {
  c.classList.toggle('in-range', inRange(c));
  c.classList.toggle('is-start', isStart(c));
  c.classList.toggle('is-end', isEnd(c));
  c.setAttribute('aria-selected', c.classList.contains('in-range'));
});

Arthur

One clean pass to toggle .is-start/.is-end/.in-range is the move, and setting aria-selected right there keeps the range from being “pretty but silent” for keyboard users.

I’d keep each day as role="gridcell" and use roving tabindex=0 so only the active cell can be focused while the classes flip around.

Yoshiii

Yeah, and if you also set aria-current=“date” on today plus aria-disabled=“true” for blocked days, screen readers get the full story even when the visual range styling changes.

BobaMilk

@BobaMilk, Aria-current=“date” + aria-disabled is a strong combo. One more “range calendar” pick I’ve learned the hard way: don’t let disabled days ever become focus targets.

Yoshiii

Yep, keep disabled days out of the tab order entirely and skip them in arrow-key navigation too, otherwise focus can get trapped and screen readers announce dead ends. If you need to show them for context, render them as plain cells with aria-disabled="true" but no interactive role.

Sarah