Skip to content

Soften the tab label hover colour so it is not the active colour - #303

Merged
chrisjsewell merged 7 commits into
executablebooks:mainfrom
dchaudhari7177:fix/tabs-label-hover-not-active-colour
Nov 9, 2026
Merged

chrisjsewell merged 7 commits into
executablebooks:mainfrom
dchaudhari7177:fix/tabs-label-hover-not-active-colour

Conversation

@dchaudhari7177

Copy link
Copy Markdown
Contributor

Closes #301.

Confirmed at 4f66f32

style/design.toml lines 29-30 carried the same literal:

["--sd-color-tabs-label-active", "hsla(231, 99%, 66%, 1)"],
["--sd-color-tabs-label-hover",  "hsla(231, 99%, 66%, 1)"],

and style/tabs.css:28 applies the hover token to the unselected label:

.sd-tab-set > input:not(:checked) + label:hover {
  color: var(--sd-color-tabs-label-hover);
  border-color: var(--sd-color-tabs-underline-hover);
}

So a hovered unselected tab rendered in exactly the selected tab's colour, leaving underline saturation as the only remaining cue — which is the "two active tabs" reading described in the issue.

The suggested 0.62 ratio does not transfer to the label

The issue proposes muting the label with the same ratio the hover underline uses. I measured it before adopting it, and it inverts the problem:

over white
label-inactive hsl(0, 0%, 66%) 2.38:1
hsla(231,99%,66%,0.62) → rgb(148,164,254) 2.33:1 ← the suggested value

Those are the same colour to the eye — 1.02:1 between them. It would swap the collision with active for a collision with inactive, and lose the hover affordance completely. Alpha also composites against whatever is behind the label, so it behaves differently on a dark background, whereas the underline can afford that because it sits on a known surface.

What this uses instead

A solid lighter form of the active hue, hsla(231, 99%, 72%, 1), positioned between the two neighbours it must stay distinct from:

token value vs white
--sd-color-tabs-label-inactive hsl(0, 0%, 66%) 2.38:1
--sd-color-tabs-label-hover hsla(231, 99%, 72%, 1) 3.21:1
--sd-color-tabs-label-active hsla(231, 99%, 66%, 1) 4.26:1

It is near-equidistant from both (1.35:1 against inactive, 1.33:1 against active), so hover is unmistakably an affordance and unmistakably not selected.

Generated CSS

Regenerated with python tools/generate_css.py. The diff in sphinx-design.min.css is a single character-level change — 66 → 72 in the tabs-label-hover value — which I verified with a difflib.SequenceMatcher opcode dump rather than eyeballing the minified line.

Full suite: 173 passed, 100 skipped. (tests/test_snippets.py needs pytest-regressions; without it those cases error on a missing file_regression fixture on a clean tree too.)

One thing worth your call

design.toml is a flat name/value list, so this is a static literal. The semantic colours already have a nicer story — the generator emits a color-mix(in srgb, black 15%, var(--sd-color-<name>)) line after the static value so downstream overrides propagate to the hover shade. A theme that overrides --sd-color-tabs-label-active with its brand colour will not get a matching hover here; it still has to override -label-hover too, exactly as today.

Deriving it as color-mix(in srgb, var(--sd-color-tabs-label-active) ~70%, var(--sd-color-tabs-label-inactive)) would fix that class of problem for every theme at once, but it needs the generator to emit a second declaration for a non-[[colors]] token, which felt like a bigger change than this bug warrants. Happy to do it in this PR if you'd prefer that shape.

dchaudhari7177 and others added 7 commits August 7, 2026 10:49
style/design.toml gave --sd-color-tabs-label-hover the identical literal to
--sd-color-tabs-label-active, while the hover underline was already softened
(--sd-color-tabs-underline-hover, 0.62 alpha). tabs.css:28 applies the hover token
to input:not(:checked) + label:hover, so hovering an unselected tab rendered its
label in exactly the selected tab's colour and underline saturation became the only
cue to which tab was open. On a site theming these tokens with a strong brand
colour that reads as two active tabs.

The issue suggests reusing the underline's 0.62 ratio on the label. Measured, that
does not work: hsla(231,99%,66%,0.62) composites over white to rgb(148,164,254),
2.33:1, which is indistinguishable from --sd-color-tabs-label-inactive at 2.38:1
(1.02:1 between them). It would trade one collision for the opposite one and lose
the hover affordance entirely. Alpha also composites against whatever sits behind
the label, so it behaves differently on a dark background.

Used a solid lighter form of the active hue instead, hsla(231, 99%, 72%, 1), which
lands between the two neighbours it has to stay distinct from - contrast against
white:

  label-inactive  hsl(0, 0%, 66%)        2.38:1
  label-hover     hsla(231,99%,72%,1)    3.21:1   (1.35:1 vs inactive, 1.33:1 vs active)
  label-active    hsla(231,99%,66%,1)    4.26:1

Near-equidistant from both, so hover is clearly an affordance and clearly not
selected.

Regenerated sphinx-design.min.css with tools/generate_css.py; the generated diff is
a single character-level change, '66' -> '72' in the tabs-label-hover value.

Full suite: 173 passed, 100 skipped.

Closes executablebooks#301
Set --sd-color-tabs-label-hover to
color-mix(in srgb, var(--sd-color-tabs-label-active) 80%, transparent),
after a static hsla(231, 99%, 66%, 0.8) fallback (the same colour) for
engines without color-mix(). A theme that overrides
--sd-color-tabs-label-active at :root now gets a matching hover colour,
instead of having to override the hover token as well.

Regenerated sphinx-design.min.css with tools/generate_css.py, and
updated the defaults listed in docs/css_variables.md.
Fails on the old tokens (hover == active), passes with the color-mix() hover token.
A custom property accepts color-mix() at parse time, so the later
color-mix() declaration always wins and the static value never applies
on its own. Say it is kept alongside, as the -highlight tokens are.
@chrisjsewell
chrisjsewell merged commit 54cd807 into executablebooks:main Nov 9, 2026
25 checks passed
chrisjsewell added a commit to kdeldycke/sphinx-design that referenced this pull request Nov 9, 2026
The only conflict was the generated sphinx_design/static/sphinx-design.min.css
(executablebooks#303 changed the tab hover rules); it is rebuilt by tools/generate_css.py
rather than hand-merged.
chrisjsewell added a commit to dipinknair/sphinx-design that referenced this pull request Nov 9, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

tabs: hovering an unselected tab shows the full active label colour

2 participants