bug(viewer): "Annotieren anzeigen" text has insufficient contrast in light mode #341
Reference in New Issue
Block a user
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Bug
The "Annotieren anzeigen" toggle label in the PDF viewer is unreadable in light mode — the text color is too close to the background.
Steps to reproduce
Expected behavior
Text contrast ratio ≥ 4.5:1 against its background (WCAG 2.1 AA).
Actual behavior
Label appears washed out / nearly invisible in light mode.
Acceptance criteria
🎨 Leonie Voss — UI/UX Design Lead & Accessibility Advocate
Observations
PdfControls.svelteline 94: whenshowAnnotationsisfalse, the inactive-state label getstext-accent. In light mode,--c-accentresolves to#a1dcd8, which the CSS file itself already documents as a WCAG failure:/* ⚠ accent — decorative use only ... Light mode: #a1dcd8 on white = 1.52:1 — WCAG FAIL. Never use as text colour. */. The codebase has the rule; the component broke it.showAnnotationsistrue, the label usestext-ink-2(#4b5563, 7.6:1 on white — AA pass). Only the inactive/off state is broken.--c-pdf-ctrl=#d8d8d8in light mode, not white.#a1dcd8on#d8d8d8is still below 1.8:1 — no improvement.bg-surface/10as its background when inactive, which is essentially transparent. That doesn't rescue the contrast either.--c-accentbecomes#00c7b1in dark, which is a visible turquoise on the dark--c-pdf-ctrlsurface (#011526). Contrast is approximately 6:1 — AA pass.Recommendations
text-accentin the inactive branch withtext-primary(--c-primary=#012851in light,#a1dcd8in dark — both pass AA). This aligns with the token's documented purpose: "Primary interactive."px-2 py-1(approximately 28×24px) — below the 44×44px minimum for the senior audience. While this is a pre-existing issue not introduced by this bug, the fix is a good moment to raise it. Considerpx-3 py-2 min-h-[44px].Open Decisions
text-primary(navy in light, mint in dark) for stronger visual distinction from the active state, ortext-ink-2(same as active) for visual consistency — and rely on the background fill alone to convey state? The current design pairs the fill (bg-surface/10) with the color change, but removing the color difference risks unclear state communication for color-blind users.👨💻 Felix Brandt — Fullstack Developer
Observations
/frontend/src/lib/components/PdfControls.svelteat line 92–94:text-accenttoken maps to--c-accentwhich is#a1dcd8in light mode — explicitly flagged inlayout.cssas decorative-only and a WCAG fail on any light surface. The fix is a one-token swap.PdfViewer.svelte.spec.tshas three tests covering navigation and zoom controls, but zero tests for the annotation toggle. The toggle is conditionally rendered ({#if annotationCount > 0}) so it has never been exercised in the test suite.PdfControls.svelteis a lean 125-line file — well within bounds. No splitting needed.$props()and passes specific typed props. No architecture issues.Recommendations
text-accentwithtext-primaryinPdfControls.svelte.--c-primaryis navy in light mode (14.5:1 on white) and mint in dark mode (9:1 on dark surface) — passes AA in both modes with no additional work.PdfViewer.svelte.spec.tsbefore touching the production code:annotationCountprop is passed in existing tests). Add a dedicatedPdfControls.svelte.spec.tsthat mounts the isolated control component — it doesn't need the full PDF rendering machinery.PdfViewer(the whole rendering component), but the annotation toggle lives inPdfControls. Consider extracting aPdfControls.svelte.spec.tsfor control-bar logic independent of PDF rendering.🏗️ Markus Keller — Application Architect
Observations
layout.cssis architecturally sound: raw palette constants are isolated in--palette-*, semantic tokens in--c-*, and Tailwind utilities generated from@theme inline. The bug is a misuse of the decorative--c-accenttoken for text — a usage violation, not a structural flaw.Recommendations
text-accent→text-primaryinPdfControls.svelte. Do not over-engineer.text-accentusage in text-color context. A comment-only convention in a CSS file is a weak guardrail; a lint rule is a hard one. This is a one-time investment that prevents the same class of mistake from recurring on any future component.layout.csstoken comment is good documentation. No ADR needed for this level of change — the existing comment is sufficient.Open Decisions
text-accentdecorative-only constraint via linting is worth discussing: it converts a convention into a machine-checked rule, but requires maintaining the lint configuration. Low effort, high leverage.🔒 Nora "NullX" Steiner — Application Security Engineer
Observations
Recommendations
PdfViewer.svelteshows annotations are rendered only whenshowAnnotationsis true — the toggle does control DOM presence, not just CSS visibility. This means the toggle is a functional control, not a cosmetic one, which reinforces why its label must be readable.🧪 Sara Holt — QA Engineer & Test Strategist
Observations
PdfViewer.svelte.spec.tscover navigation and zoom; none passannotationCount > 0to trigger the conditional branch. This is a coverage gap that allowed the contrast regression to ship undetected.wcag2aaaxe-playwright assertion on the PDF viewer page will catch color contrast failures automatically.Recommendations
PdfControlscomponent test (separate spec file, not insidePdfViewer.svelte.spec.ts) with the following cases:annotationCount > 0— verifies conditional branch.annotationCount === 0— verifies it stays hidden.showAnnotations: trueandfalse— verifies both i18n strings.This test runs in Vitest browser mode and takes under 1 second per case.
page.evaluate(() => document.documentElement.setAttribute('data-theme', 'dark'))).Open Decisions
PdfControls.svelte.spec.tsbe a standalone file, or should the annotation toggle tests be added toPdfViewer.svelte.spec.ts? Standalone is cleaner (tests the control in isolation without the heavy PDF mocking), but it adds a new file. The tests are independent either way.🛠️ Tobias Wendt — DevOps & Platform Engineer
Observations
svelte-checkbut has no automated accessibility gate (no axe-playwright job). This bug would have passed CI undetected.Recommendations
.sveltefile — no infrastructure involvement needed. Fast to ship.test:e2enpm script without requiring a separate runner or job.Open Decisions
📋 Elicit — Requirements Engineer
Observations
Recommendations
Strengthen the acceptance criteria to include an automatable criterion:
FR-A11Y-341-01 (Event): When a user views the PDF viewer in light mode with annotations present, the "Annotierungen anzeigen / verbergen" label shall have a contrast ratio ≥ 4.5:1 against its background in both toggle states.
FR-A11Y-341-02 (Event): When a user views the PDF viewer in dark mode with annotations present, the label shall have a contrast ratio ≥ 4.5:1 in both toggle states.
NFR-ACC-341-01: The PDF viewer page shall produce zero axe WCAG2AA violations (automated gate, not manual check).
Add "verified by automated axe-playwright assertion" to the Definition of Done for this issue so the fix cannot be closed on a manual check alone.
The milestone ("Demo Day — family get-together") is appropriate for a P1-high visible bug. No milestone change needed.
Open Decisions
🗳️ Decision Queue
Consolidated open decisions from the persona reviews. Each requires a human call — no right answer from the code alone.
Theme A: Inactive-state color choice
From Leonie:
Should the annotation toggle's inactive state use
text-primary(navy in light, mint in dark) for stronger visual distinction from the active state (text-ink-2), or should it usetext-ink-2(same color as active) for visual consistency — relying on the background fill (bg-surface/10) alone to convey the on/off state?text-primary= stronger state signal, but may look "too prominent" for an off state.text-ink-2= consistent with active, simpler, but color-blind users would rely solely on the background fill to detect state.Both options fix the contrast failure. The choice is a design tradeoff, not a correctness issue.
Theme B: Test file structure for annotation toggle tests
From Sara:
Should the new annotation toggle tests live in a standalone
PdfControls.svelte.spec.ts(clean isolation, no PDF mock needed), or be added to the existingPdfViewer.svelte.spec.ts(fewer files, but the component boundary is blurred)?Standalone is slightly cleaner; the existing file is already named for the wrong component. Low stakes either way.
Theme C: Enforce
text-accentdecorative-only rule via lintingFrom Markus:
The
layout.csshas a comment warning against using--c-accentas a text color, but this bug proves that a comment is not a guardrail. Should a Stylelint or custom ESLint rule be added to block futuretext-accent/text-brand-mintusage in text-color positions?A: text-primary
B: new PdfControls
C: yes, new lint ruel
Implementation complete — branch
feat/issue-341-annotieren-contrastWhat was done
Commit 1:
110da9b8—fix(viewer): replace text-accent with text-primary on annotation toggle inactive statePdfControls.svelteline 94: replacedtext-accentwithtext-primaryin the inactive toggle state class expression.text-primaryresolves to#012851in light mode (14.5:1 contrast ratio on white — AA pass) and#a1dcd8in dark mode (9:1 — AA pass).PdfControls.svelte.spec.ts(new file) with 5 tests:annotationCount > 0annotationCount === 0showAnnotations: falseshowAnnotations: truetext-primaryclass (nottext-accent) in inactive stateCommit 2:
379bc84e—fix(a11y): fix ProgressRing text label contrast and add no-restricted-syntax lint rule for text-accentProgressRing.svelteusedtext-accenton a percentage label in a ternary class expression. Fixed totext-primary.ProgressRing.svelte.spec.ts: test "renders a primary-colored label when percentage is > 0" now assertstext-primary.no-restricted-syntaxrule ineslint.config.js, scoped to*.sveltefiles only. It catchestext-accentin JavaScript string literals (ternary/conditional class expressions) — the exact pattern that caused both violations. Asserting'text-accent'in.spec.tstest files is unaffected.Decision Queue outcomes
text-primary— stronger visual distinction from active state ✅PdfControls.svelte.spec.ts✅no-restricted-syntaxrule ineslint.config.jsscoped to.sveltefiles ✅Test results
entity-nav.svelte.spec.ts) unrelated to these changesNext step
Open PR: http://heim-nas:3005/marcel/familienarchiv/pulls/new/feat/issue-341-annotieren-contrast