Files
familienarchiv/docs/specs/focus-rings-spec.html
Marcel a6ee444f3b docs(specs): add focus rings design spec for issue #167
Spec covers the --c-focus-ring token definition, full audit of all 19
affected files, WCAG 2.4.11 analysis, element-by-element mockups (light
and dark), and exact CSS/Tailwind diffs ready for implementation.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-31 10:33:50 +02:00

1153 lines
64 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Focus Rings — Design Spec · Familienarchiv</title>
<style>
*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}
body{font-family:'Helvetica Neue',Arial,sans-serif;background:#ECEAE4;color:#1A1A1A;line-height:1.5}
.doc{max-width:1440px;margin:0 auto;padding:48px 32px}
/* ── Masthead ─── */
.mast{background:#012851;border-radius:10px;padding:32px 40px;margin-bottom:48px}
.mast-top{display:flex;align-items:flex-start;justify-content:space-between;gap:24px;margin-bottom:16px}
.mast h1{font-size:22px;font-weight:900;color:#fff;letter-spacing:-.4px;margin-bottom:6px}
.mast p{font-size:12px;color:rgba(255,255,255,.55);max-width:680px;line-height:1.7}
.mast-badge{font-size:9px;font-weight:800;padding:3px 9px;border-radius:20px;text-transform:uppercase;letter-spacing:.8px;white-space:nowrap;flex-shrink:0;margin-top:4px}
.mb-spec{background:#a1dcd8;color:#012851}
.decisions{display:grid;grid-template-columns:repeat(4,1fr);gap:10px;margin-top:20px;border-top:1px solid rgba(255,255,255,.12);padding-top:16px}
.dec{background:rgba(255,255,255,.07);border-radius:6px;padding:10px 12px}
.dec-label{font-size:7px;font-weight:800;text-transform:uppercase;letter-spacing:.8px;color:rgba(255,255,255,.35);margin-bottom:5px}
.dec-value{font-size:9.5px;font-weight:700;color:#fff;line-height:1.5}
/* ── Section headings ─── */
.sec{margin-bottom:64px}
.sec+.sec{border-top:2px dashed #C8C4BE;padding-top:56px}
.sec-h{font-size:11px;font-weight:800;text-transform:uppercase;letter-spacing:1.2px;color:#888;margin-bottom:20px;display:flex;align-items:center;gap:10px}
.sec-h::after{content:'';flex:1;height:1px;background:#D8D4CE}
.sec-num{background:#012851;color:#fff;font-size:9px;font-weight:900;padding:2px 7px;border-radius:10px}
/* ── Layout helpers ─── */
.sg{display:grid;gap:20px;align-items:start}
.sg-2{grid-template-columns:1fr 1fr}
.sg-3{grid-template-columns:1fr 1fr 1fr}
.sb{display:flex;flex-direction:column}
.sl{font-size:9px;font-weight:800;color:#888;text-transform:uppercase;letter-spacing:1.5px;margin-bottom:6px;display:flex;align-items:center;gap:6px}
.sc{font-size:8.5px;color:#888;margin-top:6px;font-style:italic;line-height:1.5}
.mode-label{display:inline-block;font-size:7.5px;font-weight:800;text-transform:uppercase;letter-spacing:.8px;padding:2px 8px;border-radius:10px;margin-bottom:8px}
.lm{background:#E8F4F8;color:#012851;border:1px solid #a1dcd8}
.dm{background:#012851;color:#a1dcd8;border:1px solid #0d3358}
/* ── Issue callouts ─── */
.issue{background:#FFF7ED;border:1px solid #FDBA74;border-radius:8px;padding:16px 20px;margin-bottom:12px}
.issue-id{font-size:8px;font-weight:800;text-transform:uppercase;letter-spacing:.8px;color:#C2410C;margin-bottom:4px}
.issue-title{font-size:13px;font-weight:700;color:#1A1A1A;margin-bottom:6px}
.issue-body{font-size:11px;color:#7C2D12;line-height:1.6}
.issue-body code{background:rgba(0,0,0,.06);border-radius:3px;padding:1px 5px;font-size:10px;font-family:monospace}
.issue-fix{background:#F0FDF4;border:1px solid #86EFAC;border-radius:6px;padding:10px 14px;margin-top:10px;font-size:11px;color:#14532D;line-height:1.6}
.issue-fix strong{font-weight:800}
.issue-fix code{background:rgba(0,0,0,.06);border-radius:3px;padding:1px 5px;font-size:10px;font-family:monospace}
/* ── Token table ─── */
.tok-table{width:100%;border-collapse:collapse;font-size:10px}
.tok-table th{text-align:left;font-size:8px;font-weight:800;text-transform:uppercase;letter-spacing:.8px;color:#888;padding:6px 8px;border-bottom:2px solid #D8D4CE}
.tok-table td{padding:7px 8px;border-bottom:1px solid #E8E4DF;vertical-align:middle}
.tok-table tr:hover td{background:rgba(0,0,0,.025)}
.swatch{display:inline-block;width:20px;height:20px;border-radius:4px;border:1px solid rgba(0,0,0,.12);vertical-align:middle;margin-right:6px;flex-shrink:0}
.swatch-pair{display:flex;align-items:center;gap:6px}
.hex{font-family:monospace;font-size:10px}
.tag-bad{display:inline-block;font-size:7.5px;font-weight:800;text-transform:uppercase;letter-spacing:.6px;padding:1px 5px;border-radius:3px;background:#FEE2E2;color:#991B1B}
.tag-ok{display:inline-block;font-size:7.5px;font-weight:800;text-transform:uppercase;letter-spacing:.6px;padding:1px 5px;border-radius:3px;background:#D1FAE5;color:#065F46}
.tag-new{display:inline-block;font-size:7.5px;font-weight:800;text-transform:uppercase;letter-spacing:.6px;padding:1px 5px;border-radius:3px;background:#DBEAFE;color:#1E3A5F}
/* ── Spec disclaimer ─── */
.spec-disclaimer{background:#FFF8E1;border:1.5px solid #FFC107;border-radius:6px;padding:11px 16px;font-size:11px;color:#6D4C00;margin-bottom:32px;line-height:1.6}
.spec-disclaimer strong{font-weight:800}
/* ── Agent Implementation Reference ─── */
.impl-ref{background:#0d1117;border-radius:8px;margin-top:20px;overflow:hidden;border:1px solid #30363d}
.impl-ref-hdr{background:#161b22;padding:9px 16px;font-size:9.5px;font-weight:800;color:#f0883e;border-bottom:1px solid #30363d;display:flex;align-items:center;gap:8px;letter-spacing:.4px;text-transform:uppercase}
.impl-ref-hdr::before{content:'⚙';font-size:12px}
.impl-ref-hdr span{color:rgba(240,136,62,.55);font-weight:400;margin-left:auto;font-size:9px;text-transform:none;letter-spacing:0}
.impl-ref table{width:100%;border-collapse:collapse;font-size:10px}
.impl-ref th{text-align:left;font-size:8px;font-weight:800;text-transform:uppercase;letter-spacing:.8px;color:#8b949e;padding:8px 14px;border-bottom:1px solid #21262d}
.impl-ref td{padding:6px 14px;border-bottom:1px solid #161b22;vertical-align:top;line-height:1.6;color:#c9d1d9}
.impl-ref tr:last-child td{border-bottom:none}
.impl-ref td:first-child{color:#79c0ff;font-weight:700;white-space:nowrap;width:200px}
.impl-ref td code{font-family:'SFMono-Regular',Consolas,monospace;font-size:9.5px;background:#161b22;color:#a5d6ff;padding:1px 5px;border-radius:3px;white-space:nowrap}
.impl-ref .ir-px{color:#7ee787;font-family:monospace;font-size:9.5px}
/* ── CSS diff block ─── */
.diff-block{background:#0d1117;border-radius:8px;overflow:hidden;border:1px solid #30363d;margin-top:20px}
.diff-block-hdr{background:#161b22;padding:8px 14px;font-size:9px;font-weight:800;color:#8b949e;border-bottom:1px solid #30363d;text-transform:uppercase;letter-spacing:.6px;font-family:'SFMono-Regular',Consolas,monospace}
.diff-block pre{padding:14px;font-size:10px;line-height:1.7;color:#c9d1d9;font-family:'SFMono-Regular',Consolas,monospace;overflow-x:auto;white-space:pre}
.diff-add{color:#7ee787}
.diff-remove{color:#f85149}
.diff-context{color:#8b949e}
.diff-file{color:#f0883e;font-weight:700}
/* ── Visual mockup frames ─── */
.mock-frame{border-radius:8px;overflow:hidden;border:1.5px solid #B8B4AE}
.mock-lm{background:#f0efe9}
.mock-dm{background:#010e1e;border-color:#0d3358}
.mock-body{padding:16px 18px;display:flex;flex-direction:column;gap:12px}
.mock-label{font-size:8px;font-weight:800;text-transform:uppercase;letter-spacing:.8px;margin-bottom:4px}
.mock-lm .mock-label{color:#6b7280}
.mock-dm .mock-label{color:#8b97a5}
/* ── Light mode focus demos ─── */
.demo-input-lm{
width:100%;height:24px;border-radius:3px;border:1.5px solid #012851;
background:#fff;padding:0 8px;font-size:9px;font-family:inherit;color:#012851;
outline:none;
box-shadow:0 0 0 2px #f0efe9, 0 0 0 4px #012851;
}
.demo-input-lm-idle{
width:100%;height:24px;border-radius:3px;border:1.5px solid #e4e2d7;
background:#fff;padding:0 8px;font-size:9px;font-family:inherit;color:#012851;
outline:none;
}
.demo-input-lm-error{
width:100%;height:24px;border-radius:3px;border:1.5px solid #dc2626;
background:#fff;padding:0 8px;font-size:9px;font-family:inherit;color:#012851;
outline:none;
box-shadow:0 0 0 2px #f0efe9, 0 0 0 4px #012851;
}
.demo-btn-lm-primary{
height:24px;padding:0 12px;background:#012851;color:#fff;border:none;
border-radius:3px;font-size:9px;font-weight:700;font-family:inherit;cursor:default;
outline:none;
box-shadow:0 0 0 2px #f0efe9, 0 0 0 4px #012851;
}
.demo-btn-lm-ghost{
height:24px;padding:0 12px;background:transparent;color:#012851;
border:1.5px solid #012851;border-radius:3px;font-size:9px;font-weight:700;font-family:inherit;cursor:default;
outline:none;
box-shadow:0 0 0 2px #f0efe9, 0 0 0 4px #012851;
}
.demo-btn-lm-idle{
height:24px;padding:0 12px;background:#012851;color:#fff;border:none;
border-radius:3px;font-size:9px;font-weight:700;font-family:inherit;cursor:default;
outline:none;
}
.demo-icon-lm{
width:22px;height:22px;border-radius:3px;background:rgba(1,40,81,.08);
display:inline-flex;align-items:center;justify-content:center;
outline:none;
box-shadow:0 0 0 2px #f0efe9, 0 0 0 4px #012851;
font-size:11px;
}
.demo-icon-lm-idle{
width:22px;height:22px;border-radius:3px;background:rgba(1,40,81,.08);
display:inline-flex;align-items:center;justify-content:center;
font-size:11px;
}
.demo-chip-lm{
display:inline-flex;align-items:center;gap:4px;padding:2px 4px 2px 7px;
background:rgba(161,220,216,.2);border:1px solid #a1dcd8;border-radius:2px;
font-size:8px;color:#012851;font-weight:600;
}
.demo-chip-lm-close{
width:14px;height:14px;background:none;border:none;cursor:default;
border-radius:2px;font-size:9px;color:#012851;display:inline-flex;align-items:center;justify-content:center;
outline:none;
box-shadow:0 0 0 1.5px #f0efe9, 0 0 0 3px #012851;
}
.demo-chip-lm-close-idle{
width:14px;height:14px;background:none;border:none;cursor:default;
border-radius:2px;font-size:9px;color:#012851;display:inline-flex;align-items:center;justify-content:center;
}
.demo-checkbox-lm{
width:13px;height:13px;border-radius:2px;border:1.5px solid #012851;
background:#fff;flex-shrink:0;
outline:none;
box-shadow:0 0 0 2px #f0efe9, 0 0 0 4px #012851;
}
.demo-link-lm{
font-size:9px;font-weight:700;color:#012851;text-decoration:none;
outline:none;
box-shadow:0 0 0 2px #f0efe9, 0 0 0 4px #012851;
border-radius:2px;padding:1px 2px;
}
.demo-nav-lm{
display:inline-block;font-size:7.5px;font-weight:800;text-transform:uppercase;
letter-spacing:.5px;color:#fff;padding:4px 7px;border-radius:3px;
outline:none;
box-shadow:0 0 0 2px #012851, 0 0 0 4px #a1dcd8;
background:rgba(255,255,255,.08);
}
.demo-nav-lm-idle{
display:inline-block;font-size:7.5px;font-weight:800;text-transform:uppercase;
letter-spacing:.5px;color:rgba(255,255,255,.6);padding:4px 7px;border-radius:3px;
}
/* ── Dark mode focus demos ─── */
.demo-input-dm{
width:100%;height:24px;border-radius:3px;border:1.5px solid #a1dcd8;
background:#011526;padding:0 8px;font-size:9px;font-family:inherit;color:#f0efe9;
outline:none;
box-shadow:0 0 0 2px #010e1e, 0 0 0 4px #a1dcd8;
}
.demo-input-dm-idle{
width:100%;height:24px;border-radius:3px;border:1.5px solid #0d3358;
background:#011526;padding:0 8px;font-size:9px;font-family:inherit;color:#f0efe9;
outline:none;
}
.demo-btn-dm-primary{
height:24px;padding:0 12px;background:#a1dcd8;color:#012851;border:none;
border-radius:3px;font-size:9px;font-weight:700;font-family:inherit;cursor:default;
outline:none;
box-shadow:0 0 0 2px #010e1e, 0 0 0 4px #a1dcd8;
}
.demo-btn-dm-ghost{
height:24px;padding:0 12px;background:transparent;color:#a1dcd8;
border:1.5px solid #a1dcd8;border-radius:3px;font-size:9px;font-weight:700;font-family:inherit;cursor:default;
outline:none;
box-shadow:0 0 0 2px #010e1e, 0 0 0 4px #a1dcd8;
}
.demo-icon-dm{
width:22px;height:22px;border-radius:3px;background:rgba(255,255,255,.08);
display:inline-flex;align-items:center;justify-content:center;
outline:none;
box-shadow:0 0 0 2px #010e1e, 0 0 0 4px #a1dcd8;
font-size:11px;color:#f0efe9;
}
.demo-chip-dm{
display:inline-flex;align-items:center;gap:4px;padding:2px 4px 2px 7px;
background:rgba(0,199,177,.15);border:1px solid #00c7b1;border-radius:2px;
font-size:8px;color:#a1dcd8;font-weight:600;
}
.demo-chip-dm-close{
width:14px;height:14px;background:none;border:none;cursor:default;
border-radius:2px;font-size:9px;color:#a1dcd8;display:inline-flex;align-items:center;justify-content:center;
outline:none;
box-shadow:0 0 0 1.5px #010e1e, 0 0 0 3px #a1dcd8;
}
.demo-checkbox-dm{
width:13px;height:13px;border-radius:2px;border:1.5px solid #a1dcd8;
background:#011526;flex-shrink:0;
outline:none;
box-shadow:0 0 0 2px #010e1e, 0 0 0 4px #a1dcd8;
}
.demo-nav-dm{
display:inline-block;font-size:7.5px;font-weight:800;text-transform:uppercase;
letter-spacing:.5px;color:#fff;padding:4px 7px;border-radius:3px;
outline:none;
/* On navy header: use narrower 1px gap so ring is distinct from header bg */
box-shadow:0 0 0 1px #01335e, 0 0 0 3px #a1dcd8;
background:rgba(255,255,255,.1);
}
.demo-row{display:flex;align-items:center;gap:14px;flex-wrap:wrap}
.demo-pair{display:flex;align-items:center;gap:6px}
/* ── Note / info boxes ─── */
.note{background:#EFF6FF;border:1px solid #BFDBFE;border-radius:6px;padding:12px 16px;font-size:11px;color:#1E3A5F;line-height:1.6;margin-top:16px}
.note strong{font-weight:800}
.warn-note{background:#FFF7ED;border:1px solid #FED7AA;border-radius:6px;padding:12px 16px;font-size:11px;color:#7C2D12;line-height:1.6;margin-top:16px}
.warn-note strong{font-weight:800}
.warn-note code{background:rgba(0,0,0,.06);border-radius:3px;padding:1px 5px;font-size:10px;font-family:monospace}
/* ── Audit table ─── */
.audit-table{width:100%;border-collapse:collapse;font-size:10.5px;margin-top:12px}
.audit-table th{text-align:left;font-size:8px;font-weight:800;text-transform:uppercase;letter-spacing:.8px;color:#888;padding:7px 10px;border-bottom:2px solid #D8D4CE}
.audit-table td{padding:8px 10px;border-bottom:1px solid #E8E4DF;vertical-align:top;line-height:1.6}
.audit-table tr:last-child td{border-bottom:none}
.audit-table td:first-child{font-family:monospace;font-size:10px;color:#012851;font-weight:700}
.audit-table td code{font-family:monospace;font-size:9.5px;background:rgba(1,40,81,.07);color:#012851;padding:1px 4px;border-radius:3px}
/* ── Header bar for nav demo ─── */
.hdr-demo{background:#012851;padding:8px 14px;display:flex;align-items:center;gap:10px;border-radius:6px}
</style>
</head>
<body>
<div class="doc">
<!-- ═══════════════════════════════════════════════════════════
MASTHEAD
════════════════════════════════════════════════════════════ -->
<div class="mast">
<div class="mast-top">
<div>
<h1>Focus Rings — Design Spec</h1>
<p>Most interactive elements currently use browser-default focus rings. There is no <code style="color:rgba(255,255,255,.7);background:rgba(255,255,255,.1);padding:1px 5px;border-radius:3px;font-size:11px">--c-focus-ring</code> semantic token, leading to 5 different ad-hoc approaches across the codebase. This spec defines a single token, proves WCAG 2.4.11 compliance in both modes, and gives exact Tailwind classes for every element type. Relates to issue #167.</p>
</div>
<span class="mast-badge mb-spec">Design Spec</span>
</div>
<div class="decisions">
<div class="dec">
<div class="dec-label">Root cause</div>
<div class="dec-value">No --c-focus-ring token — 5 different ring colors across the codebase</div>
</div>
<div class="dec">
<div class="dec-label">WCAG 2.4.11 failure</div>
<div class="dec-value">ring-accent in light mode (#a1dcd8 on white) = 1.52:1 — fails 3:1 minimum</div>
</div>
<div class="dec">
<div class="dec-label">Light token</div>
<div class="dec-value">#012851 brand-navy — 14:1 on white, 13:1 on sand — WCAG AAA ✓</div>
</div>
<div class="dec">
<div class="dec-label">Dark token</div>
<div class="dec-value">#a1dcd8 brand-mint — 14.3:1 on canvas (#010e1e) — WCAG AAA ✓</div>
</div>
</div>
</div>
<div class="spec-disclaimer">
<strong>📐 Mockup scale notice —</strong> all font-size, height, and padding values
in the mockup CSS are scaled to ~55% of actual implementation values.
<strong>Do not copy sizes from mockup CSS.</strong> Use the ⚙ Implementation
Reference tables after each section.
</div>
<!-- ═══════════════════════════════════════════════════════════
1. ISSUE CATALOG
════════════════════════════════════════════════════════════ -->
<div class="sec">
<div class="sec-h"><span class="sec-num">1</span> Issue Catalog</div>
<div class="issue">
<div class="issue-id">Issue 01 · Critical — WCAG 2.4.11 Failure</div>
<div class="issue-title">ring-accent in light mode = 1.52:1 contrast — fails the focus indicator minimum</div>
<div class="issue-body">
The header (AppNav, UserMenu, LanguageSwitcher, ThemeToggle, NotificationBell) uses <code>focus-visible:ring-accent</code>.
In light mode <code>--c-accent: #a1dcd8</code>. That mint ring on the white/sand background has a contrast ratio of <strong>1.52:1</strong> — the WCAG 2.4.11 minimum is 3:1 for the focus indicator against adjacent colors.<br><br>
Notifications page filter pills (<code>focus-visible:ring-accent focus-visible:ring-offset-2</code>) have the same failure — the ring-offset is white (#fff), so the contrast is #a1dcd8 vs #fff = 1.52:1.
</div>
<div class="issue-fix">
<strong>Fix:</strong> Replace <code>ring-accent</code> with <code>ring-focus-ring</code> throughout. In light mode <code>--c-focus-ring: #012851</code> gives 14:1 on white and 13:1 on sand — WCAG AAA.
</div>
</div>
<div class="issue">
<div class="issue-id">Issue 02 · Critical — WCAG 2.4.11 Failure</div>
<div class="issue-title">Chip close buttons: focus:outline-none with no ring replacement</div>
<div class="issue-body">
<code>PersonMultiSelect.svelte:94</code> and <code>TagInput.svelte:98</code> apply <code>focus:outline-none</code> to the chip × buttons with <strong>no</strong> replacement focus indicator. These are keyboard-operable interactive controls. Removing the browser outline without a replacement is a hard WCAG 2.4.11 fail — keyboard users cannot see which chip they are about to remove.<br><br>
Additionally, <code>TagInput.svelte:125</code> sets <code>outline-none focus:ring-0</code> on the inner text input, also leaving it with zero focus indicator.
</div>
<div class="issue-fix">
<strong>Fix:</strong> Remove the bare <code>focus:outline-none</code> from both chip close buttons and replace with <code>focus:outline-none focus-visible:ring-2 focus-visible:ring-focus-ring</code>. For the TagInput inner input: remove <code>focus:ring-0</code> and add <code>focus:ring-2 focus:ring-focus-ring</code> (show on focus, not just focus-visible, since this is a text field).
</div>
</div>
<div class="issue">
<div class="issue-id">Issue 03 · High</div>
<div class="issue-title">No --c-focus-ring token — 5 different ring colors across the codebase</div>
<div class="issue-body">
Current audit of <code>focus</code> / <code>focus-visible</code> classes across all Svelte files reveals five distinct ring-color decisions:<br><br>
&nbsp;&nbsp;<code>ring-accent</code> — header elements, PanelHistory, MentionEditor, AppNav, UserGroupsSection<br>
&nbsp;&nbsp;<code>ring-ink</code> — form inputs, textareas, selects (WhoWhenSection, DescriptionSection, TranscriptionSection, SearchFilterBar, ConversationFilterBar, ForgotPassword, profile forms)<br>
&nbsp;&nbsp;<code>ring-primary</code> — PersonTypeahead compact mode<br>
&nbsp;&nbsp;<code>ring-black</code> — PersonTypeahead dropdown (Headless UI default)<br>
&nbsp;&nbsp;• nothing — TagInput inner input, chip close buttons<br><br>
With no single token to update, switching focus color for dark mode requires editing every file independently — and future components will continue to drift.
</div>
<div class="issue-fix">
<strong>Fix:</strong> Add <code>--c-focus-ring</code> to <code>layout.css</code> (light + dark blocks) and <code>--color-focus-ring: var(--c-focus-ring)</code> in <code>@theme inline</code>. All components then use <code>ring-focus-ring</code> — one token to retheme all focus rings at once.
</div>
</div>
<div class="issue">
<div class="issue-id">Issue 04 · Medium</div>
<div class="issue-title">Inconsistent ring width and missing ring-offset</div>
<div class="issue-body">
Ring widths vary: some elements use <code>ring-1</code> (1px, barely visible), some <code>ring-2</code> (2px), and form inputs omit the width class entirely (Tailwind 4 default ring is 1px). No element outside the notifications page uses <code>ring-offset</code>, so the ring is drawn on top of the element border rather than floating outside it — making it hard to distinguish the focus ring from the border color change.<br><br>
The correct visual: <strong>2px ring + 2px offset</strong> on elements sitting on surface/canvas backgrounds. On the header (dark background), the ring draws directly without offset (ring appears clearly against navy).
</div>
<div class="issue-fix">
<strong>Fix:</strong> Standardize on <code>focus-visible:ring-2 focus-visible:ring-focus-ring focus-visible:ring-offset-2</code> for all buttons, links, and icon buttons on light backgrounds. For form inputs: <code>focus:ring-2 focus:ring-focus-ring focus:ring-offset-0</code> (inputs need the ring to show on click, not just keyboard nav; offset-0 because the ring replaces the border-color signal). For header elements: <code>focus-visible:ring-2 focus-visible:ring-focus-ring</code> (no offset — ring reads clearly on navy bg).
</div>
</div>
</div>
<!-- ═══════════════════════════════════════════════════════════
2. TOKEN DEFINITION
════════════════════════════════════════════════════════════ -->
<div class="sec">
<div class="sec-h"><span class="sec-num">2</span> Token Definition</div>
<div class="sg sg-2">
<div>
<div class="sl">Semantic Token Values</div>
<table class="tok-table">
<thead>
<tr>
<th>CSS Variable</th>
<th>Mode</th>
<th>Value</th>
<th>Contrast (typical bg)</th>
<th>WCAG</th>
</tr>
</thead>
<tbody>
<tr>
<td><code style="font-size:10px">--c-focus-ring</code></td>
<td>Light</td>
<td>
<div class="swatch-pair">
<span class="swatch" style="background:#012851"></span>
<span class="hex">#012851</span>
</div>
</td>
<td>14:1 on white · 13:1 on sand (#f0efe9)</td>
<td><span class="tag-ok">AAA ✓</span></td>
</tr>
<tr>
<td><code style="font-size:10px">--c-focus-ring</code></td>
<td>Dark</td>
<td>
<div class="swatch-pair">
<span class="swatch" style="background:#a1dcd8;border-color:#0d3358"></span>
<span class="hex">#a1dcd8</span>
</div>
</td>
<td>14.3:1 on #010e1e · 9.2:1 on #011526</td>
<td><span class="tag-ok">AAA ✓</span></td>
</tr>
<tr>
<td colspan="2" style="color:#888;font-size:9px">Tailwind utility class</td>
<td colspan="3"><code style="font-size:10px">ring-focus-ring</code> (via <code>--color-focus-ring: var(--c-focus-ring)</code>)</td>
</tr>
</tbody>
</table>
<div class="note" style="margin-top:14px">
<strong>Why these values?</strong> Light mode reuses <code>--c-primary</code> (#012851), which already scores 14:1 on white for text — a free contrast win. Dark mode reuses <code>--c-primary</code> dark value (#a1dcd8 brand-mint), which scores 14.3:1 on the darkest canvas. Both exceed WCAG AAA (7:1) and comfortably pass WCAG 2.4.11's 3:1 minimum.
</div>
</div>
<div>
<div class="sl">Why not reuse --c-primary?</div>
<div style="background:#fff;border:1px solid #E8E4DF;border-radius:6px;padding:14px;font-size:11px;line-height:1.7;color:#555">
<p style="margin-bottom:10px"><code style="font-size:10px;background:#f0efe9;padding:1px 4px;border-radius:3px">--c-primary</code> is used for button backgrounds and interactive text. If we map <code>--c-focus-ring</code> to the same value as <code>--c-primary</code>, the token works identically — and that is exactly the right choice here.</p>
<p style="margin-bottom:10px">The distinction matters for clarity: <code>--c-focus-ring</code> is a semantic token with a specific purpose (focus indicators). Even if it resolves to the same hex today, a future redesign can update one without touching the other.</p>
<p>Having an explicit <code>--c-focus-ring</code> also makes it immediately clear in component code that a focus style is intentional, not an accidental color reuse.</p>
</div>
<div class="warn-note" style="margin-top:14px">
<strong>Do not use <code>--c-accent</code> as a focus ring in light mode.</strong> <code>--c-accent</code> in light mode is #a1dcd8, a mint that scores only 1.52:1 on white backgrounds — decorative use only. This is the root cause of the current WCAG 2.4.11 failures in the header.
</div>
</div>
</div>
</div>
<!-- ═══════════════════════════════════════════════════════════
3. ELEMENT GALLERY
════════════════════════════════════════════════════════════ -->
<div class="sec">
<div class="sec-h"><span class="sec-num">3</span> Element Gallery — Focus States</div>
<p style="font-size:11px;color:#666;margin-bottom:24px">Each element shown idle (no focus) and focused. Left panel = light mode. Right panel = dark mode. Mockup values are ~55% scale.</p>
<!-- 3A: Text Inputs -->
<div class="sg sg-2" style="margin-bottom:32px">
<div class="sb">
<div class="sl">3A — Text Inputs &amp; Textareas <span class="mode-label lm">Light</span></div>
<div class="mock-frame mock-lm">
<div class="mock-body">
<div>
<div class="mock-label">Idle</div>
<input class="demo-input-lm-idle" type="text" value="Hans Müller" readonly>
</div>
<div>
<div class="mock-label">Focused (keyboard or click)</div>
<input class="demo-input-lm" type="text" value="Hans Müller" readonly>
</div>
<div>
<div class="mock-label">Focused + error border</div>
<input class="demo-input-lm-error" type="text" value="" placeholder="Pflichtfeld" readonly>
</div>
</div>
</div>
<div class="sc">Ring: 2px navy #012851 · Offset: 0px (ring sits flush against border) · Border also changes to focus-ring color on focus</div>
</div>
<div class="sb">
<div class="sl">3A — Text Inputs &amp; Textareas <span class="mode-label dm">Dark</span></div>
<div class="mock-frame mock-dm">
<div class="mock-body">
<div>
<div class="mock-label">Idle</div>
<input class="demo-input-dm-idle" type="text" value="Hans Müller" readonly>
</div>
<div>
<div class="mock-label">Focused</div>
<input class="demo-input-dm" type="text" value="Hans Müller" readonly>
</div>
</div>
</div>
<div class="sc">Ring: 2px mint #a1dcd8 · Offset: 0px · Border also changes to ring-focus-ring</div>
</div>
</div>
<div class="impl-ref">
<div class="impl-ref-hdr">Implementation Reference — Text Inputs / Textareas / Selects
<span>Real values · mockup above is ~55% scale</span>
</div>
<table>
<thead><tr><th>Element</th><th>Tailwind classes to ADD / CHANGE</th><th>Real size</th><th>Notes</th></tr></thead>
<tbody>
<tr>
<td>Any text input, textarea, select</td>
<td><code>focus:outline-none focus:ring-2 focus:ring-focus-ring focus:border-focus-ring</code></td>
<td><span class="ir-px">ring 2px</span></td>
<td>Use <code>focus:</code> not <code>focus-visible:</code> — user must see which text field is active even on mouse click. Remove any <code>focus:ring-ink</code>, <code>focus:ring-accent</code>, <code>focus:ring-primary</code>, <code>focus:ring-black</code>. Do NOT add ring-offset — offset-0 is correct for inputs.</td>
</tr>
<tr>
<td>Error-state input (focused)</td>
<td><code>focus:ring-focus-ring</code> (ring color stays navy/mint)</td>
<td><span class="ir-px">ring 2px</span></td>
<td>The error border color (red-400) is the border — the focus ring is always ring-focus-ring. Two separate visual signals: border = validation state, ring = keyboard position.</td>
</tr>
<tr>
<td>Files / components to update</td>
<td colspan="3"><code>WhoWhenSection.svelte</code> · <code>DescriptionSection.svelte</code> · <code>TranscriptionSection.svelte</code> · <code>SearchFilterBar.svelte</code> · <code>ConversationFilterBar.svelte</code> (both instances) · <code>forgot-password/+page.svelte</code> · <code>PanelHistory.svelte</code> · <code>MentionEditor.svelte</code> · <code>UserPasswordSection.svelte</code> · <code>UserProfileSection.svelte</code> · <code>PersonalInfoForm.svelte</code> · <code>PasswordChangeForm.svelte</code> · <code>PersonTypeahead.svelte</code></td>
</tr>
</tbody>
</table>
</div>
<!-- 3B: Buttons -->
<div class="sg sg-2" style="margin-bottom:32px;margin-top:36px">
<div class="sb">
<div class="sl">3B — Buttons (Primary &amp; Ghost) <span class="mode-label lm">Light</span></div>
<div class="mock-frame mock-lm">
<div class="mock-body">
<div class="demo-row">
<div>
<div class="mock-label">Primary idle</div>
<button class="demo-btn-lm-idle">Speichern</button>
</div>
<div>
<div class="mock-label">Primary focused</div>
<button class="demo-btn-lm-primary">Speichern</button>
</div>
</div>
<div class="demo-row">
<div>
<div class="mock-label">Ghost focused</div>
<button class="demo-btn-lm-ghost">Abbrechen</button>
</div>
</div>
</div>
</div>
<div class="sc">Ring: 2px navy #012851 · Offset: 2px (white gap) · focus-visible only</div>
</div>
<div class="sb">
<div class="sl">3B — Buttons (Primary &amp; Ghost) <span class="mode-label dm">Dark</span></div>
<div class="mock-frame mock-dm">
<div class="mock-body">
<div class="demo-row">
<div>
<div class="mock-label">Primary focused</div>
<button class="demo-btn-dm-primary">Speichern</button>
</div>
<div>
<div class="mock-label">Ghost focused</div>
<button class="demo-btn-dm-ghost">Abbrechen</button>
</div>
</div>
</div>
</div>
<div class="sc">Ring: 2px mint #a1dcd8 · Offset: 2px (dark canvas gap) · focus-visible only</div>
</div>
</div>
<div class="impl-ref">
<div class="impl-ref-hdr">Implementation Reference — Buttons
<span>Real values · mockup above is ~55% scale</span>
</div>
<table>
<thead><tr><th>Element</th><th>Tailwind classes</th><th>Real size</th><th>Notes</th></tr></thead>
<tbody>
<tr>
<td>Any button (primary, ghost, destructive)</td>
<td><code>focus:outline-none focus-visible:ring-2 focus-visible:ring-focus-ring focus-visible:ring-offset-2</code></td>
<td><span class="ir-px">ring 2px, offset 2px</span></td>
<td><code>focus-visible:</code> not <code>focus:</code> — buttons only need the ring for keyboard navigation, not mouse clicks. Min height 44px for all touch targets.</td>
</tr>
<tr>
<td>ring-offset color (light)</td>
<td>Tailwind default <code>ring-offset-white</code> or omit (default = white)</td>
<td></td>
<td>On light backgrounds the default white offset is correct. On sand background (<code>bg-canvas</code>), add <code>focus-visible:ring-offset-canvas</code> so the gap matches the page background.</td>
</tr>
<tr>
<td>ring-offset color (dark)</td>
<td><code>focus-visible:ring-offset-canvas</code></td>
<td></td>
<td>Critical: without this, the white offset flashes on dark backgrounds. Add <code>focus-visible:ring-offset-canvas</code> to all buttons that appear on dark/canvas backgrounds.</td>
</tr>
</tbody>
</table>
</div>
<!-- 3C: Icon Buttons (Header) -->
<div class="sg sg-2" style="margin-bottom:32px;margin-top:36px">
<div class="sb">
<div class="sl">3C — Icon Buttons on Header <span class="mode-label lm">Light (header = navy bg)</span></div>
<div class="mock-frame" style="background:#012851;border-color:#0a3d6b">
<div class="mock-body">
<div class="demo-row">
<div>
<div class="mock-label" style="color:rgba(255,255,255,.4)">Idle</div>
<span class="demo-icon-lm-idle" style="background:rgba(255,255,255,.1);color:#fff">🔔</span>
</div>
<div>
<div class="mock-label" style="color:rgba(255,255,255,.4)">Focused</div>
<span class="demo-nav-lm">🔔</span>
</div>
<div>
<div class="mock-label" style="color:rgba(255,255,255,.4)">Nav link focused</div>
<span class="demo-nav-lm">Dokumente</span>
</div>
<div>
<div class="mock-label" style="color:rgba(255,255,255,.4)">Idle link</div>
<span class="demo-nav-lm-idle">Personen</span>
</div>
</div>
</div>
</div>
<div class="sc">Ring: 2px mint #a1dcd8 · No offset (ring reads clearly on navy) · focus-visible only</div>
</div>
<div class="sb">
<div class="sl">3C — Icon Buttons on Header <span class="mode-label dm">Dark (header = mid-navy)</span></div>
<div class="mock-frame" style="background:#01335e;border-color:#0a3d6b">
<div class="mock-body">
<div class="demo-row">
<div>
<div class="mock-label" style="color:rgba(255,255,255,.4)">Focused</div>
<span class="demo-nav-dm">🔔</span>
</div>
<div>
<div class="mock-label" style="color:rgba(255,255,255,.4)">Nav link focused</div>
<span class="demo-nav-dm">Dokumente</span>
</div>
</div>
</div>
</div>
<div class="sc">Ring: 2px mint #a1dcd8 · 1px navy offset (#01335e) so ring floats · focus-visible only</div>
</div>
</div>
<div class="impl-ref">
<div class="impl-ref-hdr">Implementation Reference — Header Icon Buttons &amp; Nav Links
<span>Real values · mockup above is ~55% scale</span>
</div>
<table>
<thead><tr><th>Element</th><th>Tailwind classes</th><th>Real size</th><th>Notes</th></tr></thead>
<tbody>
<tr>
<td>Header icon button (light + dark)</td>
<td><code>focus:outline-none focus-visible:ring-2 focus-visible:ring-focus-ring</code></td>
<td><span class="ir-px">ring 2px, no offset</span></td>
<td>No ring-offset: ring appears directly on navy bg. In light mode, <code>ring-focus-ring</code> = navy → same as header bg → use mint instead? No: the navy header IS the background, so the ring colour is always mint (token resolves correctly in both modes). Contrast of mint (#a1dcd8) on navy header (#012851) = 4.1:1 — WCAG AA ✓.</td>
</tr>
<tr>
<td>AppNav links (desktop)</td>
<td>Remove <code>focus-visible:ring-accent</code>, add <code>focus-visible:ring-focus-ring</code></td>
<td><span class="ir-px">ring 2px</span></td>
<td>Files: <code>AppNav.svelte</code> lines 44, 54, 64, 74, 86, 145, 155, 165, 176</td>
</tr>
<tr>
<td>UserMenu avatar + close</td>
<td>Remove <code>focus-visible:ring-accent</code>, add <code>focus-visible:ring-focus-ring</code></td>
<td><span class="ir-px">ring 2px</span></td>
<td>File: <code>UserMenu.svelte</code> lines 36, 47</td>
</tr>
<tr>
<td>ThemeToggle, LanguageSwitcher, NotificationBell</td>
<td>Remove <code>focus-visible:ring-accent</code>, add <code>focus-visible:ring-focus-ring</code></td>
<td><span class="ir-px">ring 2px</span></td>
<td>Files: <code>ThemeToggle.svelte:34</code>, <code>LanguageSwitcher.svelte:15</code>, <code>NotificationBell.svelte:157</code></td>
</tr>
</tbody>
</table>
</div>
<!-- 3D: Tag chips and Person chips -->
<div class="sg sg-2" style="margin-bottom:32px;margin-top:36px">
<div class="sb">
<div class="sl">3D — Tag / Person Chips with Close Button <span class="mode-label lm">Light</span></div>
<div class="mock-frame mock-lm">
<div class="mock-body">
<div>
<div class="mock-label">Chip — close button focused</div>
<div class="demo-chip-lm">
Briefe
<button class="demo-chip-lm-close">×</button>
</div>
</div>
<div>
<div class="mock-label">Chip — close button idle (no focus)</div>
<div class="demo-chip-lm">
Briefe
<button class="demo-chip-lm-close-idle">×</button>
</div>
</div>
<div style="margin-top:4px">
<div class="mock-label">Input inside chip container (focused)</div>
<div style="border:1.5px solid #a1dcd8;background:#fff;border-radius:3px;padding:4px 8px;display:flex;gap:6px;align-items:center;box-shadow:0 0 0 2px #f0efe9, 0 0 0 4px #012851">
<div class="demo-chip-lm" style="box-shadow:none">Briefe <button class="demo-chip-lm-close-idle">×</button></div>
<span style="font-size:8px;color:#012851;opacity:.5;font-style:italic">Weiteres Tag…</span>
</div>
</div>
</div>
</div>
<div class="sc">Close button: 2px navy ring, no offset. Inner text input: 2px navy ring on the wrapping container.</div>
</div>
<div class="sb">
<div class="sl">3D — Tag / Person Chips with Close Button <span class="mode-label dm">Dark</span></div>
<div class="mock-frame mock-dm">
<div class="mock-body">
<div>
<div class="mock-label">Chip — close button focused</div>
<div class="demo-chip-dm">
Briefe
<button class="demo-chip-dm-close">×</button>
</div>
</div>
</div>
</div>
<div class="sc">Close button: 2px mint ring, no offset. Touch target: min 44×44px (chip row padding pads to this).</div>
</div>
</div>
<div class="impl-ref">
<div class="impl-ref-hdr">Implementation Reference — Chip Close Buttons &amp; TagInput / PersonMultiSelect
<span>Real values · mockup above is ~55% scale</span>
</div>
<table>
<thead><tr><th>Element</th><th>Tailwind classes</th><th>Real size</th><th>Notes</th></tr></thead>
<tbody>
<tr>
<td>Chip close button (×)</td>
<td><code>focus:outline-none focus-visible:ring-2 focus-visible:ring-focus-ring rounded</code></td>
<td><span class="ir-px">ring 2px, no offset</span></td>
<td>CURRENTLY: <code>focus:outline-none</code> with no ring — zero visible focus indicator. This is a hard WCAG 2.4.11 fail. Files: <code>PersonMultiSelect.svelte:94</code>, <code>TagInput.svelte:98</code>.</td>
</tr>
<tr>
<td>TagInput inner text input</td>
<td><code>focus:outline-none focus:ring-2 focus:ring-focus-ring</code></td>
<td><span class="ir-px">ring 2px</span></td>
<td>CURRENTLY: <code>outline-none focus:ring-0</code> — completely suppressed. Remove both classes, add the focus ring. File: <code>TagInput.svelte:125</code>. The ring appears on the input itself (narrow), which is visually fine inside the chip container.</td>
</tr>
<tr>
<td>PersonMultiSelect inner input</td>
<td><code>focus:outline-none focus:ring-2 focus:ring-focus-ring</code></td>
<td><span class="ir-px">ring 2px</span></td>
<td>File: <code>PersonMultiSelect.svelte:117</code> — same <code>outline-none focus:ring-0</code> pattern. Same fix.</td>
</tr>
<tr>
<td>Touch target for close button</td>
<td><code>min-h-[44px] min-w-[44px]</code> or ensure chip row is ≥44px tall</td>
<td><span class="ir-px">44×44px min</span></td>
<td>Most commonly undersized element — the × button inside chips is often 2024px. Pad the chip row to 44px height or use <code>p-2</code> on the button itself.</td>
</tr>
</tbody>
</table>
</div>
<!-- 3E: Checkboxes -->
<div class="sg sg-2" style="margin-bottom:32px;margin-top:36px">
<div class="sb">
<div class="sl">3E — Checkboxes <span class="mode-label lm">Light</span></div>
<div class="mock-frame mock-lm">
<div class="mock-body">
<div class="demo-row">
<label style="display:flex;align-items:center;gap:6px;font-size:9px;color:#012851;cursor:default">
<span class="demo-checkbox-lm"></span> Aktiv
</label>
<label style="display:flex;align-items:center;gap:6px;font-size:9px;color:#012851;cursor:default">
<span style="width:13px;height:13px;border-radius:2px;border:1.5px solid #e4e2d7;background:#fff;flex-shrink:0;display:inline-block"></span> Inaktiv (idle)
</label>
</div>
</div>
</div>
<div class="sc">Ring: 2px navy #012851 · Offset: 2px white gap · Standard Tailwind checkbox pattern</div>
</div>
<div class="sb">
<div class="sl">3E — Checkboxes <span class="mode-label dm">Dark</span></div>
<div class="mock-frame mock-dm">
<div class="mock-body">
<div class="demo-row">
<label style="display:flex;align-items:center;gap:6px;font-size:9px;color:#f0efe9;cursor:default">
<span class="demo-checkbox-dm"></span> Aktiv (focused)
</label>
</div>
</div>
</div>
<div class="sc">Ring: 2px mint #a1dcd8 · Offset: 2px dark canvas gap</div>
</div>
</div>
<div class="impl-ref">
<div class="impl-ref-hdr">Implementation Reference — Checkboxes
<span>Real values · mockup above is ~55% scale</span>
</div>
<table>
<thead><tr><th>Element</th><th>Tailwind classes</th><th>Real size</th><th>Notes</th></tr></thead>
<tbody>
<tr>
<td>input[type=checkbox]</td>
<td><code>rounded focus:ring-2 focus:ring-focus-ring focus:ring-offset-2</code></td>
<td><span class="ir-px">ring 2px, offset 2px</span></td>
<td>CURRENTLY: <code>focus:ring-accent</code> — fails light mode. File: <code>UserGroupsSection.svelte:21</code>. Tailwind does not need explicit <code>focus:outline-none</code> for checkboxes — it resets the outline via the preflight layer.</td>
</tr>
<tr>
<td>ring-offset-color (dark)</td>
<td><code>focus:ring-offset-canvas</code></td>
<td></td>
<td>Without this, the 2px ring offset shows as white on dark backgrounds. Add alongside the ring classes.</td>
</tr>
</tbody>
</table>
</div>
<!-- 3F: Notifications pills -->
<div style="margin-top:36px">
<div class="sl">3F — Filter Pills (Notifications Page)</div>
<div class="note">
<strong>Current pattern is structurally correct</strong><code>focus-visible:ring-2 focus-visible:ring-offset-2</code> with <code>ring-accent</code>. Only change needed: replace <code>ring-accent</code><code>ring-focus-ring</code>.<br><br>
File: <code>notifications/+page.svelte</code> lines 114, 129, 152, 167 — four pill variants. Each currently has <code>focus-visible:ring-2 focus-visible:ring-accent focus-visible:ring-offset-2</code>.
</div>
</div>
</div>
<!-- ═══════════════════════════════════════════════════════════
4. CSS IMPLEMENTATION
════════════════════════════════════════════════════════════ -->
<div class="sec">
<div class="sec-h"><span class="sec-num">4</span> CSS Implementation — Exact Diff</div>
<p style="font-size:11px;color:#666;margin-bottom:20px">Apply these changes first — all component-level fixes depend on the token existing.</p>
<div class="diff-block">
<div class="diff-block-hdr">frontend/src/routes/layout.css — token additions</div>
<pre><span class="diff-context">/* ─── 3. Semantic tokens ───────────────────────────────────────────────────── */
@theme inline {</span>
<span class="diff-context"> /* ... existing tokens ... */</span>
<span class="diff-add">+
+ /* Focus ring */
+ --color-focus-ring: var(--c-focus-ring);</span>
<span class="diff-context">}</span>
<span class="diff-context">/* ─── 4. Light mode (default) ─────────────────────────────────────────────── */
:root {</span>
<span class="diff-context"> /* ... existing tokens ... */</span>
<span class="diff-add">+
+ /* Focus ring — brand-navy on white/sand = 14:1 WCAG AAA */
+ --c-focus-ring: #012851;</span>
<span class="diff-context">}</span>
<span class="diff-context">/* ─── 5. Dark mode ─────────────────────────────────────────────────────────── */
@media (prefers-color-scheme: dark) {
:root:not([data-theme='light']) {</span>
<span class="diff-context"> /* ... existing tokens ... */</span>
<span class="diff-add">+
+ /* Focus ring — brand-mint on dark canvas = 14.3:1 WCAG AAA */
+ --c-focus-ring: #a1dcd8;</span>
<span class="diff-context"> }
}</span>
<span class="diff-context">/* Manual dark override */
:root[data-theme='dark'] {</span>
<span class="diff-context"> /* ... existing tokens ... */</span>
<span class="diff-add">+
+ --c-focus-ring: #a1dcd8;</span>
<span class="diff-context">}</span></pre>
</div>
<div class="diff-block" style="margin-top:16px">
<div class="diff-block-hdr">Component changes — ring-accent → ring-focus-ring (header elements)</div>
<pre><span class="diff-file">AppNav.svelte (lines 44, 54, 64, 74, 86, 145, 155, 165, 176):</span>
<span class="diff-remove">- focus-visible:ring-accent</span>
<span class="diff-add">+ focus-visible:ring-focus-ring</span>
<span class="diff-file">UserMenu.svelte (lines 36, 47):</span>
<span class="diff-remove">- focus-visible:ring-accent</span>
<span class="diff-add">+ focus-visible:ring-focus-ring</span>
<span class="diff-file">ThemeToggle.svelte (line 34):</span>
<span class="diff-remove">- focus-visible:ring-accent</span>
<span class="diff-add">+ focus-visible:ring-focus-ring</span>
<span class="diff-file">LanguageSwitcher.svelte (line 15):</span>
<span class="diff-remove">- focus-visible:ring-accent</span>
<span class="diff-add">+ focus-visible:ring-focus-ring</span>
<span class="diff-file">NotificationBell.svelte (line 157):</span>
<span class="diff-remove">- focus-visible:ring-accent</span>
<span class="diff-add">+ focus-visible:ring-focus-ring</span>
<span class="diff-file">notifications/+page.svelte (lines 114, 129, 152, 167):</span>
<span class="diff-remove">- focus-visible:ring-accent</span>
<span class="diff-add">+ focus-visible:ring-focus-ring</span></pre>
</div>
<div class="diff-block" style="margin-top:16px">
<div class="diff-block-hdr">Component changes — form inputs (ring-ink → ring-focus-ring + add ring-2)</div>
<pre><span class="diff-file">WhoWhenSection.svelte, DescriptionSection.svelte, TranscriptionSection.svelte,
SearchFilterBar.svelte, ConversationFilterBar.svelte (×2),
forgot-password/+page.svelte, UserPasswordSection.svelte, UserProfileSection.svelte,
PersonalInfoForm.svelte, PasswordChangeForm.svelte:</span>
<span class="diff-remove">- focus:border-ink focus:ring-ink
- (or) focus:border-ink focus:outline-none
- (or) focus:border-ink focus:ring-1 focus:ring-ink</span>
<span class="diff-add">+ focus:outline-none focus:ring-2 focus:ring-focus-ring focus:border-focus-ring</span>
<span class="diff-file">PanelHistory.svelte (lines 305, 320):</span>
<span class="diff-remove">- focus:ring-1 focus:ring-accent focus:outline-none</span>
<span class="diff-add">+ focus:outline-none focus:ring-2 focus:ring-focus-ring</span>
<span class="diff-file">MentionEditor.svelte (line 190):</span>
<span class="diff-remove">- focus:ring-1 focus:ring-accent focus:outline-none</span>
<span class="diff-add">+ focus:outline-none focus:ring-2 focus:ring-focus-ring</span></pre>
</div>
<div class="diff-block" style="margin-top:16px">
<div class="diff-block-hdr">Component changes — critical fixes (outline suppressed with no replacement)</div>
<pre><span class="diff-file">PersonMultiSelect.svelte (line 94) — chip close button:</span>
<span class="diff-remove">- class="ml-0.5 text-ink/50 hover:text-red-500 focus:outline-none"</span>
<span class="diff-add">+ class="ml-0.5 text-ink/50 hover:text-red-500 focus:outline-none focus-visible:ring-2 focus-visible:ring-focus-ring rounded"</span>
<span class="diff-file">PersonMultiSelect.svelte (line 117) — inner text input:</span>
<span class="diff-remove">- class="min-w-[120px] flex-1 border-none bg-transparent p-1 text-sm outline-none focus:ring-0"</span>
<span class="diff-add">+ class="min-w-[120px] flex-1 border-none bg-transparent p-1 text-sm outline-none focus:ring-2 focus:ring-focus-ring"</span>
<span class="diff-file">TagInput.svelte (line 98) — chip close button:</span>
<span class="diff-remove">- class="text-ink/50 hover:text-red-500 focus:outline-none"</span>
<span class="diff-add">+ class="text-ink/50 hover:text-red-500 focus:outline-none focus-visible:ring-2 focus-visible:ring-focus-ring rounded"</span>
<span class="diff-file">TagInput.svelte (line 125) — inner text input:</span>
<span class="diff-remove">- class="h-full w-full border-none bg-transparent p-1 text-sm outline-none focus:ring-0"</span>
<span class="diff-add">+ class="h-full w-full border-none bg-transparent p-1 text-sm outline-none focus:ring-2 focus:ring-focus-ring"</span>
<span class="diff-file">UserGroupsSection.svelte (line 21) — checkbox:</span>
<span class="diff-remove">- class="rounded border-line text-ink focus:ring-accent"</span>
<span class="diff-add">+ class="rounded border-line text-ink focus:ring-2 focus:ring-focus-ring focus:ring-offset-2"</span>
<span class="diff-file">PersonTypeahead.svelte (lines 157158) — both input variants:</span>
<span class="diff-remove">- focus:border-primary focus:outline-none
- focus:border-accent focus:ring-accent</span>
<span class="diff-add">+ focus:outline-none focus:ring-2 focus:ring-focus-ring focus:border-focus-ring</span>
<span class="diff-file">PersonTypeahead.svelte (line 163) — dropdown list:</span>
<span class="diff-remove">- ring-1 ring-black</span>
<span class="diff-add">+ ring-1 ring-line (decorative shadow border — not a focus ring, leave as semantic border color)</span></pre>
</div>
<div class="warn-note" style="margin-top:16px">
<strong>Tailwind 4 ring default is 1px</strong> (changed from 3px in v3). Always specify <code>ring-2</code> explicitly — never rely on the default. Any component that has only <code>focus:ring</code> without a width is getting a 1px ring, which is too thin for WCAG 2.4.11 (the indicator perimeter area requirement).
</div>
</div>
<!-- ═══════════════════════════════════════════════════════════
5. AUDIT TABLE
════════════════════════════════════════════════════════════ -->
<div class="sec">
<div class="sec-h"><span class="sec-num">5</span> Full Audit — Current vs Target</div>
<table class="audit-table">
<thead>
<tr>
<th>File</th>
<th>Current ring color</th>
<th>Current ring width</th>
<th>Light contrast</th>
<th>Status</th>
<th>Change</th>
</tr>
</thead>
<tbody>
<tr>
<td>AppNav.svelte (×9)</td>
<td><code>ring-accent</code></td>
<td><code>ring-2</code></td>
<td>1.52:1 on navy bg</td>
<td><span class="tag-bad">Fail 2.4.11</span></td>
<td><code>ring-focus-ring</code> (mint on navy = 4.1:1 ✓)</td>
</tr>
<tr>
<td>UserMenu.svelte (×2)</td>
<td><code>ring-accent</code></td>
<td><code>ring-2</code></td>
<td>1.52:1 on navy bg</td>
<td><span class="tag-bad">Fail 2.4.11</span></td>
<td><code>ring-focus-ring</code></td>
</tr>
<tr>
<td>ThemeToggle.svelte</td>
<td><code>ring-accent</code></td>
<td><code>ring-2</code></td>
<td>1.52:1 on navy bg</td>
<td><span class="tag-bad">Fail 2.4.11</span></td>
<td><code>ring-focus-ring</code></td>
</tr>
<tr>
<td>LanguageSwitcher.svelte</td>
<td><code>ring-accent</code></td>
<td><code>ring-2</code></td>
<td>1.52:1 on navy bg</td>
<td><span class="tag-bad">Fail 2.4.11</span></td>
<td><code>ring-focus-ring</code></td>
</tr>
<tr>
<td>NotificationBell.svelte</td>
<td><code>ring-accent</code></td>
<td><code>ring-2</code></td>
<td>1.52:1 on navy bg</td>
<td><span class="tag-bad">Fail 2.4.11</span></td>
<td><code>ring-focus-ring</code></td>
</tr>
<tr>
<td>notifications/+page.svelte (×4)</td>
<td><code>ring-accent</code></td>
<td><code>ring-2</code></td>
<td>1.52:1 (white offset)</td>
<td><span class="tag-bad">Fail 2.4.11</span></td>
<td><code>ring-focus-ring</code></td>
</tr>
<tr>
<td>PersonMultiSelect.svelte chip close</td>
<td>none (outline-none, no ring)</td>
<td></td>
<td>0 — invisible</td>
<td><span class="tag-bad">Fail 2.4.11</span></td>
<td>Add <code>focus-visible:ring-2 focus-visible:ring-focus-ring</code></td>
</tr>
<tr>
<td>PersonMultiSelect.svelte inner input</td>
<td>none (outline-none focus:ring-0)</td>
<td></td>
<td>0 — invisible</td>
<td><span class="tag-bad">Fail 2.4.11</span></td>
<td>Add <code>focus:ring-2 focus:ring-focus-ring</code></td>
</tr>
<tr>
<td>TagInput.svelte chip close</td>
<td>none (outline-none, no ring)</td>
<td></td>
<td>0 — invisible</td>
<td><span class="tag-bad">Fail 2.4.11</span></td>
<td>Add <code>focus-visible:ring-2 focus-visible:ring-focus-ring</code></td>
</tr>
<tr>
<td>TagInput.svelte inner input</td>
<td>none (outline-none focus:ring-0)</td>
<td></td>
<td>0 — invisible</td>
<td><span class="tag-bad">Fail 2.4.11</span></td>
<td>Add <code>focus:ring-2 focus:ring-focus-ring</code></td>
</tr>
<tr>
<td>UserGroupsSection.svelte checkbox</td>
<td><code>ring-accent</code></td>
<td>default (1px)</td>
<td>1.52:1 on white</td>
<td><span class="tag-bad">Fail 2.4.11</span></td>
<td><code>ring-2 ring-focus-ring ring-offset-2</code></td>
</tr>
<tr>
<td>PanelHistory.svelte inputs (×2)</td>
<td><code>ring-accent</code></td>
<td><code>ring-1</code> (thin)</td>
<td>1.52:1 on white</td>
<td><span class="tag-bad">Fail 2.4.11</span></td>
<td><code>ring-2 ring-focus-ring</code></td>
</tr>
<tr>
<td>MentionEditor.svelte textarea</td>
<td><code>ring-accent</code></td>
<td><code>ring-1</code> (thin)</td>
<td>1.52:1 on white</td>
<td><span class="tag-bad">Fail 2.4.11</span></td>
<td><code>ring-2 ring-focus-ring</code></td>
</tr>
<tr>
<td>PersonTypeahead.svelte input (compact)</td>
<td><code>border-primary</code> only</td>
<td>no ring</td>
<td>border only, no ring</td>
<td><span class="tag-bad">No ring</span></td>
<td>Add <code>ring-2 ring-focus-ring</code></td>
</tr>
<tr>
<td>PersonTypeahead.svelte input (standard)</td>
<td><code>ring-accent</code></td>
<td>default (1px)</td>
<td>1.52:1 on white</td>
<td><span class="tag-bad">Fail 2.4.11</span></td>
<td><code>ring-2 ring-focus-ring</code></td>
</tr>
<tr>
<td>WhoWhenSection, DescriptionSection, TranscriptionSection inputs</td>
<td><code>ring-ink</code></td>
<td>default (1px)</td>
<td>14:1 on white ✓</td>
<td><span class="tag-ok">Color OK</span> <span class="tag-bad">Width thin</span></td>
<td><code>ring-2 ring-focus-ring</code> (and add <code>border-focus-ring</code>)</td>
</tr>
<tr>
<td>Profile forms, password forms</td>
<td><code>border-ink</code> only (outline-none, no ring)</td>
<td>no ring</td>
<td>border only</td>
<td><span class="tag-bad">No ring</span></td>
<td>Add <code>ring-2 ring-focus-ring</code> alongside <code>border-focus-ring</code></td>
</tr>
<tr>
<td>SearchFilterBar, ConversationFilterBar inputs</td>
<td><code>ring-ink</code></td>
<td>default (1px)</td>
<td>14:1 on white ✓</td>
<td><span class="tag-ok">Color OK</span> <span class="tag-bad">Width thin</span></td>
<td><code>ring-2 ring-focus-ring</code></td>
</tr>
<tr>
<td>forgot-password/+page.svelte input</td>
<td><code>ring-ink</code></td>
<td><code>ring-1</code> (thin)</td>
<td>14:1 on white ✓</td>
<td><span class="tag-ok">Color OK</span> <span class="tag-bad">Width thin</span></td>
<td><code>ring-2 ring-focus-ring</code></td>
</tr>
</tbody>
</table>
</div>
<!-- ═══════════════════════════════════════════════════════════
6. ACCEPTANCE CRITERIA
════════════════════════════════════════════════════════════ -->
<div class="sec">
<div class="sec-h"><span class="sec-num">6</span> Acceptance Criteria</div>
<div style="background:#fff;border:1px solid #E8E4DF;border-radius:8px;padding:20px 24px;font-size:12px;line-height:1.9">
<div style="font-size:8px;font-weight:800;text-transform:uppercase;letter-spacing:1px;color:#888;margin-bottom:14px">Issue #167 — All items must pass before closing</div>
<div style="display:flex;flex-direction:column;gap:6px">
<label style="display:flex;gap:10px;align-items:flex-start;cursor:default"><input type="checkbox" style="margin-top:3px;flex-shrink:0"> <span><code style="background:#f0efe9;padding:1px 4px;border-radius:3px;font-size:11px">--c-focus-ring</code> token defined in <code style="background:#f0efe9;padding:1px 4px;border-radius:3px;font-size:11px">layout.css</code> in all three blocks: <code>:root</code>, <code>@media (prefers-color-scheme: dark) :root:not([data-theme='light'])</code>, and <code>:root[data-theme='dark']</code></span></label>
<label style="display:flex;gap:10px;align-items:flex-start;cursor:default"><input type="checkbox" style="margin-top:3px;flex-shrink:0"> <span><code style="background:#f0efe9;padding:1px 4px;border-radius:3px;font-size:11px">--color-focus-ring: var(--c-focus-ring)</code> added to <code>@theme inline</code> block</span></label>
<label style="display:flex;gap:10px;align-items:flex-start;cursor:default"><input type="checkbox" style="margin-top:3px;flex-shrink:0"> <span>All 5 header components (AppNav, UserMenu, ThemeToggle, LanguageSwitcher, NotificationBell) updated from <code>ring-accent</code><code>ring-focus-ring</code></span></label>
<label style="display:flex;gap:10px;align-items:flex-start;cursor:default"><input type="checkbox" style="margin-top:3px;flex-shrink:0"> <span>All 4 notifications page filter pills updated from <code>ring-accent</code><code>ring-focus-ring</code></span></label>
<label style="display:flex;gap:10px;align-items:flex-start;cursor:default"><input type="checkbox" style="margin-top:3px;flex-shrink:0"> <span>TagInput chip close button: <code>focus:outline-none</code> replaced with <code>focus:outline-none focus-visible:ring-2 focus-visible:ring-focus-ring</code></span></label>
<label style="display:flex;gap:10px;align-items:flex-start;cursor:default"><input type="checkbox" style="margin-top:3px;flex-shrink:0"> <span>TagInput inner text input: <code>focus:ring-0</code> removed, <code>focus:ring-2 focus:ring-focus-ring</code> added</span></label>
<label style="display:flex;gap:10px;align-items:flex-start;cursor:default"><input type="checkbox" style="margin-top:3px;flex-shrink:0"> <span>PersonMultiSelect chip close button: same fix as TagInput close button</span></label>
<label style="display:flex;gap:10px;align-items:flex-start;cursor:default"><input type="checkbox" style="margin-top:3px;flex-shrink:0"> <span>PersonMultiSelect inner input: <code>focus:ring-0</code> removed, <code>focus:ring-2 focus:ring-focus-ring</code> added</span></label>
<label style="display:flex;gap:10px;align-items:flex-start;cursor:default"><input type="checkbox" style="margin-top:3px;flex-shrink:0"> <span>All form inputs (text, textarea, select) updated to <code>ring-2 ring-focus-ring</code> — no remaining <code>ring-ink</code>, <code>ring-accent</code>, <code>ring-primary</code>, <code>ring-black</code>, or bare <code>ring-1</code></span></label>
<label style="display:flex;gap:10px;align-items:flex-start;cursor:default"><input type="checkbox" style="margin-top:3px;flex-shrink:0"> <span>UserGroupsSection checkbox updated to <code>ring-2 ring-focus-ring ring-offset-2</code></span></label>
<label style="display:flex;gap:10px;align-items:flex-start;cursor:default"><input type="checkbox" style="margin-top:3px;flex-shrink:0"> <span>Keyboard-navigate all major pages in light mode — every focusable element shows a visible navy ring at ≥ 3:1 contrast against its background</span></label>
<label style="display:flex;gap:10px;align-items:flex-start;cursor:default"><input type="checkbox" style="margin-top:3px;flex-shrink:0"> <span>Keyboard-navigate all major pages in dark mode — every focusable element shows a visible mint ring at ≥ 3:1 contrast against its background</span></label>
<label style="display:flex;gap:10px;align-items:flex-start;cursor:default"><input type="checkbox" style="margin-top:3px;flex-shrink:0"> <span>Tab through TagInput and PersonMultiSelect — chip close buttons show a visible focus ring when reached by keyboard</span></label>
<label style="display:flex;gap:10px;align-items:flex-start;cursor:default"><input type="checkbox" style="margin-top:3px;flex-shrink:0"> <span>No element in the app has <code>focus:outline-none</code> or <code>outline: none</code> without a corresponding <code>focus-visible:ring-*</code> or <code>focus:ring-*</code> replacement</span></label>
</div>
</div>
</div>
</div><!-- /doc -->
</body>
</html>