Tabs: redraw underline tabs; stop Button's hover masking the selected segment
The underline tab inherited Button's 6px radius, so its 2px bottom border
curled up at the ends, and Button's quiet hover tint leaked onto every tab.
On segmented tabs that tint was the same colour as the track, so a clicked
segment didn't look selected until the pointer left it.
Underline tabs now pad and hover like a quiet Button, and the selected one
is marked by a short, flat accent bar drawn on the hairline as a
pseudo-element. Button's quiet hover rule skips [role="tab"].
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01G1ZNNE7MSJecXRqkEApUJp
diff --git a/_components/tabs.md b/_components/tabs.md
index eee397d..e256e87 100644
--- a/_components/tabs.md
+++ b/_components/tabs.md
@@ -16,8 +16,9 @@ nothing else.
## Underline
-Mono, with an accent underline. For primary section navigation — a project's
-own tabs, never a filter. This is the default; no `data-variant` needed.
+Mono labels over a hairline; the selected one is marked by a short accent bar
+as wide as its label, and hovering picks up the same flat tint as a quiet
+Button. For primary section navigation — a project's own tabs, never a filter. This is the default; no `data-variant` needed.
<figure class="example">
<figcaption>Underline</figcaption>
diff --git a/_sass/components/button.css b/_sass/components/button.css
index 374eadb..02254a5 100644
--- a/_sass/components/button.css
+++ b/_sass/components/button.css
@@ -52,8 +52,11 @@ a.btn {
}
/* Quiet variants (default, ghost) carry no fill at rest; hovering picks up
- a flat tint of the stock instead of darkening a border that isn't there. */
-:is(button:not(.icon-button), a.btn):not([data-variant="primary"]):not([data-variant="dark"]):not([data-variant="danger"]):hover:not(:disabled):not([aria-disabled="true"]) {
+ a flat tint of the stock instead of darkening a border that isn't there.
+ Tabs are <button>s too but own their hover (tabs.css): this rule's
+ specificity would otherwise paint the selected segment the colour of its
+ own track until the pointer left it. */
+:is(button:not(.icon-button), a.btn):not([role="tab"]):not([data-variant="primary"]):not([data-variant="dark"]):not([data-variant="danger"]):hover:not(:disabled):not([aria-disabled="true"]) {
background: var(--gl-color-surface-fill);
color: var(--gl-color-text-primary);
}
diff --git a/_sass/components/tabs.css b/_sass/components/tabs.css
index 8636c30..54abecc 100644
--- a/_sass/components/tabs.css
+++ b/_sass/components/tabs.css
@@ -6,10 +6,10 @@
shape. The keyboard wiring lives in assets/js/system.js and is
variant-agnostic — it only cares about `[data-tabs]` / `[role=tab]`.
- Two real variants: `underline` (mono, accent underline — primary section
- nav, the default) and `segmented` (pill track, active segment lifts to the
- surface — filters like All · Overdue · Upcoming, never primary
- navigation), chosen with `data-variant="segmented"` on the tablist since
+ Two real variants: `underline` (mono labels over a hairline, the selected
+ one marked by a short accent bar — primary section nav, the default) and
+ `segmented` (pill track, active segment lifts to the surface — filters
+ like All · Overdue · Upcoming, never primary navigation), chosen with `data-variant="segmented"` on the tablist since
neither has a natural attribute of its own. */
/* Every rule below the list container pairs [role="tablist"] with
@@ -20,26 +20,52 @@
[role="tablist"] {
display: flex;
- gap: var(--gl-space-m);
- border-bottom: 1px solid var(--gl-color-border-hairline);
+ gap: var(--gl-space-3xs);
+ border-bottom: var(--gl-border-w) solid var(--gl-color-border-hairline);
}
-[role="tablist"] [role="tab"] {
- padding: 0.5rem 0;
- margin-bottom: -1px;
+
+/* Underline. Each tab is a quiet control sitting just above the hairline —
+ same mono label and flat hover tint as a quiet Button — and the selected
+ one is marked by a short accent bar drawn on the hairline itself, as wide
+ as the label rather than the hit area. The bar is a pseudo-element, not a
+ border, so it lies flat instead of inheriting the button's radius and
+ curling up at the ends. Scoped away from segmented, which shares the
+ element but none of this shape. */
+[role="tablist"]:not([data-variant="segmented"]) [role="tab"] {
+ position: relative;
+ padding: var(--gl-space-3xs) var(--gl-space-2xs);
+ margin-bottom: var(--gl-space-3xs);
background: none;
border: 0;
- border-bottom: 2px solid transparent;
+ border-radius: var(--gl-radius-sm);
color: var(--gl-color-text-secondary);
font-family: var(--gl-font-mono);
font-size: var(--gl-step--1);
- letter-spacing: 0.02em;
+ font-weight: 500;
+ letter-spacing: 0.01em;
+ line-height: 1.15;
cursor: pointer;
- transition: color var(--gl-dur-fast) var(--gl-ease), border-color var(--gl-dur-fast) var(--gl-ease);
+ transition: background-color var(--gl-dur-fast) var(--gl-ease), color var(--gl-dur-fast) var(--gl-ease);
+}
+[role="tablist"]:not([data-variant="segmented"]) [role="tab"]::after {
+ content: "";
+ position: absolute;
+ inset-inline: var(--gl-space-2xs);
+ bottom: calc(-1 * (var(--gl-space-3xs) + var(--gl-border-w)));
+ height: 2px;
+ border-radius: var(--gl-radius-full);
+ background: transparent;
+ transition: background-color var(--gl-dur-fast) var(--gl-ease);
}
-[role="tablist"] [role="tab"]:hover { color: var(--gl-color-text-primary); }
-[role="tablist"] [role="tab"][aria-selected="true"] {
+[role="tablist"]:not([data-variant="segmented"]) [role="tab"]:hover {
+ background: var(--gl-color-surface-fill);
+ color: var(--gl-color-text-primary);
+}
+[role="tablist"]:not([data-variant="segmented"]) [role="tab"][aria-selected="true"] {
color: var(--gl-color-text-primary);
- border-bottom-color: var(--gl-color-accent);
+}
+[role="tablist"]:not([data-variant="segmented"]) [role="tab"][aria-selected="true"]::after {
+ background: var(--gl-color-accent);
}
[role="tablist"][data-variant="segmented"] {
@@ -61,6 +87,7 @@
color: var(--gl-color-text-secondary);
transition: background-color var(--gl-dur-fast) var(--gl-ease), color var(--gl-dur-fast) var(--gl-ease);
}
+[role="tablist"][data-variant="segmented"] [role="tab"]:hover { color: var(--gl-color-text-primary); }
[role="tablist"][data-variant="segmented"] [role="tab"][aria-selected="true"] {
color: var(--gl-color-text-primary);
background: var(--gl-color-surface-card);