windmill-labs on GitHub

windmill-labs/windmill

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

30 files reviewed·August 1, 2026

View on GitHub

Elevated

Design risk in this codebase.

9issues
Critical & Serious · top 6 shown below

Top fix

Convert folder row divs to focusable, keyboard-operable elements

See the fix

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

90/100

Accessibility

2 critical2 serious
AccessibilityCritical

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>
AccessibilityCritical

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>
AccessibilitySerious

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"
AccessibilitySerious

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
/>
96/100

UX

2 serious
UXSerious

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>
UXSerious

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

No issues found

Color

No issues found

Spacing

No issues found

Components

No issues found

Motion

No issues found

Craft

No issues found

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.

Or get a design review on every pull requestInstall Rams