excalidraw/excalidraw
Reviewed against Rams quality heuristics: accessibility, color, typography, spacing, components, motion, UX, and craft.
30 files reviewed·August 1, 2026
More findings
Verdict
Good bones, sloppy finish: a token-based spacing system undercut by inline margins, and structurally clean components still carrying Docusaurus boilerplate copy. The real risk is the unreachable Stats panel close control, a one-line fix that's currently a hard keyboard lockout.
Files Rams reviewed
dev-docs/src/pages/index.tsx
examples/with-nextjs/src/app/layout.tsx
examples/with-nextjs/src/app/page.tsx
dev-docs/src/components/Homepage/index.tsx
dev-docs/src/pages/index.module.css
examples/with-nextjs/src/pages/excalidraw-in-pages.tsx
packages/excalidraw/components/Stats/index.tsx
dev-docs/src/components/Homepage/styles.module.css
examples/with-script-in-browser/components/CustomFooter.tsx
examples/with-script-in-browser/components/ExampleApp.scss
excalidraw-app/components/AI.tsx
excalidraw-app/components/AppMainMenu.tsx
excalidraw-app/components/AppSidebar.scss
excalidraw-app/components/AppSidebar.tsx
excalidraw-app/components/AppWelcomeScreen.tsx
excalidraw-app/components/DebugCanvas.tsx
excalidraw-app/components/ExportToExcalidrawPlus.tsx
excalidraw-app/components/TopErrorBoundary.tsx
packages/excalidraw/components/Actions.scss
packages/excalidraw/components/ActiveConfirmDialog.tsx
packages/excalidraw/components/BraveMeasureTextError.tsx
packages/excalidraw/components/Button.tsx
packages/excalidraw/components/Card.scss
packages/excalidraw/components/CheckboxItem.scss
packages/excalidraw/components/ColorPicker/ColorInput.tsx
packages/excalidraw/components/ColorPicker/ColorPicker.scss
packages/excalidraw/components/ColorPicker/ColorPicker.tsx
packages/excalidraw/components/ColorPicker/CustomColorList.tsx
packages/excalidraw/components/ColorPicker/Picker.tsx
packages/excalidraw/components/ColorPicker/PickerColorList.tsx
Accessibility
packages/excalidraw/components/Stats/index.tsx:220
Stats panel close control is unreachable by keyboard
The close control in the Stats panel is a plain <div className="close" onClick={onClose}>{CloseIcon}</div> with no tabIndex, role, keydown handler, or aria-label. It's an icon-only div, not a <button>.
Why it matters
Keyboard-only and screen reader users cannot tab to this control or activate it at all, so they have no way to close the stats panel once it's open.
Fix
Use a native <button> for any clickable control so it gets keyboard focus, Enter/Space activation, and an accessible name for free.
<div className="close" onClick={onClose}>
{CloseIcon}
</div><button type="button" className="close" onClick={onClose} aria-label={t("buttons.close")}>
{CloseIcon}
</button>examples/with-script-in-browser/components/CustomFooter.tsx:66
Comment tool button relies on a tooltip for its only accessible name
The second footer Button renders only {COMMENT_SVG} as its child and depends on title="Comments!" for identification, with no aria-label or visible text.
Why it matters
The title attribute is inconsistently exposed across screen readers and never appears for keyboard users until a mouse hovers, so this control can go unannounced or unexplained for assistive tech users trying to find the comment tool.
Fix
Pair icon-only controls with an explicit aria-label rather than relying on title alone for the accessible name.
title="Comments!"
>
{COMMENT_SVG}
</Button> title="Comments!"
aria-label="Comments!"
>
{COMMENT_SVG}
</Button>dev-docs/src/components/Homepage/index.tsx:47
Feature icons announce as unlabeled images to screen readers
Each Feature icon in the homepage grid renders as <Svg className={styles.featureSvg} role="img" /> with no aria-label or <title>. Three icons (mountain, tree, react) sit above the headings 'Easy to Use', 'Focus on What Matters', and 'Powered by React' but carry no accessible name of their own.
Why it matters
A screen reader hits role="img" and announces 'image' with no description, giving blind users a dead stop before reaching the heading that actually explains the feature.
Fix
Give every element carrying role="img" an aria-label that matches its adjacent heading.
<Svg className={styles.featureSvg} role="img" /><Svg className={styles.featureSvg} role="img" aria-label={title} />Spacing
packages/excalidraw/components/Stats/index.tsx:285
Inline marginTop: 12 on elementStats bypasses the spacing scale
The #elementStats wrapper sets style={{ marginTop: 12 }} directly inline instead of a class, in a file where Card.scss and Actions.scss expose custom-property spacing tokens.
Why it matters
A hardcoded pixel value here means this one panel's spacing drifts independently the next time the token scale changes, and nobody editing the shared tokens will catch it.
Fix
Move one-off inline spacing values into a class or custom property so they track the shared spacing scale.
<div
id="elementStats"
style={{
marginTop: 12,
}}
><div
id="elementStats"
className="ElementStats"
>packages/excalidraw/components/Stats/index.tsx:331
Arbitrary rem margin on stats-element-type row skips the spacing scale
The StatsRow with data-testid="stats-element-type" sets style={{ margin: "0.3125rem 0" }} inline, an odd non-scale value (5px) that isn't reused as a named token anywhere else in the file.
Why it matters
A one-off decimal rem value like this is easy to lose track of and hard to reconcile with the rest of the panel's spacing, so future edits to this row's spacing won't propagate anywhere else it's needed.
Fix
Replace arbitrary inline margin values with a shared spacing class or token from the existing scale.
<StatsRow
heading
data-testid="stats-element-type"
style={{ margin: "0.3125rem 0" }}
><StatsRow
heading
data-testid="stats-element-type"
className="StatsRow--tight"
>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 freeCraft
dev-docs/src/components/Homepage/index.tsx:13
All three feature headings are unedited Docusaurus boilerplate copy
FeatureList titles read "Easy to Use", "Focus on What Matters", and "Powered by React", paired with descriptions still referencing "Docusaurus" directly (e.g. 'Docusaurus was designed from the ground up...').
Why it matters
Generic scaffold copy on a homepage tells visitors nothing about what this specific project does, so the section fails to communicate any real value before they scroll past it.
Fix
Replace template copy with headings and descriptions specific to this project's actual features.
title: "Easy to Use",title: "<project-specific feature title>",Typography
Color
Components
Motion
UX
Working well
- The Stats panel's Collapsible sections use one consistent bitmask pattern (STATS_PANELS.generalStats, elementProperties) for both open/close states, so the expand logic reads the same way across panels instead of each one inventing its own toggle convention.
- The Feature component in dev-docs/src/components/Homepage/index.tsx maps over a typed FeatureList array instead of hand-copying three near-identical JSX blocks, which keeps future feature additions to a single data entry rather than a duplicated component.
Scored August 1, 2026 with Rams Engine v0.0.3 · Engine changelog
First scored May 13, 2026: 59/100. This rescore on v0.0.3: 59/100.
This page is an automated design review of excalidraw/excalidraw’s UI code: 30 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.