Motion & accessibility
Edit on GitHubBlocks animate with motion/react and honour the system reduced-motion setting.
Motion is part of the craft, never a gimmick. Every block animates with intent — numbers roll, lines draw, rows slide in — and every one of those animations respects the visitor who prefers less motion.
The motion library
All animation is done with motion/react, the library behind Framer Motion. Blocks import a small set of primitives — motion, useInView, useReducedMotion and, where needed, animate and AnimatePresence. Nothing else is used for animation.
How blocks animate
- Entrance plays once, when the block scrolls into view, and never repeats.
- Easing is a soft ease-out curve like
[0.22, 1, 0.36, 1], with durations between 0.5 and 1.2 seconds and small, subtle staggers. - No bounce, no elastic, no infinite attention-seeking loops.
- Blocks animate transform, opacity and SVG path length, not layout properties, so motion stays smooth.
A block's entrance looks like this:
"use client";
import { useRef } from "react";
import { motion, useInView, useReducedMotion } from "motion/react";
export function Example() {
const reduce = useReducedMotion();
const ref = useRef<HTMLDivElement>(null);
const inView = useInView(ref, { once: true, margin: "-10% 0px" });
return (
<motion.div
ref={ref}
initial={reduce ? false : { opacity: 0, y: 16 }}
animate={inView ? { opacity: 1, y: 0 } : undefined}
transition={{ duration: 0.6, ease: [0.22, 1, 0.36, 1] }}
>
{/* your content */}
</motion.div>
);
}"use client";
import { useRef } from "react";
import { motion, useInView, useReducedMotion } from "motion/react";
export function Example() {
const reduce = useReducedMotion();
const ref = useRef<HTMLDivElement>(null);
const inView = useInView(ref, { once: true, margin: "-10% 0px" });
return (
<motion.div
ref={ref}
initial={reduce ? false : { opacity: 0, y: 16 }}
animate={inView ? { opacity: 1, y: 0 } : undefined}
transition={{ duration: 0.6, ease: [0.22, 1, 0.36, 1] }}
>
{/* your content */}
</motion.div>
);
}Reduced motion
Every animation is gated behind useReducedMotion(). When a visitor has enabled reduced motion in their system settings, the block renders its final state immediately — fully visible, with no movement.
Keyboard
Anything interactive is a real, focusable element:
- Buttons for every control, with a visible
focus-visible:ring-2ring. - Tooltips also appear on keyboard focus, not just hover.
- Charts are not keyboard traps — they expose their meaning through text, not only colour.
Screen readers
- Charts use
role="img"and anaria-labelthat summarises the data. - Live regions announce changes (for example the live feed) politely with
aria-live. - Text meets AA contrast, and colour is never the only signal for a value.
Building your own blocks? Follow the same rules and they will feel like part of the set. See the contributing guide for the full list.