continuedev/continue
Reviewed against Rams quality heuristics: accessibility, color, typography, spacing, components, motion, UX, and craft.
30 files reviewed·July 25, 2026
Elevated
Accessibility needs attention.
More findings
Verdict
Polished visuals sit on top of broken interaction plumbing: nested buttons, div-only click targets, and toggles that fight their own headers. The biggest risk is that keyboard and screen-reader users simply can't operate this config UI as built.
Files Rams reviewed
gui/src/pages/config/components/ConfigHeader.tsx
gui/src/pages/config/components/ConfigItemSelect.tsx
gui/src/pages/config/components/ConfigRow.tsx
gui/src/pages/config/components/ModelRoleRow.tsx
gui/src/pages/config/components/ModelRoleSelector.tsx
gui/src/pages/config/components/ToolPoliciesGroup.tsx
gui/src/pages/config/components/ToolPolicyItem.tsx
gui/src/pages/config/components/UserSetting.tsx
docs-site/app/components/NotFoundPage.tsx
gui/src/components/Layout.tsx
gui/src/components/console/Layout.tsx
gui/src/pages/config/components/ConfigSection.tsx
gui/src/pages/config/components/ConfigSubsection.tsx
docs-site/app/[[...slug]]/page.tsx
docs-site/app/layout.tsx
gui/src/pages/config/index.tsx
gui/src/pages/gui/ToolCallDiv/index.tsx
docs-site/app/components/ClientRedirect.tsx
gui/src/components/AssistantAndOrgListbox/index.tsx
gui/src/components/History/index.tsx
gui/src/components/StyledMarkdownPreview/StepContainerPreToolbar/index.tsx
gui/src/components/StyledMarkdownPreview/index.tsx
gui/src/components/dialogs/index.tsx
gui/src/pages/config/configTabs.tsx
gui/src/pages/config/features/indexing/IndexingProgress.tsx
gui/src/pages/config/features/indexing/IndexingProgressErrorText.tsx
gui/src/pages/config/features/indexing/IndexingProgressIndicator.tsx
gui/src/pages/config/features/indexing/IndexingProgressSubtext.tsx
gui/src/pages/config/features/keyboard/KeyboardShortcuts.tsx
gui/src/pages/config/sections/ConfigsSection.tsx
Accessibility
gui/src/pages/config/components/ToolPoliciesGroup.tsx:64
Tool group expand header is a div, unreachable by keyboard
The expand/collapse header in ToolPoliciesGroup.tsx is a <div> with onClick={() => setIsExpanded(!isExpanded)} and cursor-pointer styling, showing displayName and the enabled-count badge. It has no role, tabIndex, or onKeyDown, so it isn't in the tab order and Enter/Space do nothing.
Why it matters
Keyboard-only users cannot expand or collapse a tool group at all, permanently hiding the tool list inside it from anyone who can't use a mouse.
Fix
Give any div acting as a toggle control role="button", tabIndex={0}, and an onKeyDown handler for Enter and Space.
<div
className="flex cursor-pointer items-center justify-between gap-3 rounded px-2 py-2 hover:bg-gray-50 hover:bg-opacity-5"
onClick={() => setIsExpanded(!isExpanded)}
><div
role="button"
tabIndex={0}
aria-expanded={isExpanded}
className="flex cursor-pointer items-center justify-between gap-3 rounded px-2 py-2 hover:bg-gray-50 hover:bg-opacity-5"
onClick={() => setIsExpanded(!isExpanded)}
onKeyDown={(e) => {
if (e.key === "Enter" || e.key === " ") {
e.preventDefault();
setIsExpanded(!isExpanded);
}
}}
>gui/src/pages/config/components/ConfigHeader.tsx:43
Icon-only add button has no accessible name for screen readers
The add button in ConfigHeader.tsx renders only a PlusIcon inside a <Button variant="icon">, wrapped in a ToolTip that shows addButtonTooltip on hover. Tooltips are not exposed to assistive tech the way an aria-label is, so a screen reader announces just "button" with no indication it adds an item.
Why it matters
Screen reader users can't tell what this control does before activating it, so they either skip it or trigger an unknown action, blocking task completion for a class of users.
Fix
Add aria-label to icon-only buttons using the same text already passed to the tooltip.
<Button
onClick={onAddClick}
variant="icon"
size={isSmall ? "sm" : "lg"}
>
<PlusIcon className={isSmall ? "h-2.5 w-2.5" : "h-3 w-3"} />
</Button><Button
onClick={onAddClick}
variant="icon"
size={isSmall ? "sm" : "lg"}
aria-label={addButtonTooltip}
>
<PlusIcon className={isSmall ? "h-2.5 w-2.5" : "h-3 w-3"} />
</Button>gui/src/pages/config/components/ConfigRow.tsx:48
Row renders arbitrary interactive children inside a native button
ConfigRow wraps its {children} (which, per the handleClick logic checking for 'button, input, textarea, select, [role="button"], [role="switch"]', is expected to include switches or buttons) inside a native <Button variant="ghost">. Nesting interactive elements inside a <button> is invalid HTML.
Why it matters
Nested interactive controls inside a button lose predictable keyboard focus order and some browsers block or double-fire click events on the inner control, so a toggle or button placed in a config row can become unreliable or untappable by keyboard.
Fix
Use a non-button container with role="button" and a keydown handler for the row's own click target, and keep nested controls as true siblings outside any button element.
<Button
variant="ghost"
className={cn(
baseClasses,
interactiveClasses,
disabledClasses,
"!my-0 text-left",
className,
)}
onClick={handleClick}
disabled={disabled}
data-config-row
>
<div className="flex flex-col">
<span className="text-sm font-medium">{title}</span>
<p className="mt-0.5 text-xs text-gray-500">{description}</p>
</div>
<div className="flex items-center gap-4">
{children}
{Icon && <Icon className="h-5 w-5 flex-shrink-0 text-gray-400" />}
</div>
</Button><div
role="button"
tabIndex={disabled ? -1 : 0}
onKeyDown={(e) => (e.key === "Enter" || e.key === " ") && handleClick(e as any)}
className={cn(
baseClasses,
interactiveClasses,
disabledClasses,
"!my-0 text-left",
className,
)}
onClick={handleClick}
aria-disabled={disabled}
data-config-row
>
<div className="flex flex-col">
<span className="text-sm font-medium">{title}</span>
<p className="mt-0.5 text-xs text-gray-500">{description}</p>
</div>
<div className="flex items-center gap-4">
{children}
{Icon && <Icon className="h-5 w-5 flex-shrink-0 text-gray-400" />}
</div>
</div>gui/src/pages/config/components/ModelRoleSelector.tsx:153
Invalid model status text renders at 10px, below legible floor
The invalid-config message ('(Missing API Key)', '(Missing env secret)', '(Invalid config)') in ModelRoleSelector's option list renders in a <span className="ml-2 text-[10px] italic">, well under the 12px minimum for comfortable reading.
Why it matters
Text this small is hard to read for any user and fails typical legibility guidance, so the exact reason a model option is disabled becomes difficult to confirm at a glance.
Fix
Raise small status text to at least the 12px (text-xs) scale step.
<span className="ml-2 text-[10px] italic">
{invalidMessage}
</span><span className="ml-2 text-xs italic">
{invalidMessage}
</span>Typography
gui/src/pages/config/components/ConfigHeader.tsx:28
Heading level tied to visual size instead of document structure
ConfigHeader picks HeadingTag as h3 or h2 purely from the variant prop (isSmall), with no way for a consuming page to specify the actual semantic level. Any page that nests a "sm" ConfigHeader under an existing h2 gets an h3 (or vice versa) regardless of the true outline.
Why it matters
When heading level is coupled to visual variant rather than page structure, screen reader users navigating by heading level get an outline that doesn't match the page's real hierarchy, and skipped or duplicated levels compound as this component gets reused.
Fix
Decouple heading semantics from visual variant by exposing an explicit headingLevel prop.
const HeadingTag = isSmall ? "h3" : "h2";const HeadingTag = headingLevel ?? (isSmall ? "h3" : "h2");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 freeUX
gui/src/pages/config/components/ToolPoliciesGroup.tsx:93
Group toggle switch click also collapses the header it sits in
The ToggleSwitch that enables/disables all tools in a group sits inside a <div> inside the header row, and that whole header row has onClick={() => setIsExpanded(!isExpanded)}. Toggling the switch doesn't stop propagation, so a click on the switch also fires the header's expand/collapse handler.
Why it matters
A user flipping the group toggle gets an unexpected side effect of the panel collapsing or expanding, making the toggle feel unreliable and forcing a second click just to see the tool list again.
Fix
Stop click propagation on nested interactive controls placed inside a clickable header.
<div>
<ToggleSwitch
isToggled={isGroupEnabled}
onToggle={() => dispatch(toggleToolGroupSetting(groupName))}
text=""
size={10}
disabled={allToolsOff}
/>
</div><div onClick={(e) => e.stopPropagation()}>
<ToggleSwitch
isToggled={isGroupEnabled}
onToggle={() => dispatch(toggleToolGroupSetting(groupName))}
text=""
size={10}
disabled={allToolsOff}
/>
</div>Color
Spacing
Components
Motion
Craft
Working well
- Disabled invalid model options in ModelRoleSelector combine a visible '(Missing API Key)' or '(Invalid config)' label with the disabled/italic state, so the reason for unavailability is never conveyed by color or opacity alone.
- Using ToolTip to surface addButtonTooltip on the icon-only add button is the right instinct for an unlabeled affordance, it just needs to also reach the accessible name via aria-label rather than relying on hover-only text.
- The badge combining enabled and total tool count ('3/5') in ToolPoliciesGroup gives a compact, scannable summary of group state without forcing the group open.
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 continuedev/continue’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.