mckaywrigley/chatbot-ui
Reviewed against Rams quality heuristics: accessibility, color, typography, spacing, components, motion, UX, and craft.
30 files reviewed·July 25, 2026
Elevated
UX needs attention.
More findings
Verdict
Minimal chrome hides real fragility: a logo that won't center, a label pointing nowhere, and workspace fetches that silently go stale or freeze on error. The look is clean but the plumbing beneath it is unguarded, turning small oversights into user-facing dead ends.
Files Rams reviewed
app/[locale]/[workspaceid]/chat/page.tsx
app/[locale]/[workspaceid]/layout.tsx
app/[locale]/layout.tsx
app/[locale]/login/page.tsx
app/[locale]/setup/page.tsx
app/[locale]/[workspaceid]/chat/[chatid]/page.tsx
app/[locale]/[workspaceid]/page.tsx
app/[locale]/help/page.tsx
app/[locale]/login/password/page.tsx
app/[locale]/page.tsx
app/[locale]/globals.css
components/chat/assistant-picker.tsx
components/chat/chat-command-input.tsx
components/chat/chat-files-display.tsx
components/chat/chat-help.tsx
components/chat/chat-hooks/use-chat-handler.tsx
components/chat/chat-hooks/use-chat-history.tsx
components/chat/chat-hooks/use-prompt-and-command.tsx
components/chat/chat-hooks/use-scroll.tsx
components/chat/chat-hooks/use-select-file-handler.tsx
components/chat/chat-input.tsx
components/chat/chat-messages.tsx
components/chat/chat-retrieval-settings.tsx
components/chat/chat-secondary-buttons.tsx
components/chat/chat-settings.tsx
components/chat/chat-ui.tsx
components/chat/file-picker.tsx
components/chat/prompt-picker.tsx
components/chat/quick-setting-option.tsx
components/chat/quick-settings.tsx
UX
app/[locale]/[workspaceid]/chat/page.tsx:31
Brand logo never actually centers due to invalid Tailwind classes
The Brand wrapper on the empty chat state uses `top-50%`, `left-50%`, `-translate-x-50%`, and `-translate-y-50%`. None of these are valid Tailwind utilities (percentage values need bracket syntax like `top-[50%]` or the fraction classes `top-1/2`/`translate-x-1/2`), so Tailwind's JIT compiler drops all four rules silently. The `<Brand>` logo sits at its default top-left position inside the absolutely positioned wrapper instead of being centered on screen.
Why it matters
The empty chat state's one visual anchor, the product logo, renders in the wrong spot for every user who opens a fresh chat, making the first screen look broken before any message is sent.
Fix
Use Tailwind's fraction-based centering utilities so the transform rules actually compile.
<div className="top-50% left-50% -translate-x-50% -translate-y-50% absolute mb-20"><div className="absolute left-1/2 top-1/2 mb-20 -translate-x-1/2 -translate-y-1/2">app/[locale]/[workspaceid]/chat/page.tsx:49
ChatHelp disappears entirely on mobile with no alternative access
The `<ChatHelp>` wrapper uses `hidden md:block`, so below the md breakpoint the help/onboarding entry point is removed from the DOM's visible render with no other trigger for it anywhere in the empty chat state.
Why it matters
Mobile users, who often need onboarding help more than desktop users, lose access to help content completely rather than getting a repositioned or condensed version of it.
Fix
Keep a visible, repositioned trigger for ChatHelp at small breakpoints instead of hiding it outright.
<div className="absolute bottom-2 right-2 hidden md:block lg:bottom-4 lg:right-4">
<ChatHelp />
</div><div className="absolute bottom-2 right-2 lg:bottom-4 lg:right-4">
<ChatHelp />
</div>app/[locale]/[workspaceid]/layout.tsx:130
Workspace data fetch has no error handling, freezing the screen on failure
`fetchWorkspaceData` awaits a long chain of sequential calls (`getWorkspaceById`, `getAssistantWorkspacesByWorkspaceId`, `getChatsByWorkspaceId`, `getCollectionWorkspacesByWorkspaceId`, `getFoldersByWorkspaceId`, `getFileWorkspacesByWorkspaceId`, `getPresetWorkspacesByWorkspaceId`, `getPromptWorkspacesByWorkspaceId`) with no try/catch anywhere in the sequence.
Why it matters
If any single request fails (network blip, expired session, server error), the promise chain throws unhandled and the user is left on a blank or perpetually loading workspace with no error message and no retry action.
Fix
Wrap the fetch sequence in try/catch and surface a recoverable error state instead of letting the throw propagate silently.
const workspace = await getWorkspaceById(workspaceId)
setSelectedWorkspace(workspace)
const assistantData = await getAssistantWorkspacesByWorkspaceId(workspaceId)try {
const workspace = await getWorkspaceById(workspaceId)
setSelectedWorkspace(workspace)
const assistantData = await getAssistantWorkspacesByWorkspaceId(workspaceId)
// ...remaining fetches
} catch (error) {
setLoadError("Couldn't load this workspace. Retry?")
}app/[locale]/[workspaceid]/layout.tsx:74
Overlapping workspace fetches on rapid switching can overwrite fresh state with stale data
The `useEffect` on `[workspaceId]` fires `fetchWorkspaceData(workspaceId)` with no cancellation token or request-id guard. If a user switches workspaces twice in quick succession, both async calls run concurrently and whichever resolves last wins, regardless of which workspace is actually selected.
Why it matters
A user who quickly clicks through two workspaces can end up viewing chats, files, or presets from the wrong workspace because a slower, stale response overwrote the correct one after the fact.
Fix
Guard the async effect with a cancellation flag or request id so a stale response can't overwrite state after a newer request has resolved.
useEffect(() => {
;(async () => await fetchWorkspaceData(workspaceId))()
setUserInput("")useEffect(() => {
let cancelled = false
;(async () => {
await fetchWorkspaceData(workspaceId, () => cancelled)
})()
setUserInput("")
// ...
return () => { cancelled = true }app/[locale]/login/page.tsx:185
Login inputs miss autoComplete, breaking password manager autofill
Both the email `<Input name="email">` and password `<Input type="password" name="password">` have no `autoComplete` attribute set.
Why it matters
Without autoComplete="email" and autoComplete="current-password", browsers and password managers can't reliably offer to save or autofill credentials, adding friction for every returning user on this login form.
Fix
Set autoComplete to the correct token (email, current-password) on each credential field.
<Input
className="mb-6 rounded-md border bg-inherit px-4 py-2"
type="password"
name="password"
placeholder="••••••••"
/><Input
className="mb-6 rounded-md border bg-inherit px-4 py-2"
type="password"
name="password"
autoComplete="current-password"
placeholder="••••••••"
/>Accessibility
app/[locale]/login/page.tsx:175
Email label points to an input id that doesn't exist
The `<Label htmlFor="email">Email</Label>` targets `id="email"`, but the `<Input>` right below it only sets `name="email"` with no `id` prop at all.
Why it matters
Screen reader users tabbing to the field hear no accessible name, and clicking the visible "Email" text won't focus the input, breaking a basic form interaction pattern for assistive tech users.
Fix
Add a matching id to every input that has an associated label using htmlFor.
<Input
className="mb-3 rounded-md border bg-inherit px-4 py-2"
name="email"
placeholder="you@example.com"
required
/><Input
id="email"
className="mb-3 rounded-md border bg-inherit px-4 py-2"
name="email"
placeholder="you@example.com"
required
/>Typography
Color
Spacing
Components
Motion
Craft
Working well
- QuickSettings and ChatSettings sit at symmetric top-left/top-right offsets in the empty chat state, giving the screen a balanced frame even before any content loads: a small consistency detail that keeps the empty state from feeling accidental.
- The workspaceId-change effect resets setUserInput, setChatMessages, setSelectedChat, and the file/image state arrays together, correctly preventing one workspace's draft input or chat history from leaking into the next.
- On the login form, "Login" is a solid blue-700 button with white text (6.7:1 contrast, passes AA) while "Sign Up" is an outlined secondary button: a clear, correctly differentiated primary/secondary CTA pair.
Scored July 25, 2026 with Rams Engine v0.0.3 · Engine changelog
First scored May 13, 2026: 63/100. This rescore on v0.0.3: 59/100.
This page is an automated design review of mckaywrigley/chatbot-ui’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.