pmndrs/zustand
Reviewed against Rams quality heuristics — accessibility, hierarchy, color, typography, spacing, components, motion, and UX.
12 files reviewed·May 13, 2026
Top fix
Add prefers-reduced-motion guard to stop looping animation harming vestibular users.
See the fixAlso worth noting
Verdict
Accessibility is the consistent failure mode — icon buttons, canvas elements, and language labels all invisible to assistive tech. The looping animation with no reduced-motion guard is the one issue that actively harms vestibular users.
Files Rams reviewed
examples/demo/src/components/CodePreview.jsx
examples/demo/src/components/CopyButton.jsx
examples/demo/src/components/Fireflies.jsx
examples/demo/src/components/Scene.jsx
examples/demo/src/components/Details.jsx
examples/demo/src/components/SnippetLang.jsx
examples/starter/src/index.tsx
examples/demo/src/pmndrs.css
examples/demo/src/styles.css
examples/demo/src/App.jsx
examples/demo/src/main.jsx
examples/starter/src/index.css
Accessibility
Icon-only button has no accessible name for screen readers
The <button className="copy-button"> renders only an SVG copy icon when `isCopied` is false. There is no aria-label, no visually-hidden text, and no title element on the SVG. Screen readers announce this as an unlabeled button.
Why it matters
Screen reader users encounter a button with no name and have no way to know what it does — keyboard-only users are blocked from understanding the control's purpose.
Fix
Every icon-only button needs an aria-label that names the action.
<button className="copy-button" onClick={handleCopy} {...props}><button className="copy-button" onClick={handleCopy} aria-label={isCopied ? 'Copied' : 'Copy code'} {...props}>language="tsx" mismatches the selected lang state — screen readers get the wrong label
The `<Highlight>` component has `language="tsx"` hardcoded, but the `lang` state toggles between `'javascript'` and `'typescript'`. The rendered language attribute on the `<pre>` element will always announce "tsx" regardless of which code is displayed, misleading assistive technology and syntax highlighters that use the language value for token parsing.
Why it matters
Screen readers and tooling that consume the `language-*` class on `<pre>` will always see "tsx" — users who switch to JavaScript get a mismatched code context, and the language toggle becomes semantically invisible.
Fix
Derive the Highlight language prop from the active lang state so the rendered output matches what is displayed.
<Highlight code={code} language="tsx" theme={undefined}><Highlight code={code} language={lang === 'javascript' ? 'javascript' : 'typescript'} theme={undefined}>Canvas element has no ARIA role or label, making it invisible to assistive tech
The `<canvas>` element returned by the `Canvas` component has no `role`, `aria-label`, or descriptive text. Screen readers encounter a nameless, purpose-unknown canvas — the entire animated scene is invisible to them.
Why it matters
Users on assistive tech get no information about this visual. Adding a role and label is a one-line fix that surfaces at least a description of what the canvas contains.
Fix
Add `role="img"` and `aria-label` to the canvas element to give screen readers a meaningful description of the decorative scene.
<canvas
ref={canvas}
style={{<canvas
ref={canvas}
role="img"
aria-label="Animated parallax forest scene with fireflies"
style={{Motion
Looping animation ignores prefers-reduced-motion, affecting vestibular users
The `useFrame` callback in `Fatline` unconditionally decrements `dashOffset` every frame, driving a permanent infinite animation. There is no check for `prefers-reduced-motion` and no way to pause it.
Why it matters
Users with vestibular disorders can experience nausea from continuous motion. An infinite looping animation with no reduced-motion path fails WCAG 2.3.3 and excludes a real subset of users.
Fix
Read the user's motion preference and skip the animation update when reduced motion is requested.
useFrame(
(state, delta) =>
(material.current.uniforms.dashOffset.value -= delta / 100),
)const prefersReduced = window.matchMedia('(prefers-reduced-motion: reduce)').matches
useFrame(
(state, delta) => {
if (!prefersReduced) {
material.current.uniforms.dashOffset.value -= delta / 100
}
},
)Want this on your repo?
Rams reviews your next PR automatically and posts inline fix suggestions.
Review my public repoFreeTypography
Counter digits shift layout on every increment without tabular-nums
The counter `<span className="text-3xl">{count}</span>` renders proportional digits. As `count` grows from single to double to triple digits, each character width differs, causing the span — and the button next to it — to shift horizontally on every click.
Why it matters
Layout shift on a counter is the canonical example of why `tabular-nums` exists. In a starter template this is a teaching moment: developers who copy this pattern will repeat the mistake in production dashboards and pricing displays.
Fix
Apply `tabular-nums` to any number that updates dynamically so digit-width changes don't cause layout shift.
<span className="text-3xl">{count}</span><span className="text-3xl tabular-nums">{count}</span>UX
Counter button missing explicit type, defaults to submit inside any form
The `<button onClick={inc}>one up</button>` has no `type` attribute. HTML buttons default to `type="submit"`, which would accidentally submit any ancestor form this component is ever placed inside.
Why it matters
If Counter is ever embedded in a form context, the button submits the form instead of incrementing — a silent, hard-to-debug regression with no visible warning.
Fix
Always set type="button" on action buttons that are not intended to submit a form.
<button onClick={inc}>one up</button><button type="button" onClick={inc} aria-label="Increment count">one up</button>Hierarchy
Color
Spacing
Components
Working well
- ✓Placing <SnippetLang> and <CopyButton> inside a <div className='snippet-container'> inside the <pre> is a deliberate layout choice that keeps the overlay controls semantically within the code block — so when the <pre> scrolls, the overlay can be positioned relative to it correctly. This is the right structural instinct, even if the inline style for position: relative should be moved to a class.
- ✓The error boundary pattern — catching WebGL init failure via `onError` and swapping to `<FallbackScene>` — is exactly right. Rather than letting a canvas crash silently or show a broken element, users get a meaningful static fallback image with proper alt text ('Zustand Bear'). This is the correct graceful degradation approach for any GPU-dependent content.
- ✓Colocating the Zustand store definition with the component that owns it is the right call for a scoped, single-use state like a language toggle. It avoids prop-drilling from a parent and keeps the language/code coupling explicit — the getCode selector that derives from lang makes the relationship between state and content readable at a glance.
- ✓Using `useAspect` for both `scaleN` and `scaleW` to drive plane sizes is clean responsive design at the 3D layer — the scene recomputes its geometry proportions based on actual viewport dimensions rather than hardcoding pixel values, which means the parallax layers fill the screen correctly at any aspect ratio.
Score your own repo with Rams.
Free on public repos. Reviews run on every pull request and post inline fix suggestions.
Review my public repoFree