diff --git a/docs/superpowers/plans/2026-06-08-design-kit-consistency.md b/docs/superpowers/plans/2026-06-08-design-kit-consistency.md
new file mode 100644
index 0000000..6b0890c
--- /dev/null
+++ b/docs/superpowers/plans/2026-06-08-design-kit-consistency.md
@@ -0,0 +1,211 @@
+# Design-Kit Consistency — Implementation Plan
+
+> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
+
+**Goal:** Add three shared helpers (`useLang`, `segmentClass`, `rowStateClass`), adopt them across the duplicated sites, and apply behavior-preserving kit one-offs (delete dead Card, sidebar focusRing, login PageTitle, field-list Badge, size-4, icon dismiss buttons).
+
+**Architecture:** Task 1 creates the helpers + deletes Card (additive/safe). Task 2 adopts the 3 helpers across 6 + 3 + 4 sites. Task 3 applies the one-off cleanups + full gate. Behavior-preserving throughout; `check:colors`/`check:size`/existing component tests are the guards.
+
+**Tech Stack:** React 19 + TS + pnpm, Tailwind v4 (token classes + `cn`), react-i18next, Base UI, Vitest 4 + RTL.
+
+**Conventions:** pnpm; **no `any`/`eslint-disable`/`@ts-ignore`**; no codename; double-quote+semicolon; token classes only (`check:colors`). `tsconfig` has `noUnusedLocals`, so remove any destructure that becomes unused.
+
+**Spec:** `docs/superpowers/specs/2026-06-08-design-kit-consistency-design.md`
+
+**Key facts (verified current):**
+- `lib/focus-ring.ts` exports `focusRing = "outline-none focus-visible:ring-3 focus-visible:ring-ring/50"`. `cn` is `@/lib/utils`.
+- `Button` (`@/components/ui/button`) has sizes incl. `icon-sm`. `Badge` (`@/components/ui/badge`) has a `secondary` variant. `PageTitle` (`@/components/ui/page-title`) is an `
` styled `text-2xl font-semibold tracking-tight`.
+- `components/ui/card.tsx` has ZERO importers and no `card.stories`.
+- `useLang` sites (each currently `const lang = i18n.language.startsWith("sv") ? "sv" : "en";`): `objects/object-detail.tsx:59`, `objects/field-input.tsx:32`, `vocab/vocabulary-terms.tsx:13`, `vocab/vocabulary-list.tsx:17`, `fields/field-list.tsx:27`, `authorities/authorities-page.tsx:19`.
+- `segmentClass` sites: `objects/objects-table.tsx:174` (`` className={`${focusRing} rounded-md px-2 py-1 ${active ? "bg-primary text-primary-foreground" : "border"}`} ``), `search/search-panel.tsx:76` (`className={cn("rounded-md px-2 py-0.5", focusRing, active ? "bg-primary text-primary-foreground" : "border")}`), `authorities/authorities-page.tsx:41` (`cn("rounded-md px-3 py-1 text-sm", focusRing, isActive ? "bg-primary text-primary-foreground" : "border")`).
+- `rowStateClass` sites: `objects/objects-table.tsx:252` (`selected ? "bg-primary/10" : "hover:bg-muted"`), `vocab/vocabulary-list.tsx:113` (`isActive ? "bg-primary/10" : "hover:bg-muted"`), `search/search-result-row.tsx:15` (`isActive ? "bg-primary/10" : "hover:bg-muted"`), `fields/field-list.tsx:86` (`def.key === selectedKey ? "bg-primary/10" : ""` — note the missing idle hover).
+
+---
+
+# Task 1: Create helpers + delete dead Card
+
+**Files:** Create `web/src/lib/use-lang.ts`, `web/src/lib/class-recipes.ts`, `web/src/lib/class-recipes.test.ts`; Delete `web/src/components/ui/card.tsx`.
+
+- [ ] **Step 1: `web/src/lib/use-lang.ts`:**
+```ts
+import { useTranslation } from "react-i18next";
+
+/** The instance's active UI language, narrowed to the two supported locales. */
+export function useLang(): "sv" | "en" {
+ const { i18n } = useTranslation();
+ return i18n.language.startsWith("sv") ? "sv" : "en";
+}
+```
+
+- [ ] **Step 2: `web/src/lib/class-recipes.ts`:**
+```ts
+import { cn } from "@/lib/utils";
+
+import { focusRing } from "./focus-ring";
+
+/** Segmented-control / filter-pill item. Unifies the active/inactive token recipe +
+ * focus ring; callers pass their contextual padding/size via `className`. */
+export function segmentClass(active: boolean, className?: string): string {
+ return cn("rounded-md", focusRing, active ? "bg-primary text-primary-foreground" : "border", className);
+}
+
+/** Selected vs idle row background for master-detail / list rows. */
+export function rowStateClass(active: boolean): string {
+ return active ? "bg-primary/10" : "hover:bg-muted";
+}
+```
+
+- [ ] **Step 3: `web/src/lib/class-recipes.test.ts`** (write + run):
+```ts
+import { expect, test } from "vitest";
+
+import { rowStateClass, segmentClass } from "./class-recipes";
+
+test("segmentClass active uses the primary tokens + focus ring", () => {
+ const cls = segmentClass(true, "px-2 py-1");
+ expect(cls).toContain("bg-primary");
+ expect(cls).toContain("text-primary-foreground");
+ expect(cls).toContain("focus-visible:ring-ring/50");
+ expect(cls).toContain("px-2");
+});
+
+test("segmentClass inactive uses border, not the primary fill", () => {
+ const cls = segmentClass(false);
+ expect(cls).toContain("border");
+ expect(cls).not.toContain("bg-primary");
+});
+
+test("rowStateClass toggles selected vs idle-hover", () => {
+ expect(rowStateClass(true)).toBe("bg-primary/10");
+ expect(rowStateClass(false)).toBe("hover:bg-muted");
+});
+```
+Run: `cd web && pnpm vitest run src/lib/class-recipes.test.ts` → 3 passing.
+
+- [ ] **Step 4: Delete the dead Card component:**
+```bash
+cd /Users/olsson/Laboratory/biggus-dickus
+git rm web/src/components/ui/card.tsx
+```
+(Confirm no references first: `git grep -n "components/ui/card\"" web/src` returns nothing.)
+
+- [ ] **Step 5: Verify + lint:**
+```bash
+cd web && pnpm vitest run src/lib/class-recipes.test.ts && pnpm typecheck && pnpm lint
+```
+Expected: green (Card had no importers, so its deletion can't break typecheck/lint).
+
+- [ ] **Step 6: Commit**
+```bash
+cd /Users/olsson/Laboratory/biggus-dickus
+git add web/src/lib/use-lang.ts web/src/lib/class-recipes.ts web/src/lib/class-recipes.test.ts
+git rm -q web/src/components/ui/card.tsx 2>/dev/null; git add -A web/src/components/ui
+git commit -m "feat(web): useLang + segmentClass/rowStateClass helpers; delete dead Card (#66)"
+```
+
+---
+
+# Task 2: Adopt the helpers across the duplicated sites
+
+**Files:** Modify `objects/object-detail.tsx`, `objects/field-input.tsx`, `vocab/vocabulary-terms.tsx`, `vocab/vocabulary-list.tsx`, `fields/field-list.tsx`, `authorities/authorities-page.tsx`, `objects/objects-table.tsx`, `search/search-panel.tsx`, `search/search-result-row.tsx`.
+
+- [ ] **Step 1: Adopt `useLang()` in the 6 components.** In each of `objects/object-detail.tsx`, `objects/field-input.tsx`, `vocab/vocabulary-terms.tsx`, `vocab/vocabulary-list.tsx`, `fields/field-list.tsx`, `authorities/authorities-page.tsx`: add `import { useLang } from "../lib/use-lang";` and replace `const lang = i18n.language.startsWith("sv") ? "sv" : "en";` with `const lang = useLang();`. Then, if `i18n` is no longer referenced anywhere else in that component, change `const { t, i18n } = useTranslation();` to `const { t } = useTranslation();` (the `noUnusedLocals` typecheck will fail otherwise — so this removal is required wherever `i18n` becomes unused). Note `authorities/authorities-page.tsx` also imports `focusRing` and uses `cn` — leave those.
+
+- [ ] **Step 2: Adopt `segmentClass` at the 3 segmented sites.**
+ - `objects/objects-table.tsx`: add `import { segmentClass } from "../lib/class-recipes";`; change the pill `className` (currently `` `${focusRing} rounded-md px-2 py-1 ${active ? "bg-primary text-primary-foreground" : "border"}` ``) to `className={segmentClass(active, "px-2 py-1")}`. If `focusRing` is now unused in this file, remove its import. (The object-number `` also uses `focusRing` — if so, KEEP the import.)
+ - `search/search-panel.tsx`: add the import; change `className={cn("rounded-md px-2 py-0.5", focusRing, active ? "bg-primary text-primary-foreground" : "border")}` to `className={segmentClass(active, "px-2 py-0.5")}`. Remove now-unused `focusRing`/`cn` imports if they're unused elsewhere in the file.
+ - `authorities/authorities-page.tsx`: add the import; change the NavLink className callback body `cn("rounded-md px-3 py-1 text-sm", focusRing, isActive ? "bg-primary text-primary-foreground" : "border")` to `segmentClass(isActive, "px-3 py-1 text-sm")`. Remove now-unused `focusRing`/`cn` imports if unused elsewhere.
+
+- [ ] **Step 3: Adopt `rowStateClass` at the 4 selected-row sites.** Add `import { rowStateClass } from "…/lib/class-recipes";` (or extend the existing class-recipes import) to each:
+ - `objects/objects-table.tsx`: in the row `className`, change `${selected ? "bg-primary/10" : "hover:bg-muted"}` to `${rowStateClass(selected)}`.
+ - `vocab/vocabulary-list.tsx`: change `${isActive ? "bg-primary/10" : "hover:bg-muted"}` to `${rowStateClass(isActive)}`.
+ - `search/search-result-row.tsx`: change `${isActive ? "bg-primary/10" : "hover:bg-muted"}` to `${rowStateClass(isActive)}`.
+ - `fields/field-list.tsx`: change `${def.key === selectedKey ? "bg-primary/10" : ""}` to `${rowStateClass(def.key === selectedKey)}` (this ADDS the `hover:bg-muted` idle hover the others have — an intended consistency fix).
+
+- [ ] **Step 4: Verify (vitest ONCE for the affected suites), typecheck, lint:**
+```bash
+cd web && pnpm vitest run src/objects src/vocab src/fields src/authorities src/search && pnpm typecheck && pnpm lint
+```
+Expected: green. These are class-string-equivalent changes (segmentClass/rowStateClass produce the same token sets; `cn` ordering is irrelevant to Tailwind), so the existing component tests pass unchanged. `field-list`'s row now also carries `hover:bg-muted` (additive). If a test asserted the exact old className string, update it to match the new equivalent (unlikely — tests query by role/text).
+
+- [ ] **Step 5: Commit**
+```bash
+cd /Users/olsson/Laboratory/biggus-dickus
+git add web/src/objects/object-detail.tsx web/src/objects/field-input.tsx web/src/vocab/vocabulary-terms.tsx web/src/vocab/vocabulary-list.tsx web/src/fields/field-list.tsx web/src/authorities/authorities-page.tsx web/src/objects/objects-table.tsx web/src/search/search-panel.tsx web/src/search/search-result-row.tsx
+git commit -m "refactor(web): adopt useLang + segmentClass/rowStateClass across sites (#66)"
+```
+
+---
+
+# Task 3: One-off kit cleanups + full gate
+
+**Files:** Modify `shell/sidebar.tsx`, `auth/login-page.tsx`, `fields/field-list.tsx`, `shell/theme-switch.tsx`, `shell/user-menu.tsx`, `shell/header-search.tsx`, `objects/objects-page.tsx`, `objects/object-detail-drawer.tsx`.
+
+- [ ] **Step 1: `shell/sidebar.tsx`** — use the `focusRing` constant. Add `import { focusRing } from "../lib/focus-ring";` (if not already imported). At the two `cn(...)` sites (lines ~46 and ~88) replace the literal `"focus-visible:ring-3 focus-visible:ring-ring/50"` entry with `focusRing`. (Both are inside `cn(...)` lists, so just swap the string for the constant.)
+
+- [ ] **Step 2: `auth/login-page.tsx`** — use `PageTitle`. Add `import { PageTitle } from "@/components/ui/page-title";` and change `
{app_name}
` to `{app_name}`.
+
+- [ ] **Step 3: `fields/field-list.tsx`** — type-tag → `Badge`. Add `import { Badge } from "@/components/ui/badge";` and change the type-tag `{…}` (line ~97) to `{…}` (keep the inner expression/children unchanged).
+
+- [ ] **Step 4: Icon sizing → `size-4`** in the 3 app-source sites: `shell/theme-switch.tsx:39` (`` → `className="size-4"`), `shell/user-menu.tsx:27` (`` → `size-4`), `shell/header-search.tsx:23` (the search icon's `… h-4 w-4 …` → replace `h-4 w-4` with `size-4`, keeping the other classes). Do NOT touch `components/ui/select.tsx`.
+
+- [ ] **Step 5: Icon dismiss buttons → kit Button.**
+ - `objects/objects-page.tsx:54`: add `import { Button } from "@/components/ui/button";` (if absent) and change the `` to:
+```tsx
+
+```
+ - `objects/object-detail-drawer.tsx:31-36`: add `import { Button } from "@/components/ui/button";` and render the `DrawerClose` AS the kit Button via the render prop:
+```tsx
+ }
+ >
+
+
+```
+ (This mirrors the `AlertDialogTrigger render={}` pattern in `components/delete-confirm-dialog.tsx`; the `DrawerClose` keeps its close-on-click behaviour and the `aria-label`.)
+
+- [ ] **Step 6: FULL FRONTEND GATE (run tests EXACTLY ONCE):**
+```bash
+cd web && pnpm typecheck && pnpm lint && pnpm test && pnpm build && pnpm check:size && pnpm check:colors
+```
+All green. Report test totals, largest chunk (gz) from check:size (should be ≤ the prior ~216.5 KB — the Card delete only removes dead code), and the `check:colors` line. The existing `user-menu`, `objects-table`, `object-detail`/drawer, `login-page`, sidebar, `field-list`, search tests must pass unchanged (the icon buttons keep their `aria-label`s; the drawer still closes; login still renders an `
` via PageTitle).
+
+- [ ] **Step 7: Codename + status:**
+```bash
+cd /Users/olsson/Laboratory/biggus-dickus
+git grep -in 'biggus\|dickus' -- web/src; echo "codename-exit=$?"
+git status --short
+```
+Expected: no matches (`codename-exit=1`).
+
+- [ ] **Step 8: Manual smoke (recommended).** `pnpm dev`: the visibility pills / authority tabs / search facets look unchanged and keep their focus rings; the selected list rows (objects, vocab, search, fields) highlight identically and field rows now have a hover; the object-detail close buttons (wide pane + drawer) work; the login title and field-list type tag look right.
+
+- [ ] **Step 9: Commit**
+```bash
+cd /Users/olsson/Laboratory/biggus-dickus
+git add web/src/shell/sidebar.tsx web/src/auth/login-page.tsx web/src/fields/field-list.tsx web/src/shell/theme-switch.tsx web/src/shell/user-menu.tsx web/src/shell/header-search.tsx web/src/objects/objects-page.tsx web/src/objects/object-detail-drawer.tsx
+git commit -m "refactor(web): kit consistency — focusRing, PageTitle, Badge, size-4, icon buttons (#66)"
+```
+
+---
+
+## Self-Review (completed)
+
+**Spec coverage:** AC1 `useLang` + 6 sites (T1 S1, T2 S1); AC2 `segmentClass`/`rowStateClass` + adoption + field-list hover fix (T1 S2-S3, T2 S2-S3); AC3 Card deleted (T1 S4); AC4 one-offs — sidebar focusRing, login PageTitle, field-list Badge, size-4, icon buttons (T3 S1-S5); AC5 gate/check:size/codename (T3 S6-S7). ✓
+
+**Placeholder scan:** every edit gives the exact before string + after code; helper bodies are complete; the test has concrete assertions. The "remove `i18n` if unused" instructions are concrete (driven by `noUnusedLocals`). No TBD. ✓
+
+**Type/consistency:** `useLang()` (T1) returns `"sv" | "en"` consumed as `const lang` (T2 S1); `segmentClass(active, className?)` / `rowStateClass(active)` (T1) called with the exact args in T2 S2-S3; `Button size="icon-sm"`, `Badge variant="secondary"`, `PageTitle` all confirmed to exist. ✓
+
+## Notes
+- No new dependency, no new i18n keys. `check:colors` stays green — `segmentClass`/`rowStateClass` and all edits use tokens (`bg-primary`, `border`, `ring-ring`, `bg-muted`). Card deletion only removes dead code.
+- `cn()` (tailwind-merge) makes class ordering irrelevant, so the helper outputs are visually identical to the prior inline strings (except field-list's intended added hover).
+- The `` component and the form-spacing scale are deferred (out of scope).
diff --git a/docs/superpowers/specs/2026-06-08-design-kit-consistency-design.md b/docs/superpowers/specs/2026-06-08-design-kit-consistency-design.md
new file mode 100644
index 0000000..6ca201a
--- /dev/null
+++ b/docs/superpowers/specs/2026-06-08-design-kit-consistency-design.md
@@ -0,0 +1,122 @@
+# Design-Kit Consistency — Design
+
+**Date:** 2026-06-08
+**Status:** Approved (brainstorming) — ready for implementation planning.
+**Issue:** #66 (dead Card, duplicated segmented-control + selected-row recipes, `useLang`/`focusRing` drift, misc kit one-offs).
+
+## Context
+
+A frontend deep audit found subtler design-system inconsistencies the `check:colors` guard doesn't catch
+(the app is already hex-free + token-based + dark-mode-clean). These are duplicated class recipes and
+non-adoption of the `ui/*` kit. All fixes are behavior-preserving; `check:colors`/`check:size`/the existing
+component tests are the guards. State re-verified against the current code (post #62/#64).
+
+## Components
+
+### New shared helpers
+
+**`lib/use-lang.ts`** — `useLang(): "sv" | "en"`
+```ts
+import { useTranslation } from "react-i18next";
+
+/** The instance's active UI language, narrowed to the two supported locales. */
+export function useLang(): "sv" | "en" {
+ const { i18n } = useTranslation();
+ return i18n.language.startsWith("sv") ? "sv" : "en";
+}
+```
+Replaces the inline `const lang = i18n.language.startsWith("sv") ? "sv" : "en";` in **6** components:
+`objects/object-detail.tsx`, `objects/field-input.tsx`, `vocab/vocabulary-terms.tsx`,
+`vocab/vocabulary-list.tsx`, `fields/field-list.tsx`, `authorities/authorities-page.tsx`. Each switches to
+`const lang = useLang();` and drops the now-unused `i18n` from its `useTranslation()` destructure where
+`i18n` is otherwise unused. (Left untouched: `shell/lang-switch.tsx` — derives from a different `locale`
+var; `i18n/index.ts` — the infra `languageChanged` handler.)
+
+**`lib/class-recipes.ts`** — two shared class helpers
+```ts
+import { cn } from "@/lib/utils";
+import { focusRing } from "./focus-ring";
+
+/** Segmented-control / filter-pill item. Unifies the active/inactive token recipe +
+ * focus ring; callers pass their contextual padding/size via `className`. */
+export function segmentClass(active: boolean, className?: string): string {
+ return cn("rounded-md", focusRing, active ? "bg-primary text-primary-foreground" : "border", className);
+}
+
+/** Selected vs idle row background for master-detail / list rows. */
+export function rowStateClass(active: boolean): string {
+ return active ? "bg-primary/10" : "hover:bg-muted";
+}
+```
+- **`segmentClass`** is adopted at the 3 segmented sites, each keeping its contextual padding:
+ - `objects/objects-table.tsx:174` → `segmentClass(active, "px-2 py-1")`
+ - `search/search-panel.tsx:76` → `segmentClass(active, "px-2 py-0.5")`
+ - `authorities/authorities-page.tsx:41` (NavLink) → `segmentClass(isActive, "px-3 py-1 text-sm")`
+ This DRYs the bug-prone recipe (the active/inactive token pair + `focusRing` — the part that drifted and
+ caused the #62 missing-ring bug); contextual sizing is intentionally preserved per site.
+- **`rowStateClass`** is adopted at the 4 selected-row sites:
+ - `objects/objects-table.tsx:252` → `rowStateClass(selected)`
+ - `vocab/vocabulary-list.tsx:113` → `rowStateClass(isActive)`
+ - `search/search-result-row.tsx:15` → `rowStateClass(isActive)`
+ - `fields/field-list.tsx:86` → `rowStateClass(def.key === selectedKey)` — **fixes** this site, which
+ currently uses `… ? "bg-primary/10" : ""` (dropping the `hover:bg-muted` idle hover the others have).
+
+### One-off cleanups
+- **Delete `components/ui/card.tsx`** — zero importers (no app/test/story references; no `card.stories`).
+- **`shell/sidebar.tsx:46,88`** — replace the raw `focus-visible:ring-3 focus-visible:ring-ring/50`
+ string (inside the existing `cn(...)`) with the imported `focusRing` constant (adds `outline-none`,
+ matching every other call site).
+- **`auth/login-page.tsx:49`** — `