← All posts
Postmortems
1 min read

A selector that flattened the activity grid

The activity grid lost its colours because a two-part selector outranked the level classes. A short tour of CSS specificity.


The activity grid is the heart of PaceStreak’s look: a year of small squares, each coloured by how much happened that day. One day every square turned the same colour.

The two rules

The grid cells are <i> elements inside a .heat container. Each cell gets a level class, .lvl--0 to .lvl--4, that paints its colour:

.lvl--3 {
  background: var(--heat-3);
}

Someone (reasonably) wanted a default colour for cells, and wrote:

.heat i {
  background: var(--heat-0);
}

Why the default won

Specificity is compared as a tuple: IDs, then classes, then elements.

  • .lvl--3 is one class: 0-1-0.
  • .heat i is one class and one element: 0-1-1.

0-1-1 beats 0-1-0, whatever order the rules appear in. So the “default” overrode every level, on every cell, all the time.

The fix

.heat i now sets no background at all, deliberately, with a comment saying why. Cells are painted only through the level class, and .lvl--0 is the empty state. There is one way to colour a cell, so there is nothing to conflict.

The general lesson

When a descendant selector and a single class both want to set the same property, the descendant selector will usually win, and it will win silently. Either give one selector the job exclusively, or make the specificity deliberate. Do not rely on source order to break a tie that is not a tie.

AlzyWelzy

Building PaceStreak. GitHub

Related

  • Design

    "Meh" is not a component bug

    Fixing the same-size card grids made the site more correct and no less forgettable. The actual complaint was about the whole page, not a pattern - which meant the fix had to be a hero, motion and rhythm pass, not another round of component surgery.

    2 min
  • Design

    The same-size card grid is the lazy container

    'How it works' and 'Principles' were both an icon-heading-text grid, identically sized, in the exact way any landing page's craft floor flags as the default nobody actually chose. Replaced with two compositions that carry real information in their shape.

    2 min
  • Design

    Drawing a marker X in two strokes of SVG

    The signature interaction of the redesign is a hand-drawn X that draws itself when you tick a day. It's two SVG paths, one CSS keyframe and a pathLength trick, with no JavaScript animation at all.

    2 min

Build the habit. Keep the week.

PaceStreak is a habit and streak tracker for training and everything else worth doing regularly.