Skip to content

Semantic border tone ladder was incomplete — add medium/strong for danger, info, success, warning #5328

Description

@eddiman

Parent: #4740

Problem

The semantic border.<tone>.<strength> ladder was incomplete and internally inconsistent. Only accent and neutral had all three strengths; the four status tones had subtle alone:

tone subtle medium strong
accent accent.4 accent.7 accent.9
neutral neutral.4 neutral.4 ⚠️ neutral.6
danger / info / success / warning *.4

Two separate defects:

  1. Eight tokens simply missing — no medium/strong for danger, info, success, warning. There was no way to express a mid- or high-emphasis status border.
  2. border.neutral.subtle and border.neutral.medium resolved to the identical colour ({neutral.4}), so of neutral's three named strengths only two were visually distinct.

Root cause

Not a design decision — a migration artifact of the rename in #5280.

Before that PR the semantic layer had no status borders at all, only neutral chrome plus one accent token:

--eds-border-subtle:     var(--eds-neutral-2)
--eds-border-default:    var(--eds-neutral-4)
--eds-border-emphasized: var(--eds-neutral-6)
--eds-border-hover:      var(--eds-neutral-7)
--eds-border-selected:   var(--eds-accent-13)

#5280 restructured these into border.<tone>.<strength>. neutral's subtle/medium/strong is a literal 1:1 rename of border-subtle/-default/-emphasized — three levels existed, so three got names. The four status tones were new in that PR and only got the single level messages/banners needed at the time. Nothing was dropped; the rest was never created.

The duplicate then appeared in #5284, which bumped border.neutral.subtle from {neutral.2} to {neutral.4} — sensible on its own, since ladder steps 1–2 are background planes and read as invisible borders, but medium was already {neutral.4} and wasn't adjusted to compensate.

Resolution

Adopt subtle = step 4 · medium = step 7 · strong = step 9 for all six tones.

That is accent's existing mapping, and the only one that sits on the band already used everywhere else in the semantic set — 4 = interactive.muted.default, 7 = interactive.muted.pressed, 9 = interactive.emphasis.default. Every other slot in semantic uses the same ladder step across all six tones, so border was the lone exception. Neutral's 4/4/6 matched nothing.

Lightness separation from input/scale:

step light L dark L
4 subtle 0.87 0.52
7 medium 0.72 0.61
9 strong 0.52 0.82

Applied in Tokens Studio on branch border-tone-ladder — 8 creates plus 2 value updates, each new token carrying scopes: ["STROKE_COLOR"], hiddenFromPublishing: false and codeSyntax.WEB. Awaiting human merge in the Studio UI.

New CSS custom properties once released:

--eds-border-danger-medium    --eds-border-danger-strong
--eds-border-info-medium      --eds-border-info-strong
--eds-border-success-medium   --eds-border-success-strong
--eds-border-warning-medium   --eds-border-warning-strong

⚠️ Visually breaking

border.neutral.medium and border.neutral.strong change value. In light mode medium goes L 0.87 → 0.72 and strong L 0.77 → 0.52, so any card, divider or input border bound to those two tokens gets noticeably darker. Worth a visual pass on components before this ships.

Verification

Verified by direct GET on the branch (tsctl diff is blind to extensions-only writes):

  • 1270 → 1278 tokens; exactly 8 added, 0 removed
  • exactly 2 pre-existing tokens changed, with no drift in any other token's value, scopes, hiddenFromPublishing or codeSyntax
  • all 57 border.* tokens carry STROKE_COLOR
  • no CSS-variable name collisions on main or branch
  • resolver: 1016 keys, 0 unresolved, 0 errors; light resolves to L 0.87 / 0.72 / 0.52 exactly as input/scale predicts

Dark could not be verified through /resolved_tokens — passing the dark color-scheme theme option returns light values even for a control token that must differ (background.canvas gives oklch(0.98 0 0) under both). No new reference risk regardless: {tone.7} and {tone.9} are already resolved in both schemes by the existing border.interactive.*.muted.pressed and .emphasis.default tokens for every tone.

Follow-ups

  • Merge border-tone-ladder in the Studio UI, then release
  • Visual check of components bound to border.neutral.medium / .strong
  • border.interactive.neutral.muted is 4/7/8 where the other five tones are 4/5/7 — neutral's other outlier from the same rename, not addressed here
  • Document the tone/strength model; the 4/7/9 rule currently exists only implicitly in the values

Metadata

Metadata

Assignees

Labels

Type

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions