Frontend design-kit consistency: dead Card, duplicated segmented-control + row/icon recipes, focusRing/useLang drift #66

Closed
opened 2026-06-08 13:42:17 +00:00 by logaritmisk · 1 comment
Owner

Severity: Low–Medium. From a frontend deep audit, 2026-06-08. check:colors passes and the app is hex-free + dark-mode-clean; these are the subtler consistency issues the guard doesn't catch.

Problems

  • [Med] Dead Card component. components/ui/card.tsx has zero importers (app/tests/stories) while the app hand-builds card-like surfaces. Rots and misleads.
  • [Med] Segmented-control / active-pill recipe duplicated 3× with 3 paddingsobjects-table.tsx:173 (px-2 py-1, no focusRing), search-panel.tsx:76 (px-2 py-0.5, focusRing), authorities-page.tsx:75 (px-3 py-1 text-sm, focusRing). (The missing focus ring is tracked as an a11y defect in the a11y bundle issue.)
  • [Med] lang derivation i18n.language.startsWith("sv") ? "sv" : "en" duplicated 6×object-detail.tsx:59, field-input.tsx:32, vocabulary-terms.tsx:24, vocabulary-list.tsx:16, field-list.tsx:27, authorities-page.tsx:28.
  • [Low] focusRing constant re-inlined as a raw string in shell/sidebar.tsx:46,88 (every other call site imports lib/focus-ring.ts).
  • [Low] "Selected/active row" recipe (bg-primary/10 / hover:bg-muted) duplicated 4×objects-table.tsx:257, vocabulary-list.tsx:120, search-result-row.tsx:15, field-list.tsx:86 (last omits the hover fallback).
  • [Low] "Icon dismiss button" recipe hand-rolled at objects-page.tsx:54 + object-detail-drawer.tsx:33 instead of Button variant="ghost" size="icon-sm".
  • [Low] login-page.tsx:49 raw <h1> instead of <PageTitle> (also drops tracking-tight); type-tag pill at field-list.tsx:97 should be <Badge variant="secondary">; mixed h-4 w-4 vs size-4 icon sizing (user-menu, theme-switch, header-search); inconsistent form space-y-* density scale.

Suggested fix

Extract a SegmentedControl/ToggleGroup (or a Button variant) into components/ui/; add a useLang() hook; import the focusRing constant in sidebar; a rowStateClasses(isActive) helper for selected rows; adopt the kit's icon button + PageTitle + Badge; standardize on size-4 and one form-density scale. Resolve Card (adopt for detail/list surfaces or delete).

Source: frontend deep audit (design-system dimension), 2026-06-08.

**Severity: Low–Medium.** _From a frontend deep audit, 2026-06-08. `check:colors` passes and the app is hex-free + dark-mode-clean; these are the subtler consistency issues the guard doesn't catch._ ## Problems - **[Med] Dead `Card` component.** `components/ui/card.tsx` has zero importers (app/tests/stories) while the app hand-builds card-like surfaces. Rots and misleads. - **[Med] Segmented-control / active-pill recipe duplicated 3× with 3 paddings** — `objects-table.tsx:173` (`px-2 py-1`, **no focusRing**), `search-panel.tsx:76` (`px-2 py-0.5`, focusRing), `authorities-page.tsx:75` (`px-3 py-1 text-sm`, focusRing). (The missing focus ring is tracked as an a11y defect in the a11y bundle issue.) - **[Med] `lang` derivation `i18n.language.startsWith("sv") ? "sv" : "en"` duplicated 6×** — `object-detail.tsx:59`, `field-input.tsx:32`, `vocabulary-terms.tsx:24`, `vocabulary-list.tsx:16`, `field-list.tsx:27`, `authorities-page.tsx:28`. - **[Low] `focusRing` constant re-inlined as a raw string** in `shell/sidebar.tsx:46,88` (every other call site imports `lib/focus-ring.ts`). - **[Low] "Selected/active row" recipe (`bg-primary/10` / `hover:bg-muted`) duplicated 4×** — `objects-table.tsx:257`, `vocabulary-list.tsx:120`, `search-result-row.tsx:15`, `field-list.tsx:86` (last omits the hover fallback). - **[Low] "Icon dismiss button" recipe hand-rolled** at `objects-page.tsx:54` + `object-detail-drawer.tsx:33` instead of `Button variant="ghost" size="icon-sm"`. - **[Low] `login-page.tsx:49` raw `<h1>` instead of `<PageTitle>`** (also drops `tracking-tight`); type-tag pill at `field-list.tsx:97` should be `<Badge variant="secondary">`; mixed `h-4 w-4` vs `size-4` icon sizing (`user-menu`, `theme-switch`, `header-search`); inconsistent form `space-y-*` density scale. ## Suggested fix Extract a `SegmentedControl`/`ToggleGroup` (or a `Button` variant) into `components/ui/`; add a `useLang()` hook; import the `focusRing` constant in sidebar; a `rowStateClasses(isActive)` helper for selected rows; adopt the kit's icon button + `PageTitle` + `Badge`; standardize on `size-4` and one form-density scale. Resolve `Card` (adopt for detail/list surfaces or delete). _Source: frontend deep audit (design-system dimension), 2026-06-08._
Author
Owner

Done in merge 7cabebc.

Shared helpers (stop future drift):

  • lib/use-lang.tsuseLang(): "sv" | "en" replaces the inline i18n.language.startsWith("sv") derivation in 6 components.
  • lib/class-recipes.tssegmentClass(active, className?) (adopted at the 3 segmented sites — objects-table pill, search-panel facet, authorities NavLink — unifying the recipe + focus ring while keeping contextual padding) and rowStateClass(active) (adopted at the 4 selected-row sites; fixes field-list's row, which was missing the hover:bg-muted idle hover).

One-off kit adoption: deleted the dead components/ui/card.tsx; sidebar now imports the focusRing constant; login uses <PageTitle> (restores tracking-tight); field-list type-tag → <Badge variant="secondary">; h-4 w-4size-4 in theme-switch/user-menu/header-search; both object-detail close buttons → Button variant="ghost" size="icon-sm" (the drawer via Base UI's render prop).

Behavior-preserving (className token sets byte-equivalent except field-list's intended added hover). 268 tests pass unchanged; typecheck/lint/build clean; check:size 216.5 KB gz (identical to baseline); check:colors clean; no new dependency; no new i18n keys; no codename.

Deferred (out of scope): a full <SegmentedControl>/<ToggleGroup> component (the button-vs-NavLink interaction split makes a class helper the better fit) and the subjective form space-y-* spacing scale.

Done in merge `7cabebc`. **Shared helpers (stop future drift):** - `lib/use-lang.ts` — `useLang(): "sv" | "en"` replaces the inline `i18n.language.startsWith("sv")` derivation in 6 components. - `lib/class-recipes.ts` — `segmentClass(active, className?)` (adopted at the 3 segmented sites — objects-table pill, search-panel facet, authorities NavLink — unifying the recipe + focus ring while keeping contextual padding) and `rowStateClass(active)` (adopted at the 4 selected-row sites; **fixes** field-list's row, which was missing the `hover:bg-muted` idle hover). **One-off kit adoption:** deleted the dead `components/ui/card.tsx`; sidebar now imports the `focusRing` constant; login uses `<PageTitle>` (restores `tracking-tight`); field-list type-tag → `<Badge variant="secondary">`; `h-4 w-4` → `size-4` in theme-switch/user-menu/header-search; both object-detail close buttons → `Button variant="ghost" size="icon-sm"` (the drawer via Base UI's `render` prop). Behavior-preserving (className token sets byte-equivalent except field-list's intended added hover). 268 tests pass unchanged; typecheck/lint/build clean; check:size 216.5 KB gz (identical to baseline); check:colors clean; no new dependency; no new i18n keys; no codename. **Deferred (out of scope):** a full `<SegmentedControl>`/`<ToggleGroup>` component (the button-vs-NavLink interaction split makes a class helper the better fit) and the subjective form `space-y-*` spacing scale.
Sign in to join this conversation.
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: logaritmisk/biggus-dickus#66