makeplane on GitHub

makeplane/plane

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.

6issues
Critical, Serious & Moderate · top 5 shown below

Verdict

This set is mostly solid structurally (clean modal patterns, labeled menu items) but the AI response menu is the weak point: two icon-only actions ship with tooltip-only labeling and undersized hit areas, which is a real lockout for assistive tech and a friction point for everyone else. The content-limit banner is a quieter but real miss, a state-change warning that never reaches screen reader users. Fixes across all three files are small, targeted attribute and class additions, not redesigns.

Files Rams reviewed

apps/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/pages/(detail)/[pageId]/page.tsx

apps/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/pages/(detail)/layout.tsx

apps/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/pages/(list)/page.tsx

apps/web/app/(all)/[workspaceSlug]/(settings)/settings/projects/[projectId]/features/pages/page.tsx

apps/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/views/(detail)/[viewId]/page.tsx

apps/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/views/(list)/page.tsx

apps/web/app/(all)/[workspaceSlug]/(settings)/settings/projects/[projectId]/features/views/page.tsx

apps/web/core/components/power-k/ui/pages/work-item-selection-page.tsx

apps/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/pages/(list)/layout.tsx

apps/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/pages/(detail)/header.tsx

apps/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/pages/(list)/header.tsx

apps/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/views/(detail)/layout.tsx

apps/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/views/(list)/layout.tsx

apps/web/app/(all)/[workspaceSlug]/(settings)/settings/projects/[projectId]/features/pages/header.tsx

apps/admin/app/components/404.tsx

apps/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/views/(detail)/[viewId]/header.tsx

apps/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/views/(list)/header.tsx

apps/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/views/(list)/mobile-header.tsx

apps/web/app/(all)/[workspaceSlug]/(settings)/settings/projects/[projectId]/features/views/header.tsx

apps/web/core/components/pages/dropdowns/actions.tsx

apps/web/core/components/pages/editor/ai/ask-pi-menu.tsx

apps/web/core/components/pages/editor/ai/menu.tsx

apps/web/core/components/pages/editor/content-limit-banner.tsx

apps/web/core/components/pages/editor/editor-body.tsx

apps/web/core/components/pages/editor/header/logo-picker.tsx

apps/web/core/components/pages/editor/header/root.tsx

apps/web/core/components/pages/editor/page-root.tsx

apps/web/core/components/pages/editor/summary/content-browser.tsx

apps/web/core/components/pages/editor/summary/heading-components.tsx

apps/web/core/components/pages/editor/title.tsx

96/100

Spacing

2 serious
Design SystemSerious

apps/web/core/components/pages/editor/ai/menu.tsx:148

Hardcoded pixel widths on the AI menu bypass the spacing scale

The AI menu container sets "w-[210px]" by default and "w-[700px]" when a task is active, plus "border-[0.5px]", instead of scale-based Tailwind width or a token.

Why it matters

Arbitrary pixel values drift independently from the rest of the layout scale, so future width tweaks (e.g. responsive breakpoints) require hunting down bespoke numbers instead of adjusting one scale step.

Fix

Replace one-off pixel widths with the nearest step on the existing Tailwind width scale or a named token for menu width.

"flex w-[210px] flex-col rounded-md border-[0.5px] border-strong bg-surface-1 shadow-raised-200 transition-all",
{
  "w-[700px]": activeTask,
}
"flex w-52 flex-col rounded-md border border-strong bg-surface-1 shadow-raised-200 transition-all",
{
  "w-[44rem]": activeTask,
}
Design SystemSerious

apps/web/core/components/pages/dropdowns/actions.tsx:188

Dropdown menu caps height with an arbitrary viewport value

CustomMenu's optionsClassName="max-h-[90vh]" sizes the page-actions dropdown against 90% of viewport height directly instead of a defined max-height token or scale step.

Why it matters

A viewport-relative arbitrary value behaves unpredictably across small viewports and doesn't align with any other menu's sizing in the app, so menus grow inconsistent max heights across the product.

Fix

Use a fixed max-height token consistent with other CustomMenu instances instead of a one-off viewport-relative arbitrary value.

<CustomMenu placement="bottom-end" optionsClassName="max-h-[90vh]" ellipsis closeOnSelect>
<CustomMenu placement="bottom-end" optionsClassName="max-h-96" ellipsis closeOnSelect>
97/100

Accessibility

1 critical
AccessibilityCritical

apps/web/core/components/pages/editor/ai/menu.tsx:238

Icon-only insert and regenerate buttons give screen readers no name

