windmill-labs/windmill
Reviewed against Rams quality heuristics: accessibility, color, typography, spacing, components, motion, UX, and craft.
30 files reviewed·August 1, 2026
Elevated
Design risk in this codebase.
More findings
Verdict
A tree-based navigation UI built entirely around mouse interaction, keyboard and screen reader users can't reach folders at all. The biggest risk is structural: fixing semantics later means retrofitting focus order and roles across every row, not a quick patch.
Files Rams reviewed
frontend/src/lib/components/tutorials/app/BackgroundRunnablesTutorial.svelte
frontend/src/lib/components/tutorials/app/ConnectionTutorial.svelte
ai_evals/fixtures/frontend/app/initial/file_manager/frontend/components/FileItem.tsx
ai_evals/fixtures/frontend/app/initial/file_manager/frontend/components/FileList.tsx
ai_evals/fixtures/frontend/app/initial/file_manager/frontend/components/FolderTree.tsx
ai_evals/fixtures/frontend/app/initial/file_manager/frontend/components/Toolbar.tsx
ai_evals/fixtures/frontend/app/initial/shopping_cart/frontend/components/Cart.tsx
frontend/src/lib/components/tutorials/app/ExpressionEvaluationTutorial.svelte
ai_evals/fixtures/frontend/app/initial/file_manager/frontend/components/Breadcrumb.tsx
ai_evals/fixtures/frontend/app/initial/shopping_cart/frontend/components/ProductCard.tsx
ai_evals/fixtures/frontend/app/initial/shopping_cart/frontend/components/ProductList.tsx
ai_evals/fixtures/frontend/app/initial/file_manager/frontend/index.tsx
ai_evals/fixtures/frontend/app/initial/inventory_tracker/frontend/index.tsx
ai_evals/fixtures/frontend/app/initial/recipe_book/frontend/index.tsx
ai_evals/fixtures/frontend/app/initial/session_id_chat/frontend/index.tsx
ai_evals/fixtures/frontend/app/initial/shopping_cart/frontend/index.tsx
ai_evals/fixtures/frontend/app/initial/token_heavy_context/frontend/components/ReferencePanel.tsx
frontend/src/lib/components/AIAgentLogViewer.svelte
frontend/src/lib/components/AIProviderPicker.svelte
frontend/src/lib/components/AIReasoningEffortPicker.svelte
frontend/src/lib/components/AddUser.svelte
frontend/src/lib/components/ApiConnectForm.svelte
frontend/src/lib/components/AppConnectDrawer.svelte
frontend/src/lib/components/AppConnectLightweightResourcePicker.svelte
frontend/src/lib/components/ArgEnum.svelte
frontend/src/lib/components/ArgInfo.svelte
frontend/src/lib/components/ArrayTypeNarrowing.svelte
frontend/src/lib/components/AssignableTags.svelte
frontend/src/lib/components/AssignableTagsInner.svelte
frontend/src/lib/components/Auth0Setting.svelte
Accessibility
ai_evals/fixtures/frontend/app/initial/file_manager/frontend/components/FolderTree.tsx:20
Root folder row is a div, locking out keyboard-only users
In FolderTree.tsx, the 'Root' row ("📁 Root") is a <div onClick={() => onFolderSelect(null, 'Root')}> with no role, tabIndex, or key handler. There is no way to tab to it or activate it from the keyboard.
Why it matters
Keyboard-only and screen reader users cannot select the root folder at all, which cuts off the top level of navigation for anyone who can't use a mouse.
Fix
Use a native <button> for any element that triggers an action so it gets keyboard focus and activation for free.
<div
className={`px-4 py-2 cursor-pointer hover:bg-gray-100 ${
currentFolderId === null ? 'bg-blue-100 text-blue-700' : ''
}`}
onClick={() => onFolderSelect(null, 'Root')}
>
<span className="mr-2">📁</span>
Root
</div><button
type="button"
className={`w-full text-left px-4 py-2 cursor-pointer hover:bg-gray-100 ${
currentFolderId === null ? 'bg-blue-100 text-blue-700' : ''
}`}
onClick={() => onFolderSelect(null, 'Root')}
>
<span className="mr-2">📁</span>
Root
</button>ai_evals/fixtures/frontend/app/initial/file_manager/frontend/components/FolderTree.tsx:33
Every folder row in the tree is unreachable without a mouse
Each folder row rendered from folders.map (e.g. folder.name) is a <div onClick={() => onFolderSelect(folder.id, folder.name)}> with no button semantics, tabIndex, or onKeyDown. The entire recursive tree is mouse-only.
Why it matters
A keyboard or screen reader user can never descend into any subfolder, which makes the file manager's core navigation feature completely inaccessible to them.
Fix
Render interactive list items as <button> elements so focus order and Enter/Space activation come from the browser, not custom JS.
<div
className={`px-4 py-2 cursor-pointer hover:bg-gray-100 ${
currentFolderId === folder.id ? 'bg-blue-100 text-blue-700' : ''
}`}
style={{ paddingLeft: `${(depth + 1) * 16 + 16}px` }}
onClick={() => onFolderSelect(folder.id, folder.name)}
>
<span className="mr-2">📁</span>
{folder.name}
</div><button
type="button"
className={`w-full text-left px-4 py-2 cursor-pointer hover:bg-gray-100 ${
currentFolderId === folder.id ? 'bg-blue-100 text-blue-700' : ''
}`}
style={{ paddingLeft: `${(depth + 1) * 16 + 16}px` }}
onClick={() => onFolderSelect(folder.id, folder.name)}
>
<span className="mr-2">📁</span>
{folder.name}
</button>ai_evals/fixtures/frontend/app/initial/file_manager/frontend/components/Toolbar.tsx:30
"Create" button text fails contrast against its blue fill
The "Create" button in Toolbar.tsx uses `bg-blue-500 text-white`. White (#ffffff) on blue-500 (#3b82f6) computes to ~3.68:1, which fails WCAG AA's 4.5:1 threshold for normal-size text.
Why it matters
Low-vision users straining to read the button label may not be able to confirm the action before clicking, directly hurting task completion on the primary create flow.
Fix
Darken the fill within the existing accent hue until white text clears 4.5:1, rather than lightening the text.
className="px-3 py-1 bg-blue-500 text-white rounded hover:bg-blue-600"className="px-3 py-1 bg-blue-700 text-white rounded hover:bg-blue-800"ai_evals/fixtures/frontend/app/initial/file_manager/frontend/components/Toolbar.tsx:22
Folder name input has no persistent label for screen readers
The input in Toolbar.tsx only has `placeholder="Folder name"` with no <label> or aria-label. Placeholder text disappears on input and isn't reliably announced across screen readers.
Why it matters
A screen reader user who tabs into this field after typing, or whose AT doesn't expose placeholders, has no way to know what the field is for.
Fix
Give every form input a persistent accessible name via a <label> or aria-label, not just a placeholder.
<input
type="text"
value={folderName}
onChange={(e) => setFolderName(e.target.value)}
onKeyDown={(e) => e.key === 'Enter' && handleCreate()}
placeholder="Folder name"
className="border rounded px-3 py-1"
autoFocus
/><input
type="text"
value={folderName}
onChange={(e) => setFolderName(e.target.value)}
onKeyDown={(e) => e.key === 'Enter' && handleCreate()}
placeholder="Folder name"
aria-label="Folder name"
className="border rounded px-3 py-1"
autoFocus
/>UX
ai_evals/fixtures/frontend/app/initial/shopping_cart/frontend/components/Cart.tsx:29
"Remove" deletes a cart item instantly with no undo
The "Remove" button in Cart.tsx calls `onRemoveItem(item.product.id)` directly on click, with no confirmation step and no undo affordance after the item disappears.
Why it matters
A misclick removes a cart item permanently, which forces the user to re-search and re-add the product, adding friction right before checkout.
Fix
Give destructive one-click actions a lightweight recovery path, such as a brief undo toast, instead of an irreversible delete.
<button
onClick={() => onRemoveItem(item.product.id)}
className="text-red-500 hover:text-red-700 px-2"
>
Remove
</button><button
onClick={() => {
onRemoveItem(item.product.id)
// surface a toast here with an Undo action tied to a restore handler
}}
className="text-red-500 hover:text-red-700 px-2 py-2"
>
Remove
</button>ai_evals/fixtures/frontend/app/initial/file_manager/frontend/components/FileList.tsx:14
Empty folder state gives users no next step
FileList.tsx renders only "This folder is empty" in gray text when files.length is 0, with no prompt or action pointing the user toward the Toolbar's create/upload controls.
Why it matters
A user landing on an empty folder has to guess that the Toolbar above can create content, which adds friction right at the point where onboarding matters most.
Fix
Give empty states a hint that points at the actual next action available on the page.
return (
<div className="text-center text-gray-500 py-8">This folder is empty</div>
)return (
<div className="text-center text-gray-500 py-8">
<p>This folder is empty</p>
<p className="text-sm mt-1">Use the toolbar above to create a folder or upload a file.</p>
</div>
)Typography
Color
Spacing
Components
Motion
Craft
Working well
- Using the highest-contrast gray (text-gray-900, 17.74:1) to mark the current folder is a clear, legible way to signal 'you are here' without color alone.
- Sorting folders before files with a localeCompare fallback is a sensible, predictable ordering convention for file browsers.
- The empty-cart state ("Your cart is empty") is explicitly designed rather than left blank, giving users clear feedback.
- The Cancel button clearing folderName state before hiding the input prevents stale text from reappearing on next open.
Scored August 1, 2026 with Rams Engine v0.0.3 · Engine changelog
First scored July 9, 2026: 59/100. This rescore on v0.0.3: 59/100.
This page is an automated design review of windmill-labs/windmill’s UI code: 30 files read against 309 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.