Skip to content

[design] The atomic layer under #187: 273 buttons in 14 shapes, 3 card geometries, 17 font sizes #366

Description

@serge-ivo

The console has design-system vocabulary but no design system

Tokens exist and are mostly used — bg-panel, border-line, text-muted, text-accent are
everywhere, and that is the part that works. What does not exist is any layer between those tokens
and the JSX. Every control is assembled by hand, every time.

Measured across store/console/src:

count
Hand-authored control class strings (padding + radius) 291
Distinct padding/radius combinations for those controls 14+
Hand-written "card" divs ~59
Distinct card geometries 3
Arbitrary font sizes (text-[…rem]) 168 uses, 17 distinct values
Shared Button / Card / Badge / Input primitives 0

What that produces

Buttons. px-3 py-1.5 rounded-lg appears 62 times; px-2.5 py-1.5 rounded-lg appears 23 times.
Those differ by 2px of horizontal padding and are otherwise the same control. Nobody chose that
difference 23 times — it was copied from whichever neighbour was nearest. Add px-3 py-2, px-4 py-2, px-2 py-1, px-2.5 py-1, px-5 py-2.5, each in rounded-lg / rounded-xl / rounded-md
/ rounded-full variants, and there are 14+ shapes expressing what is really three sizes.

Cards. The same panel is written as rounded-xl p-3 sm:p-4 (26×), rounded-xl p-4 (15×), and
rounded-lg p-3 (10×). So a card's corner radius and whether its padding responds to viewport
depend on which file you are in.

Type. The design system says, verbatim: "Use Tailwind defaults… No custom font-size
values.
"
There are 168, across 17 values. Nine of them — 0.65 · 0.66 · 0.68 · 0.70 · 0.72 · 0.74 · 0.75 · 0.76 · 0.78 — sit inside a 2px band. text-[0.68rem] vs text-[0.7rem] is a 0.32px
difference at a 16px root: not a design decision, an artefact of copying.

It also produces text that is too small to read. text-[0.7rem] is 11.2px and is used 95 times,
including for descriptive body copy under form controls.

The fix is a component layer, not a lint rule

A lint that bans text-[…] would relocate the problem into 168 mechanical edits. The reason the
values proliferate is that there is nowhere to put a decision once.

  1. Button — variants (primary · secondary · ghost · danger) × sizes (sm · md),
    which covers essentially all 291 sites. The chip local const already invented in
    TeamworkSection.tsx:228 and again in TriggersSection is this component, discovered twice and
    shared zero times.
  2. Card — one geometry, with the responsive padding as the default rather than one of three.
  3. Badge/Chip — for the status pills currently written inline with rounded-full and
    text-[0.62rem].
  4. A real type ramp. Three or four named steps mapped onto Tailwind defaults, and the 17
    arbitrary values collapsed into them. Nothing below text-xs (12px) for anything a user reads
    as prose.

Migration is mechanical and safe to do incrementally — a new Card can wrap existing children with
no behaviour change, and each converted call site deletes a string rather than adding one.

Why now

SettingsTab.tsx (1,154 lines) and InstanceDetail.tsx (1,275) are both on #305's split list, and
#365 restructures the chat composer. Both will be far cheaper against primitives than against 291
bespoke strings — and doing them first bakes the strings in.

Related: #305 (large-file split), #365 (composer restructure), #367 (design-system conformance).

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or requestfrontendFrontend / UI work

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions