Rams MCP · The full engine, now in your coding agent
immich-app on GitHub

immich-app/immich

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

30 files reviewed·August 1, 2026

View on GitHub

Low

Design risk in this codebase.

10issues
Serious · top 6 shown below

Top fix

Replace the 50px arbitrary radius with a theme token

See the fix

Verdict

A theme gets defined then routinely ignored: arbitrary radii, a stray hex, and alert-red borders on routine emails all bypass the very tokens this codebase just set up. The bigger risk is silent failure, wrong passwords and broken fetches both fail quietly instead of telling the user what happened.

Files Rams reviewed

web/src/lib/components/pages/SharedLinkPage.svelte

server/src/emails/components/futo.layout.tsx

server/src/emails/components/immich.layout.tsx

web/src/lib/components/pages/SharedLinkErrorPage.svelte

docs/src/components/version-switcher.tsx

docs/src/pages/index.tsx

server/src/emails/components/footer.template.tsx

web/src/lib/components/AdaptiveImage.svelte

web/src/lib/components/ApiKeyPermissionsPicker.svelte

web/src/lib/components/BreadcrumbActionPage.svelte

web/src/lib/components/CalendarHeatmap.svelte

web/src/lib/components/Image.svelte

web/src/lib/components/SchemaAlbumPicker.svelte

web/src/lib/components/SchemaConfiguration.svelte

web/src/lib/components/SharedLinkExpiration.svelte

web/src/lib/components/SharedLinkFormFields.svelte

web/src/lib/components/Thumbhash.svelte

web/src/lib/components/admin-settings/StorageTemplateSettings.svelte

web/src/lib/components/admin-settings/SupportedDatetimePanel.svelte

web/src/lib/components/admin-settings/SupportedVariablesPanel.svelte

web/src/lib/components/album-page/AlbumCard.svelte

web/src/lib/components/album-page/AlbumCardGroup.svelte

web/src/lib/components/album-page/AlbumMap.svelte

web/src/lib/components/album-page/AlbumSharedLink.svelte

web/src/lib/components/album-page/AlbumThumbnail.svelte

web/src/lib/components/album-page/AlbumViewer.svelte

web/src/lib/components/album-page/AlbumsList.svelte

web/src/lib/components/album-page/AlbumsTable.svelte

web/src/lib/components/album-page/AlbumsTableHeader.svelte

web/src/lib/components/album-page/AlbumsTableRow.svelte

96/100

UX

2 serious
UXSerious

docs/src/components/version-switcher.tsx:6

Version switcher vanishes with no fallback when the fetch fails

`VersionSwitcher` starts with `useState([])` and only renders `versions.length > 0 && (...)`. If `getVersions` fails, `versions` stays empty and the whole dropdown never mounts, with no retry or fallback state offered.

Why it matters

A user on a flaky connection loses the ability to switch documentation versions for the entire session with no visible sign anything is wrong, they simply don't see the control they expected.

Fix

Render a disabled or fallback state when the version fetch fails instead of silently omitting the control.

const [versions, setVersions] = useState([]);
const [versions, setVersions] = useState([]);
const [fetchFailed, setFetchFailed] = useState(false);
UXSerious

web/src/lib/components/pages/SharedLinkPage.svelte:62

Wrong password only surfaces a generic toast, not an inline error

`handlePasswordSubmit` catches a failed login and calls `handleError(error, $t('errors.unable_to_get_shared_link'))`, which fires a toast elsewhere on screen. Nothing renders next to the `PasswordInput` itself to say the password was wrong.

Why it matters

A user who mistypes the password sees a passing toast and a field that looks unchanged, so they can't tell whether to retype, refresh, or contact the link owner. Recovery from the error takes longer than it should.

Fix

Show a field-level error state next to the input when submission fails, in addition to or instead of a transient toast.

} catch (error) {
      handleError(error, $t('errors.unable_to_get_shared_link'));
    }
} catch (error) {
      passwordError = $t('errors.unable_to_get_shared_link');
      handleError(error, passwordError);
    }
