Rams MCP · The full engine, now in your coding agent
resend on GitHub

resend/react-email

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

30 files reviewed·July 9, 2026

Top fix

Fix color-scheme attribute so dark theme reaches browser chrome and controls

See the fix

Verdict

Component-level discipline is solid, typed primitives, real alt text, but global layout defaults quietly undercut the whole app: dark mode never reaches browser chrome, line height is stretched everywhere, and uncompressed fonts load on every page. The biggest risk is that these root-layout mistakes compound silently across every screen, while boilerplate metadata and a glowing logo signal nobody did a final pass.

Files Rams reviewed

apps/web/src/app/components/[slug]/page.tsx

apps/web/src/app/components/page.tsx

packages/react-email/src/components/tailwind/e2e/nextjs/src/app/layout.tsx

packages/react-email/src/components/tailwind/e2e/nextjs/src/app/page.tsx

apps/web/src/app/components/get-imported-components-for.tsx

apps/web/src/app/editor/page.tsx

apps/web/src/app/layout.tsx

apps/web/src/app/templates/page.tsx

packages/ui/src/app/layout.tsx

packages/ui/src/app/page.tsx

packages/ui/src/app/preview/[...slug]/page.tsx

apps/web/src/app/editor/basic-editor/page.tsx

apps/web/src/app/editor/bubble-menu/page.tsx

apps/web/src/app/editor/buttons/page.tsx

apps/web/src/app/editor/column-layouts/page.tsx

apps/web/src/app/editor/custom-bubble-menu/page.tsx

apps/web/src/app/editor/custom-extensions/page.tsx

apps/web/src/app/editor/custom-theme/page.tsx

apps/web/src/app/editor/email-export/page.tsx

apps/web/src/app/editor/email-theming/page.tsx

apps/web/src/app/editor/full-email-builder/page.tsx

apps/web/src/app/editor/image-upload/page.tsx

apps/web/src/app/editor/inspector-composed/page.tsx

apps/web/src/app/editor/inspector-custom/page.tsx

apps/web/src/app/editor/inspector-defaults/page.tsx

apps/web/src/app/editor/layout.tsx

apps/web/src/app/editor/link-editing/page.tsx

apps/web/src/app/editor/slash-commands/page.tsx

apps/web/src/app/editor/standalone-editor-full/page.tsx

apps/web/src/app/editor/standalone-editor-inspector/page.tsx

96/100

Craft

2 serious
Craft·packages/ui/src/app/page.tsx:22Serious

Oversized glow behind the logo reads as decoration, not elevation

The logo's shadow is `0 .625rem 12.5rem 1.25rem` (10px offset, 200px blur, 20px spread) at 50% opacity, a 20x blur-to-offset ratio.

Why it matters

At that ratio the shadow smears into a soft haze around the mark instead of reading as a lifted surface, which is the classic 'glow' substitute for real hierarchy rather than an intentional elevation cue.

Fix

Keep shadows tight and directional: small offset, modest blur, layered sharp-to-soft, instead of one huge low-opacity blur.

boxShadow: '0 .625rem 12.5rem 1.25rem #2B7CA080',
boxShadow: '0 0.25rem 0.5rem rgba(43,124,160,0.15), 0 0.5rem 1.5rem rgba(43,124,160,0.15)',
Craft·packages/react-email/src/components/tailwind/e2e/nextjs/src/app/layout.tsx:6Serious

Default boilerplate metadata ships as real page title and description

The `metadata` export sets `title: 'Create Next App'` and `description: 'Generated by create next app'`, the unmodified create-next-app defaults.

Why it matters

Unedited placeholder copy in the document title and meta description ships to browser tabs, search results, and social previews, signaling the page was never finished.

Fix

Replace placeholder metadata with copy that names the actual app or test target.

export const metadata: Metadata = {
  title: 'Create Next App',
  description: 'Generated by create next app',
};
export const metadata: Metadata = {
  title: 'Tailwind Email E2E',
  description: 'End-to-end test harness for the Tailwind email component',
};
98/100

Accessibility

1 serious
Accessibility·packages/react-email/src/components/tailwind/e2e/nextjs/src/app/page.tsx:6Serious

Rendered email HTML has no landmark or heading context for this test page

The page renders `<div dangerouslySetInnerHTML={{ __html: emailHtml }} />` as its only content, with no `<main>` wrapper or page heading around the injected email markup.

Why it matters

This is an e2e test harness page, so the immediate user impact is limited, but screen reader users and automated a11y checks landing on this route get no page-level structure to orient with before hitting the email content.

