fix(ui): single focus ring (no double-ring) + neutralize stagger delay under reduced motion

Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
Assisted-by: Claude:claude-opus-4-8 [Claude Code]
This commit is contained in:
Ettore Di Giacinto
2026-06-18 11:17:34 +00:00
parent 76f3e032ae
commit f9a130e446
3 changed files with 24 additions and 8 deletions

View File

@@ -26,3 +26,19 @@ test.describe('Editorial design system', () => {
expect(name).toBe('pageReveal')
})
})
test.describe('reduced motion', () => {
test('stagger animation-delay is neutralized under reduced motion', async ({ page }) => {
// Emulate prefers-reduced-motion explicitly. (The fixture-option form
// test.use({ reducedMotion }) does not propagate through our extended
// coverage `page` fixture, so set it on the page directly.)
await page.emulateMedia({ reducedMotion: 'reduce' })
await page.goto('/app') // Home renders .reveal-stagger children
// .home-status-line is staggerStyle(1) -> 60ms delay without the fix.
const child = page.locator('.home-status-line').first()
await expect(child).toBeVisible({ timeout: 15_000 })
const delay = await child.evaluate(el => getComputedStyle(el).animationDelay)
// Under reduced motion the per-child delay must be ~0 (not 60ms+).
expect(parseFloat(delay)).toBeLessThan(0.05)
})
})

View File

@@ -1668,20 +1668,17 @@
box-shadow: 0 0 0 3px var(--color-focus-ring);
}
/* Global focus ring any interactive that isn't a .btn */
/* Global focus ring - any interactive that isn't a .btn. This box-shadow ring
is the single focus technique app-wide (it covers .btn plus the :where(...)
list below). The strengthened --color-focus-ring keeps it WCAG-AA visible
(>=3:1), so no separate solid-outline rule is needed; a bare :focus-visible
outline here would double-ring every element. */
:where(a, button, input, select, textarea, [tabindex]:not([tabindex="-1"])):focus-visible {
outline: none;
box-shadow: 0 0 0 3px var(--color-focus-ring);
border-radius: var(--radius-sm);
}
/* Solid outline fallback so the focus ring stays AA-visible (>=3:1) on every
interactive element, including those that clip or override box-shadow. */
:focus-visible {
outline: 2px solid var(--color-focus-ring);
outline-offset: 2px;
}
.btn-primary {
background: var(--color-primary);
color: var(--color-primary-text);

View File

@@ -93,6 +93,9 @@ a:hover {
@media (prefers-reduced-motion: reduce) {
*, *::before, *::after {
animation-duration: 0.01ms !important;
/* Neutralize per-child stagger delays (e.g. .reveal-stagger) so content
does not sit at the hidden "from" keyframe before snapping in. */
animation-delay: 0s !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
scroll-behavior: auto !important;