In the AI response menu, the 'Add to next line' button (CornerDownRight icon) and the 'Re-generate response' button (RefreshCcw icon) both render with no text content, no aria-label, and no visually-hidden label. Their only description comes from a Tooltip, which is hover/focus-visible content, not an accessible name. A screen reader lands on both as an unlabeled 'button'.

Why it matters

Screen reader and switch-control users can't tell what either button does or that they're different from each other, so they can't act on the AI response with confidence.

Fix

Add an aria-label matching the tooltip text to every icon-only button so its accessible name doesn't depend on hover state.

<Tooltip tooltipContent="Add to next line">
  <button
    type="button"
    className="grid size-6 flex-shrink-0 place-items-center rounded-sm outline-none hover:bg-layer-1"
    onClick={() => handleInsertText(true)}
  >
    <CornerDownRight className="size-4 text-tertiary" />
  </button>
</Tooltip>
<Tooltip tooltipContent="Re-generate response">
  <button
    type="button"
    className="grid size-6 flex-shrink-0 place-items-center rounded-sm outline-none hover:bg-layer-1"
    onClick={(e) => { e.preventDefault(); e.stopPropagation(); handleRegenerate(); }}
    disabled={isRegenerating}
  >
    <RefreshCcw className={cn("size-4 text-tertiary", { "animate-spin": isRegenerating })} />
  </button>
</Tooltip>
<Tooltip tooltipContent="Add to next line">
  <button
    type="button"
    aria-label="Add to next line"
    className="grid size-6 flex-shrink-0 place-items-center rounded-sm outline-none hover:bg-layer-1"
    onClick={() => handleInsertText(true)}
  >
    <CornerDownRight className="size-4 text-tertiary" />
  </button>
</Tooltip>
<Tooltip tooltipContent="Re-generate response">
  <button
    type="button"
    aria-label="Re-generate response"
    className="grid size-6 flex-shrink-0 place-items-center rounded-sm outline-none hover:bg-layer-1"
    onClick={(e) => { e.preventDefault(); e.stopPropagation(); handleRegenerate(); }}
    disabled={isRegenerating}
  >
    <RefreshCcw className={cn("size-4 text-tertiary", { "animate-spin": isRegenerating })} />
  </button>
</Tooltip>

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 free
98/100

Components

1 serious
ComponentsSerious

apps/web/core/components/pages/editor/ai/menu.tsx:250

24px icon buttons undersized for reliable pointer interaction

The same 'Add to next line' and 'Re-generate response' buttons use className="grid size-6 ...", a 24x24px hit area, sitting next to the larger text button 'Replace selection'.

Why it matters

A hit target under ~44px raises mis-click rates for anyone with limited dexterity or on touch, and it reads as an afterthought next to the taller text button beside it.

Fix

Increase the tappable area to at least 32-36px with padding while keeping the icon at its current visual size.

className="grid size-6 flex-shrink-0 place-items-center rounded-sm outline-none hover:bg-layer-1"
className="grid size-8 flex-shrink-0 place-items-center rounded-sm outline-none hover:bg-layer-1"
98/100

UX

1 serious
UXSerious

apps/web/core/components/pages/dropdowns/actions.tsx:63

'Move' menu action sets state that no modal ever reads

PageActions declares const [movePageModal, setMovePageModal] = useState(false) and a 'Move' item calls setMovePageModal(true) on click, but unlike DeletePageModal there is no <MovePageModal isOpen={movePageModal} .../> rendered in the component's return, so movePageModal is never consumed.

Why it matters

Clicking 'Move' updates state with no visible effect, so users who expect a move dialog get silent failure and have to guess whether the action registered at all.

Fix

Render the corresponding move-page modal wired to the existing movePageModal state, mirroring the DeletePageModal pattern already in this file.

const [movePageModal, setMovePageModal] = useState(false);
...
action: () => setMovePageModal(true),
const [movePageModal, setMovePageModal] = useState(false);
...
action: () => setMovePageModal(true),

// in render:
<MovePageModal isOpen={movePageModal} onClose={() => setMovePageModal(false)} page={page} storeType={storeType} />

Typography

No issues found

Color

No issues found

Motion

No issues found

Craft

No issues found

Working well

  • Splitting 'Replace selection' out as a labeled text button while keeping insert/regenerate as icon buttons gives each affordance a distinct visual weight that matches its distinct purpose, so the primary text action reads first.
  • The dismiss button in ContentLimitBanner correctly uses aria-label="Dismiss content limit warning" on an icon-only control, which is the right pattern for giving a glyph-only button a real accessible name.
  • Menu items throughout PageActions pair an icon with visible title text like 'Lock'/'Unlock' and 'Delete', so screen readers get a real accessible name for free without extra aria-label plumbing.

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 makeplane/plane’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