Fix

Wrap injected third-party markup in a landmark element so the page has a navigable structure.

return <div dangerouslySetInnerHTML={{ __html: emailHtml }} />;
return <main dangerouslySetInnerHTML={{ __html: emailHtml }} />;

Want this on your repo?

Rams reviews your next PR automatically and posts inline fix suggestions.

Review my public repo for free
98/100

Typography

1 serious
Typography·packages/ui/src/app/layout.tsx:35Serious

Global leading-loose stretches line height across every screen in the app

`<body>` sets `leading-loose` (line-height 2) as the base for all text in the product, with no per-element override visible in this file.

Why it matters

A line-height of 2 is built for airy marketing copy, not dense UI text like lists, labels, or table rows; every component built on top of this default inherits oversized vertical rhythm unless it explicitly fights the inheritance, which compounds into inconsistent spacing across the app.

Fix

Set body leading to a UI-appropriate default (leading-normal or leading-relaxed) and apply leading-loose locally only where long-form copy needs it.

<body className="relative h-screen bg-black text-slate-11 leading-loose selection:bg-cyan-5 selection:text-cyan-12">
<body className="relative h-screen bg-black text-slate-11 leading-normal selection:bg-cyan-5 selection:text-cyan-12">
98/100

Color

1 serious
Color·apps/web/src/app/layout.tsx:96Serious

Dark theme never reaches the browser chrome or form controls

The `<html>` element sets `color-scheme="dark"` as a JSX/HTML attribute rather than the CSS property. Browsers only read `color-scheme` as a CSS declaration (or a `<meta name="color-scheme">` tag), so this attribute is inert markup.

Why it matters

Scrollbars, form control defaults, and native UI stay in light mode against the `bg-black` body, producing a visible light-on-dark mismatch for every user on this all-dark site.

Fix

Set color-scheme through CSS or a meta tag, not an HTML attribute.

<html
  className={`${inter.variable} ${commitMono.variable} antialiased`}
  lang="en"
  color-scheme="dark"
>
<html
  className={`${inter.variable} ${commitMono.variable} antialiased`}
  lang="en"
  style={{ colorScheme: 'dark' }}
>
98/100

UX

1 serious
UX·apps/web/src/app/components/[slug]/page.tsx:71Serious

Invalid category slug renders an unstyled, dead-end paragraph

When `foundCategory` isn't found, the page returns `<p>Component category not found.</p>` with no `PageWrapper`, no heading, and no link back to `/components`.

Why it matters

The user lands outside the entire site shell (no nav, no styling) with zero path forward, so a bad or outdated link becomes a total dead end instead of a recoverable moment.

Fix

Render error states inside the same layout shell with a heading and a link back to the working list.

if (!foundCategory) {
  return <p>Component category not found.</p>;
}
if (!foundCategory) {
  return (
    <PageWrapper>
      <div className="flex flex-col items-center gap-4 px-6 py-24 text-center">
        <Heading size="6" weight="medium" className="text-slate-12">
          Component category not found
        </Heading>
        <Link className="text-slate-11 underline hover:text-slate-12" href="/components">
          Back to all components
        </Link>
      </div>
    </PageWrapper>
  );
}

Spacing

No issues found

Components

No issues found

Motion

No issues found

Working well

  • The `Button asChild` wrapping a `Link` to "Check the docs" keeps the component semantically an anchor while inheriting the button's visual and focus treatment, which is the correct pattern: users get real link behavior (right-click, open in new tab, crawlable href) without losing button styling.
  • Using `<Heading size="6" weight="medium">` for the category title instead of a raw `text-2xl font-medium` span means the type scale lives in one component's API. If the scale ever changes, every page that imports `Heading` updates together instead of needing a find-and-replace across the app.
  • The 'Back' link's states are built entirely from one token family: `text-slate-11` at rest, `hover:text-slate-12` on hover, `focus:bg-slate-6` plus `focus:ring-slate-3` on focus. Staying inside one gray scale for every interaction state is exactly how a system avoids drift between pages.
  • The `<html lang="en">` attribute is set correctly on the root layout, which is exactly where it belongs per A11Y-014. Screen readers and browsers use this to pick pronunciation and hyphenation rules, and it's easy to forget on scaffold files like this one.

Score your own repo with Rams.

Free on public repos. Rams reviews every pull request for accessibility, color, type, spacing, components, motion, UX, and craft, and posts inline fixes.