khoj-ai/khoj
Reviewed against Rams quality heuristics: accessibility, color, typography, spacing, components, motion, UX, and craft.
30 files reviewed·July 25, 2026
More findings
Verdict
Core actions look intentional but only work for pointer users, a hard blocker for any keyboard-first flow. Sidebar icons mix libraries and undercut the otherwise coherent visual language.
Files Rams reviewed
src/interface/web/app/components/allConversations/sidePanel.module.css
src/interface/web/app/components/appSidebar/appSidebar.tsx
src/interface/web/app/components/chatHistory/chatHistory.module.css
src/interface/web/app/components/chatMessage/FileContentSnippet.tsx
src/interface/web/app/components/chatMessage/chatMessage.module.css
src/interface/web/app/components/deprecationBanner.tsx
src/interface/web/app/components/excalidraw/excalidrawWrapper.tsx
src/interface/web/app/components/loading/loading.tsx
src/interface/web/app/components/loginPrompt/loginPopup.tsx
src/interface/web/app/components/loginPrompt/loginPrompt.module.css
src/interface/web/app/components/logo/fileLogo.tsx
src/interface/web/app/components/mermaid/mermaid.tsx
src/interface/web/app/components/navMenu/navMenu.module.css
src/interface/web/app/components/navMenu/navMenu.tsx
src/interface/web/app/components/profileCard/profileCard.tsx
src/interface/web/app/components/shareLink/shareLink.tsx
src/interface/web/app/components/suggestions/suggestionCard.tsx
src/interface/web/app/components/userMemory/userMemory.tsx
src/interface/web/app/agents/layout.tsx
src/interface/web/app/automations/layout.tsx
src/interface/web/app/chat/layout.tsx
src/interface/web/app/layout.tsx
src/interface/web/app/settings/layout.tsx
src/interface/web/app/share/chat/layout.tsx
src/interface/web/app/share/chat/page.tsx
src/interface/web/app/components/agentCard/agentCard.module.css
src/interface/web/app/components/chatInputArea/chatInputArea.module.css
src/interface/web/app/components/chatWoot/ChatwootWidget.tsx
src/interface/web/app/components/excalidraw/excalidraw.tsx
src/interface/web/app/components/loginPrompt/GoogleSignIn.tsx
Accessibility
src/interface/web/app/components/appSidebar/appSidebar.tsx:147
"Sign up to get started" and "New" actions are unreachable by keyboard
Both `SidebarMenuButton` items use `asChild` with a plain `<div>` as the rendered element: the sign-up prompt ("Sign up to get started", line 147) and the primary chat action ("New", line 161). The `onClick` handlers land on a non-interactive div, so neither element receives a tab stop, a button role, or Enter/Space activation.
Why it matters
Keyboard and screen reader users cannot start a new chat or sign up at all, the two most frequent entry points in this sidebar, which locks a whole user segment out of the core action.
Fix
Render the asChild target as a real button element so native focus and keyboard activation come for free.
<SidebarMenuButton asChild variant={"default"} onClick={() => setShowLoginPrompt(true)}>
<div>
<UserPlusIcon />
<span>Sign up to get started</span>
</div>
</SidebarMenuButton><SidebarMenuButton asChild variant={"default"} onClick={() => setShowLoginPrompt(true)}>
<button type="button">
<UserPlusIcon />
<span>Sign up to get started</span>
</button>
</SidebarMenuButton>Components
src/interface/web/app/components/appSidebar/appSidebar.tsx:21
Mixed icon libraries in the sidebar break visual consistency
The sidebar imports `Plus`, `Gear`, `HouseSimple` from `@phosphor-icons/react` but `UserPlusIcon` from `lucide-react`. Phosphor icons in this codebase use a consistent stroke weight and rounded terminal style; lucide's default stroke and corner geometry differ, so the "Sign up to get started" icon reads at a different weight than every other icon in the same menu.
Why it matters
A single panel with two icon styles reads as unfinished and undermines the perceived polish of the product on the very first screen users see.
Fix
Use one icon library consistently within a shared component tree; swap the lucide icon for its Phosphor equivalent.
import { Plus, Gear, HouseSimple } from "@phosphor-icons/react";
...
import { UserPlusIcon } from "lucide-react";import { Plus, Gear, HouseSimple, UserPlus } from "@phosphor-icons/react";
// remove the lucide-react import and use <UserPlus /> in its placeTypography
Color
Spacing
Motion
UX
Craft
Working well
- The inline blocking script that applies the `dark` class before hydration is the correct way to prevent a flash of incorrect theme on load, and pairing it with `suppressHydrationWarning` avoids a React mismatch warning.
- Setting `lang="en"` on the html root is exactly right for screen readers and pronunciation, and using next/font variables for both noto_sans and noto_sans_arabic keeps font loading self-hosted and swap-safe.
- Splitting the collapsed vs expanded logo into two SidebarMenuButton branches (KhojLogoType vs KhojLogo) is a clean way to keep the header usable at both sidebar widths.
- Escape-to-collapse plus dispatching a resize event so Excalidraw recalculates its canvas is a thoughtful touch most implementations miss.
Scored July 25, 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 khoj-ai/khoj’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.