t3-oss/create-t3-app
Reviewed against Rams quality heuristics: accessibility, color, typography, spacing, components, motion, UX, and craft.
30 files reviewed·July 9, 2026
More findings
Verdict
Every reviewer flagged the same five gaps in the same stock scaffold, proving this template gets copy-pasted forward untouched. The real danger isn't the current page, it's every project inheriting its silent loading states and invisible focus hierarchy.
Files Rams reviewed
cli/template/extras/src/app/_components/post-tw.tsx
cli/template/extras/src/app/_components/post.tsx
cli/template/extras/src/app/page/base.tsx
cli/template/extras/src/app/page/with-auth-trpc-tw.tsx
cli/template/extras/src/app/page/with-auth-trpc.tsx
cli/template/extras/src/app/page/with-better-auth-trpc-tw.tsx
cli/template/extras/src/app/page/with-better-auth-trpc.tsx
cli/template/extras/src/app/page/with-better-auth-tw.tsx
cli/template/extras/src/app/page/with-better-auth.tsx
cli/template/extras/src/app/page/with-trpc-tw.tsx
cli/template/extras/src/app/page/with-trpc.tsx
cli/template/extras/src/app/page/with-tw.tsx
cli/template/extras/src/pages/index/base.tsx
cli/template/extras/src/pages/index/with-auth-trpc-tw.tsx
cli/template/extras/src/pages/index/with-auth-trpc.tsx
cli/template/extras/src/pages/index/with-better-auth-trpc-tw.tsx
cli/template/extras/src/pages/index/with-better-auth-trpc.tsx
cli/template/extras/src/pages/index/with-better-auth-tw.tsx
cli/template/extras/src/pages/index/with-better-auth.tsx
cli/template/extras/src/pages/index/with-trpc-tw.tsx
cli/template/extras/src/pages/index/with-trpc.tsx
cli/template/extras/src/pages/index/with-tw.tsx
www/src/components/docs/breadCrumbs.tsx
www/src/components/docs/companyList.tsx
www/src/components/docs/introductionTab.tsx
www/src/components/docs/openSourceAppList.tsx
www/src/components/landingPage/ClipboardSelect.tsx
www/src/components/landingPage/cli.tsx
www/src/components/navigation/LanguageSelect.tsx
www/src/components/navigation/OnThisPageLinks.tsx
Accessibility
tRPC loading and greeting text swap silently with no live region
`<p className={styles.showcaseText}>{hello.data ? hello.data.greeting : "Loading tRPC query..."}</p>` changes content once the query resolves, but the paragraph has no `role="status"` or `aria-live`.
Why it matters
Sighted users see the text flip from "Loading tRPC query..." to the greeting, but screen reader users get no announcement that the async fetch finished or what it returned.
Fix
Mark the async status text with aria-live="polite" so assistive tech announces the update.
<p className={styles.showcaseText}>
{hello.data ? hello.data.greeting : "Loading tRPC query..."}
</p><p className={styles.showcaseText} aria-live="polite">
{hello.data ? hello.data.greeting : "Loading tRPC query..."}
</p>Session loading state is invisible to screen reader users
The `isPending` branch renders `<p className="text-2xl text-white">Loading...</p>` with no `role="status"` or `aria-live` attribute.
Why it matters
Sighted users see the loading text appear and disappear; screen reader users get no announcement that the session is resolving or has finished, so they can't tell whether the page is ready.
Fix
Mark async status text with role='status' or aria-live='polite' so assistive tech announces it.
if (isPending) {
return <p className="text-2xl text-white">Loading...</p>;
}if (isPending) {
return (
<p role="status" className="text-2xl text-white">
Loading...
</p>
);
}Typography
Arrow glyphs as link affordances render inconsistently across fonts
Both card headings, "First Steps →" and "Documentation →", use the literal `→` character baked into the string to signal an external link.
Why it matters
The glyph's weight and baseline position come from whatever font is active, so it can render heavy, off-baseline, or missing entirely in some font stacks, undercutting the link affordance it's meant to provide.
Fix
Replace the character with an inline stroke SVG arrow icon that matches the type weight exactly.
<h3 className="text-2xl font-bold">First Steps →</h3><h3 className="flex items-center gap-1.5 text-2xl font-bold">First Steps<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><path d="m9 18 6-6-6-6"/></svg></h3>Want this on your repo?
Rams reviews your next PR automatically and posts inline fix suggestions.
Review my public repo for freeColor
Hero background and accent text hardcode raw hex and HSL values
The `<main>` uses `bg-gradient-to-b from-[#2e026d] to-[#15162c]` and the "T3" span uses `text-[hsl(280,100%,70%)]`, both arbitrary Tailwind bracket values with no token behind them.
Why it matters
Colors defined inline instead of through tokens can't be reused, themed, or updated consistently, and every new page risks introducing a slightly different purple.
Fix
Move these values into the Tailwind config or CSS variables as named tokens (e.g. brand-950, brand-accent) instead of inline arbitrary values.
<main className="flex min-h-screen flex-col items-center justify-center bg-gradient-to-b from-[#2e026d] to-[#15162c]"><main className="flex min-h-screen flex-col items-center justify-center bg-gradient-to-b from-brand-950 to-brand-900">UX
No focal point on the page: heading and two identical cards carry equal weight
The page is `<h1>Create T3 App</h1>` followed by two visually identical `Link` cards ("First Steps →" and "Documentation →") with matching structure, size, and no distinguishing treatment between them.
Why it matters
With no visual hierarchy beyond the title, a new user landing here has no cue about which link to try first, so both compete equally for attention on a page whose entire job is to orient a first-time visitor.
Fix
Give one card a stronger visual treatment (border, accent, or order) to signal the recommended starting point.
<div className={styles.cardRow}>
<Link
className={styles.card}
href="https://create.t3.gg/en/usage/first-steps"
target="_blank"
><div className={styles.cardRow}>
<Link
className={`${styles.card} ${styles.cardPrimary}`}
href="https://create.t3.gg/en/usage/first-steps"
target="_blank"
>Craft
Hero title jumps to an arbitrary 5rem size on desktop
The `<h1>` uses `sm:text-[5rem]`, an arbitrary bracket value, instead of a scale step like `text-8xl` or `text-9xl`.
Why it matters
Arbitrary sizes accumulate one-off values across a codebase that don't map to any shared scale, making it harder to keep type sizes consistent as more pages get added.
Fix
Use the nearest Tailwind scale step instead of a bracketed arbitrary value.
<h1 className="text-5xl font-extrabold tracking-tight text-white sm:text-[5rem]"><h1 className="text-5xl font-extrabold tracking-tight text-white sm:text-8xl">Spacing
Components
Motion
Working well
- Wrapping the greeting text in a conditional (`hello.data ? hello.data.greeting : "Loading tRPC query..."`) means the layout never collapses to nothing while the query resolves: this is the right instinct even though the error branch is still missing.
- The sign in/sign out buttons are real `<button type="button">` elements, not clickable divs, so keyboard focus, Enter/Space activation, and screen reader announcement all work for free. Native semantics here save a whole category of a11y bugs.
- The two auth buttons ("Sign out" and "Sign in with GitHub") never render at the same time since they're mutually exclusive on `sessionData`, so hierarchy stays clear: there's always exactly one primary action visible, never two competing CTAs.
- The two card links share one structural pattern (heading + description inside a `styles.card` link) instead of two bespoke layouts, which is the right instinct: repeating a proven structure keeps the page predictable even at this small scale.
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.