Rams MCP · The full engine, now in your coding agent
pmndrs on GitHub

pmndrs/zustand

Reviewed against Rams quality heuristics: accessibility, color, typography, spacing, components, motion, UX, and craft.

14 files reviewed·August 1, 2026

View on GitHub

Top fix

Add an upfront WebGL capability check before mounting the scene

See the fix

Verdict

A WebGL mascot with a fallback plan sounds resilient, but the fallback only fires after something breaks, not before. Colors are scattered as raw hex across buttons, shadows, and backdrops, so the palette will fracture as soon as either file grows.

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

examples/demo/index.html

examples/starter/index.html

96/100

Components

2 serious
ComponentsSerious

examples/starter/src/index.tsx:44

Mascot drop-shadow color is a hardcoded hex baked into inline style

The mascot `<img>` uses `style={{ filter: 'drop-shadow(0 0 2em #582d3e)' }}` to render its glow, a second unique hex value in the same file separate from the button's `#252b37`. Neither ties back to a shared token or CSS variable.

Why it matters

Two unrelated hex values in one small file means any theme or brand-color update requires hunting through inline styles and className strings separately, and the two colors will silently drift apart over time.

Fix

Move the drop-shadow color into a CSS custom property or Tailwind class so it can be updated in one place alongside other brand colors.

style={{
  filter: 'drop-shadow(0 0 2em #582d3e)',
}}
className="w-36 drop-shadow-mascot"
ComponentsSerious

examples/demo/src/components/Scene.jsx:154

Fallback scene background is a hardcoded hex with no shared source

`FallbackScene`'s wrapper `<div>` sets `background: '#010101'` directly in an inline style object rather than through a shared variable or class, alongside the `<img src="/ogimage.jpg" alt="Zustand Bear">` it centers.

Why it matters

If the app's dark background is defined anywhere else (canvas clear color, page background), this near-black gets out of sync the next time either value changes, producing a visible seam between the fallback and the real scene.

Fix

Reference a shared background token or CSS variable for the fallback's backdrop instead of a one-off hex value.

background: '#010101',
background: 'var(--color-canvas-bg)',
98/100

Accessibility

1 serious
AccessibilitySerious

examples/demo/src/components/Scene.jsx:200

WebGL scene mounts with no capability check before rendering

`Scene` renders `<Canvas><Experience /><Effects /></Canvas>` unconditionally and only swaps to `<FallbackScene />` after `onError` fires from inside the r3f root. There's no upfront check for WebGL support or a low-power/reduced-motion state before the GPU-heavy scene attempts to initialize.

Why it matters

Users on devices without WebGL, or with GPU drivers that fail silently rather than throwing, sit on a blank canvas with nothing rendered and no explanation, since the fallback path is reachable only through a thrown error.

Fix

Check for WebGL support before mounting the Canvas and render the fallback immediately when it's absent, instead of relying solely on a runtime error callback.

export default function Scene() {
  const [error, setError] = useState(null)

  if (error) {
    return <FallbackScene />
  }

  return (
    <Canvas onError={setError}>
      <Experience />
      <Effects />
    </Canvas>
  )
}
export default function Scene() {
  const [error, setError] = useState(null)
  const supportsWebGL = useMemo(() => hasWebGLSupport(), [])

  if (error || !supportsWebGL) {
    return <FallbackScene />
  }

  return (
    <Canvas onError={setError}>
      <Experience />
      <Effects />
    </Canvas>
  )
}

Get this score on every PR.

Rams reviews each pull request on your repo and posts inline one-click fixes — about a minute per review.

Install Rams free
98/100

Color

1 serious
Design SystemSerious

examples/starter/src/index.tsx:27

Counter button background is a raw hex value with no token backing

The "+1" button in `Counter` is styled with `className="bg-[#252b37] font-bold py-2 px-4 rounded"`, an arbitrary Tailwind bracket value instead of a theme color or CSS variable. There's no design-token layer in this starter, so this hex is invented on the spot and has no relationship to any other color in the app.

Why it matters

Every future button in this app has to guess or re-copy this exact hex string, so the palette drifts as soon as a second component needs a similar dark background.

Fix

Extract shared colors into a Tailwind theme token (e.g. `bg-surface`) so components reference one source instead of repeating hex literals.

<button
  className="bg-[#252b37] font-bold py-2 px-4 rounded"
  onClick={inc}
>
<button
  className="bg-surface font-bold py-2 px-4 rounded"
  onClick={inc}
>

Typography

No issues found

Spacing

No issues found

Motion

No issues found

UX

No issues found

Craft

No issues found

Working well

  • `Counter` keeps all state in the zustand store (`useStore((s) => s.count)`, `useStore((s) => s.inc)`) and leaves the component purely presentational, which keeps the increment logic testable in one place instead of scattered across the UI.
  • The `Canvas` helper in Scene.jsx cleanly separates r3f root creation, resize handling, and error boundary wiring into one function, keeping WebGL setup isolated from the rest of the app's layout code.
  • The `+1` button renders with visible text content, so it gets an accessible name for free with no extra ARIA needed, which is the simplest correct pattern for labeling interactive controls.

Scored August 1, 2026 with Rams Engine v0.0.3 · Engine changelog
First scored May 13, 2026: 94/100. This rescore on v0.0.3: 92/100.

This page is an automated design review of pmndrs/zustand’s UI code: 14 files read against 308 versioned rules covering accessibility, color, typography, spacing, components, UX, motion, and craft. The score is out of 100; confirmed criticals cap it — one at 59, two at 49, three or more at 39.

More design scores

Score your own repo.

Free on public repos, no account. The same engine that scored this page reads your UI code and mints a score page like this one.

Public repos only. The full engine reviews the UI code and mints a public score page — we email you the link too. Already-scored repos open instantly.

Or get a design review on every pull requestInstall Rams