Skip to content

Commit 6dc3cf2

Browse files
Reduce motion for arrow key scrolling in Carousel (#1549)
Implement reduced motion preferences for users when scrolling with arrow keys in the Carousel component, enhancing accessibility for those who prefer less motion. The component now respects reduced motion when both using the arrow buttons and arrow keys. For regular scrolling/swiping it still does not. This would require some advanced tweaking and it isn't strictly required: *"For example, if scrolling a page causes elements to move (other than the essential movement associated with scrolling the content, which is under the user's control)"* (https://www.w3.org/WAI/WCAG22/Techniques/css/C39). Scrolling or swiping left/right in the `Carousel` is under the users control. And users with a reduced motion preference can avoid this by using the arrow buttons/keys.
1 parent 37ca479 commit 6dc3cf2

File tree

2 files changed

+43
-3
lines changed

2 files changed

+43
-3
lines changed

.changeset/dark-knives-brush.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@obosbbl/grunnmuren-react": patch
3+
---
4+
5+
Movments in the `Carousel` are now eliminated when using arrow keys for users that has a reduced motion preferrence

packages/react/src/carousel/carousel.tsx

Lines changed: 38 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -272,6 +272,8 @@ const Carousel = ({
272272
carouselItemsRef,
273273
onScroll,
274274
activeIndex: scrollTargetIndex,
275+
handlePrevious,
276+
handleNext,
275277
},
276278
],
277279
[
@@ -370,6 +372,8 @@ type CarouselItemsContextValue = {
370372
carouselItemsRef: React.Ref<HTMLDivElement>;
371373
onScroll?: (event: React.UIEvent<HTMLDivElement>) => void;
372374
activeIndex: number;
375+
handlePrevious?: () => void;
376+
handleNext?: () => void;
373377
};
374378

375379
const CarouselItemsContext = createContext({
@@ -378,6 +382,28 @@ const CarouselItemsContext = createContext({
378382
} as CarouselItemsContextValue);
379383

380384
const CarouselItems = ({ className, children }: CarouselItemsProps) => {
385+
const {
386+
carouselItemsRef,
387+
onScroll,
388+
activeIndex,
389+
handlePrevious,
390+
handleNext,
391+
} = useContext(CarouselItemsContext);
392+
393+
const prefersReducedMotion = useRef(
394+
window.matchMedia('(prefers-reduced-motion: reduce)').matches,
395+
);
396+
397+
// Update the ref when the media query changes
398+
useEffect(() => {
399+
const mediaQuery = window.matchMedia('(prefers-reduced-motion: reduce)');
400+
const handleChange = (e: MediaQueryListEvent) => {
401+
prefersReducedMotion.current = e.matches;
402+
};
403+
mediaQuery.addEventListener('change', handleChange);
404+
return () => mediaQuery.removeEventListener('change', handleChange);
405+
}, []);
406+
381407
const handleKeyDown = (event: React.KeyboardEvent<HTMLDivElement>) => {
382408
// Prevent default behavior when holding down arrow keys (when repeat is true)
383409
// The default behavior in scroll snapping causes a staggering scroll effect that feels janky
@@ -386,11 +412,20 @@ const CarouselItems = ({ className, children }: CarouselItemsProps) => {
386412
(event.key === 'ArrowLeft' || event.key === 'ArrowRight')
387413
) {
388414
event.preventDefault();
415+
return;
389416
}
390-
};
391417

392-
const { carouselItemsRef, onScroll, activeIndex } =
393-
useContext(CarouselItemsContext);
418+
// For users with prefers-reduced-motion, trigger button click behavior instead of native scroll
419+
if (prefersReducedMotion.current) {
420+
if (event.key === 'ArrowLeft' && handlePrevious) {
421+
event.preventDefault();
422+
handlePrevious();
423+
} else if (event.key === 'ArrowRight' && handleNext) {
424+
event.preventDefault();
425+
handleNext();
426+
}
427+
}
428+
};
394429

395430
return (
396431
// biome-ignore lint/a11y/noStaticElementInteractions: The keydown handler is only to prevent undesired scrolling behavior when using the arrow keys

0 commit comments

Comments
 (0)