diff --git a/frontend/src/lib/timeline/BucketHeaderChip.svelte b/frontend/src/lib/timeline/BucketHeaderChip.svelte
index d3fa1561..c5bdeb98 100644
--- a/frontend/src/lib/timeline/BucketHeaderChip.svelte
+++ b/frontend/src/lib/timeline/BucketHeaderChip.svelte
@@ -28,10 +28,12 @@ const TAG_COLORS = new Set([
let { name, color }: { name: string; color: string | null } = $props();
const token = $derived(color && TAG_COLORS.has(color) ? color : null);
+// The tint paints the chip's fill + dot only — never the label text. The saturated
+// --c-tag-* tokens used AS text over their own wash drop below WCAG AA 4.5:1 for the
+// light tokens (amber ≈3.0, sand ≈3.2, sage ≈3.4); a fixed dark ink keeps every token
+// legible while the 18% wash still reads as a genuinely tinted chip (REQ-015).
const chipStyle = $derived(
- token
- ? `background-color: color-mix(in srgb, var(--c-tag-${token}) 14%, transparent); color: var(--c-tag-${token})`
- : ''
+ token ? `background-color: color-mix(in srgb, var(--c-tag-${token}) 18%, transparent)` : ''
);
const dotStyle = $derived(token ? `background-color: var(--c-tag-${token})` : '');
@@ -44,7 +46,6 @@ const dotStyle = $derived(token ? `background-color: var(--c-tag-${token})` : ''
class:border={!token}
class:border-line={!token}
class:bg-surface={!token}
- class:text-ink-3={!token}
>
{m.timeline_tag_chip_label()}:
- {name}{name}
diff --git a/frontend/src/lib/timeline/BucketHeaderChip.svelte.spec.ts b/frontend/src/lib/timeline/BucketHeaderChip.svelte.spec.ts
index 61c926ad..24a60ba4 100644
--- a/frontend/src/lib/timeline/BucketHeaderChip.svelte.spec.ts
+++ b/frontend/src/lib/timeline/BucketHeaderChip.svelte.spec.ts
@@ -41,4 +41,17 @@ describe('BucketHeaderChip (REQ-015/009)', () => {
expect(document.body.textContent).toContain(evil);
expect(document.querySelector('img')).toBeNull();
});
+
+ it('paints the label in a fixed ink colour, never the saturated tag token (contrast, REQ-015)', () => {
+ // A saturated --c-tag-* token used as TEXT over its own wash fails 4.5:1 for the
+ // light tokens (amber/sand/sage ≈ 3:1). The tint must go to the background + dot;
+ // the label keeps a guaranteed-contrast ink token.
+ render(BucketHeaderChip, { name: 'Weihnachten', color: 'amber' });
+ const chip = document.querySelector('[data-testid="bucket-header-chip"]') as HTMLElement;
+ expect(chip.getAttribute('style') ?? '').not.toContain('color: var(--c-tag-');
+ const label = document.querySelector('[data-testid="bucket-header-chip-label"]') as HTMLElement;
+ expect(label.className).toContain('text-ink');
+ // still genuinely tinted — the token paints the wash and the dot
+ expect(document.body.innerHTML).toContain('var(--c-tag-amber)');
+ });
});