From grimoire
Adds pause/play controls to auto-advancing content (carousels, slideshows, session timeouts) to meet WCAG 2.1 SC 2.2.1, 2.2.2, and 2.2.6 accessibility requirements.
How this skill is triggered — by the user, by Claude, or both
Slash command
/grimoire:apply-timed-content-controlsThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Provide user controls to pause, stop, or extend moving content and session timelines — so users with motor or cognitive disabilities are not blocked by content that advances without their control.
Provide user controls to pause, stop, or extend moving content and session timelines — so users with motor or cognitive disabilities are not blocked by content that advances without their control.
Adopted by: WCAG 2.1 SCs 2.2.1 and 2.2.2 (Level A) are required by Section 508 (US),
EU EN 301 549, and UK PSBAR 2018. SC 2.2.6 (Level AA) is required by jurisdictions mandating
WCAG 2.1 AA. The WAI-ARIA Carousel Pattern provides the reference implementation for accessible
auto-advancing carousels. UK GOV.UK Design System and the US Web Design System both require pause
controls on animated content.
Impact: Users with motor disabilities who have slow response times cannot interact with
auto-advancing content before it advances. Users with cognitive disabilities who read slowly cannot
process content before it disappears. WHO reports 16% of adults have a significant motor or cognitive
disability affecting interaction speed. Auto-advancing carousels — used on 72% of major e-commerce
homepages — fail WCAG 2.2.2 without a pause button.
Why best: CSS prefers-reduced-motion respects a system-level preference but does not give users
control over specific auto-advancing elements. WCAG 2.2.2 requires an explicit user-controlled pause
mechanism — not just a media query. Session timeouts without warning (failing 2.2.6) are a separate
problem: users with cognitive disabilities completing forms slowly lose unsaved data.
Sources: W3C WCAG 2.1 SCs 2.2.1, 2.2.2, 2.2.6 (2018); WAI-ARIA Carousel Pattern; GOV.UK Design System; NIST SP 800-63B
Any content that moves, blinks, scrolls, or auto-updates for more than 5 seconds must have a mechanism to pause, stop, or hide it:
<section aria-label="Featured products" aria-roledescription="carousel">
<button id="carousel-pause" aria-label="Pause carousel" type="button">⏸ Pause</button>
<div id="carousel-slides" aria-live="off">
<div role="group" aria-label="Slide 1 of 3" aria-roledescription="slide">
<!-- slide content -->
</div>
</div>
</section>
let isPlaying = true;
let intervalId = setInterval(advanceSlide, 5000);
const pauseBtn = document.getElementById('carousel-pause');
const slides = document.getElementById('carousel-slides');
pauseBtn.addEventListener('click', () => {
if (isPlaying) {
clearInterval(intervalId);
isPlaying = false;
pauseBtn.textContent = '▶ Play';
pauseBtn.setAttribute('aria-label', 'Play carousel');
slides.setAttribute('aria-live', 'off');
} else {
intervalId = setInterval(advanceSlide, 5000);
isPlaying = true;
pauseBtn.textContent = '⏸ Pause';
pauseBtn.setAttribute('aria-label', 'Pause carousel');
}
});
// Also pause when keyboard focus enters the carousel
const carousel = document.querySelector('[aria-roledescription="carousel"]');
carousel.addEventListener('focusin', () => clearInterval(intervalId));
carousel.addEventListener('focusout', () => { if (isPlaying) intervalId = setInterval(advanceSlide, 5000); });
carousel.addEventListener('mouseenter', () => clearInterval(intervalId));
carousel.addEventListener('mouseleave', () => { if (isPlaying) intervalId = setInterval(advanceSlide, 5000); });
If a session timeout will cause data loss, warn the user at least 20 seconds before expiry and offer extension:
<!-- Warning dialog — present in DOM at page load, shown on timeout warning -->
<div id="timeout-dialog" role="alertdialog" aria-modal="true"
aria-labelledby="timeout-title" aria-describedby="timeout-desc" hidden>
<h2 id="timeout-title">Session expiring soon</h2>
<p id="timeout-desc">
Your session will expire in <span id="countdown">2:00</span>.
Unsaved changes will be lost.
</p>
<button id="extend-btn" onclick="extendSession()">Extend session</button>
<button onclick="saveAndLogOut()">Save and log out</button>
</div>
<!-- Screen reader announcement region -->
<div id="timeout-live" role="status" aria-live="polite" aria-atomic="true"></div>
const SESSION_MS = 20 * 60 * 1000; // 20 minutes
const WARNING_MS = 2 * 60 * 1000; // warn 2 minutes before
let sessionTimer = setTimeout(expireSession, SESSION_MS);
let warningTimer = setTimeout(showWarning, SESSION_MS - WARNING_MS);
function showWarning() {
document.getElementById('timeout-dialog').removeAttribute('hidden');
document.getElementById('extend-btn').focus();
document.getElementById('timeout-live').textContent =
'Session expires in 2 minutes. Choose Extend session to continue.';
startCountdown();
}
function extendSession() {
clearTimeout(sessionTimer);
clearTimeout(warningTimer);
document.getElementById('timeout-dialog').setAttribute('hidden', '');
document.getElementById('timeout-live').textContent = 'Session extended by 20 minutes.';
sessionTimer = setTimeout(expireSession, SESSION_MS);
warningTimer = setTimeout(showWarning, SESSION_MS - WARNING_MS);
}
Auto-updating tables or dashboards must offer a pause/disable control:
<div class="live-data-header">
<h2>Live stock prices</h2>
<label>
<input type="checkbox" id="auto-refresh" checked>
Auto-refresh every 30 seconds
</label>
</div>
<div role="status" aria-live="polite" id="refresh-status"></div>
<table id="stock-table"><!-- ... --></table>
let refreshInterval = setInterval(refreshData, 30000);
document.getElementById('auto-refresh').addEventListener('change', (e) => {
if (!e.target.checked) {
clearInterval(refreshInterval);
document.getElementById('refresh-status').textContent = 'Auto-refresh paused.';
} else {
refreshData();
refreshInterval = setInterval(refreshData, 30000);
document.getElementById('refresh-status').textContent = 'Auto-refresh enabled.';
}
});
prefers-reduced-motion instead (apply-reduced-motion).Carousel pauses visually but aria-live keeps announcing. When paused, set aria-live="off"
on the slides container so screen readers stop announcing slide changes.
Timeout dialog doesn't move focus. The timeout warning is a dialog — move focus to the "Extend
session" button when it appears (apply-focus-management). Restore focus to the user's previous
element when dismissed.
setInterval drift in countdown display. setInterval drifts over long sessions. Track
remaining time using Date.now() deltas rather than decrementing a counter.
npx claudepluginhub jeffreytse/grimoire --plugin grimoireImplements animations that respect user motion preferences, avoid seizure triggers, and provide pause controls. Covers CSS media queries, JavaScript checks, and React hooks.
Provides patterns for audio/video autoplay with accessible pause, stop, or mute controls per WCAG 1.4.2. Use when adding auto-playing media to ensure screen reader compatibility.
Audits and improves web accessibility following WCAG 2.2 guidelines, including POUR principles, alt text for images/icons, color contrast, and focus states. Use for a11y audits and compliance.