98/100

Accessibility

1 serious
AccessibilitySerious

web/src/lib/components/pages/SharedLinkPage.svelte:89

Password field has no accessible label, only a placeholder

The password gate's form contains `<PasswordInput autocomplete="off" bind:value={password} placeholder="Password" />` with no `<label>` and no `aria-label`. The placeholder is the only identifying text, and it vanishes the moment the user starts typing.

Why it matters

Screen reader users get no accessible name for the field, and sighted users who tab back to it after typing lose the only cue for what it is. Form completion friction increases for both groups.

Fix

Give every form input a persistent accessible name via a visible label or aria-label, not a placeholder alone.

<PasswordInput autocomplete="off" bind:value={password} placeholder="Password" />
<PasswordInput autocomplete="off" bind:value={password} placeholder="Password" aria-label={$t('password_required')} />

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

Color

1 serious
ColorSerious

server/src/emails/components/futo.layout.tsx:61

Red border wraps routine email content, signaling an error that isn't one

The main content `<Section>` in the FUTO layout is styled `border border-red-400 rounded-[50px] bg-gray-50`, wrapping the logo and every `{children}` body passed into this layout, none of which represent an error or warning state.

Why it matters

Red borders read as alerts across nearly every UI convention. A transactional email framed in red primes recipients to expect something went wrong before they've read a word of the actual message.

Fix

Reserve red borders for destructive or error states; use a neutral gray or the layout's existing muted border color for standard content framing.

<Section className="my-6 p-12 border border-red-400 rounded-[50px] bg-gray-50">
<Section className="my-6 p-12 border border-gray-200 rounded-2xl bg-gray-50">
98/100

Spacing

1 serious
SpacingSerious

server/src/emails/components/futo.layout.tsx:60

50px radius on the content card sits off any radius scale

The same content `<Section>` sets `rounded-[50px]` on a rectangular card holding a logo and body text, far larger than any step in Tailwind's default radius scale.

Why it matters

An unscaled radius this large on a boxy container reads as an accidental value rather than a deliberate shape choice, and it won't match the corner treatment used elsewhere in the product's emails.

Fix

Use a scale-based radius (e.g. rounded-2xl or rounded-3xl) instead of an arbitrary pixel value.

<Section className="my-6 p-12 border border-red-400 rounded-[50px] bg-gray-50">
<Section className="my-6 p-12 border border-gray-200 rounded-2xl bg-gray-50">
98/100

Craft

1 serious
CraftSerious

server/src/emails/components/immich.layout.tsx:61

50px radius on a boxy card reads as an unintentional value

The Immich layout's content `<Section>` also uses `rounded-[50px]` on a rectangular card, matching the same off-scale pattern as the FUTO layout.

Why it matters

A radius this size on a straight-edged content box looks like a copy-paste leftover rather than a considered shape choice, and it breaks visual consistency with any other rounded surface in the product.

Fix

Use a scale-based radius token instead of an arbitrary pixel value.

<Section className="my-6 p-12 border border-red-400 rounded-[50px] bg-gray-50">
<Section className="my-6 p-12 border border-gray-200 rounded-2xl bg-gray-50">

Typography

No issues found

Components

No issues found

Motion

No issues found

Working well

  • VersionSwitcher derives the active version label by matching the current origin against fetched versions rather than hardcoding it, so the label stays correct across preview and production deployments.
  • SharedLinkPage reuses ControlAppBar, ThemeButton, and Logo across both the password gate and the authenticated view, so the shell stays visually coherent no matter which state the page is in.
  • Both Img elements in the email layouts ('Immich', 'FUTO') carry descriptive alt text, keeping the emails legible to screen readers and mail clients that block images by default.

Scored August 1, 2026 with Rams Engine v0.0.3 · Engine changelog
First scored May 13, 2026: 59/100. This rescore on v0.0.3: 80/100.

This page is an automated design review of immich-app/immich’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.

Or get a design review on every pull requestInstall Rams