supabase/supabase
Reviewed against Rams quality heuristics: accessibility, color, typography, spacing, components, motion, UX, and craft.
30 files reviewed·August 1, 2026
Elevated
Design risk in this codebase.
More findings
Verdict
Decorative motion outruns accessibility here, animations and marquees keep running for the users least able to escape them. The biggest risk is a critical reduced-motion gap sitting right next to otherwise sound, simple layouts.
Files Rams reviewed
blocks/vue/registry/default/password-based-auth/nuxtjs/app/pages/protected/index.vue
apps/studio/components/interfaces/ConnectSheet/content/nextjs/app/supabasejs/content.tsx
apps/studio/components/interfaces/ConnectSheet/content/nextjs/pages/supabasejs/content.tsx
apps/www/app/state-of-startups/components/CohortToggle.tsx
apps/www/app/state-of-startups/components/DecorativeProgressBar.tsx
apps/www/app/state-of-startups/components/ParticipantsCarousel.tsx
apps/www/app/state-of-startups/components/SectionCallout.tsx
apps/www/app/state-of-startups/components/StateOfStartupsAuroraHeader.tsx
apps/www/app/state-of-startups/components/StateOfStartupsHeader.tsx
apps/www/app/state-of-startups/components/SurveyChannelMixChart.tsx
apps/www/app/state-of-startups/components/SurveyChapter.tsx
apps/www/app/state-of-startups/components/SurveyChapterSection.tsx
apps/www/app/state-of-startups/components/SurveyChart.tsx
apps/www/app/state-of-startups/components/SurveyChartShell.tsx
apps/www/app/state-of-startups/components/SurveyCompareStatCard.tsx
apps/www/app/state-of-startups/components/SurveyCrossTabChart.tsx
apps/www/app/state-of-startups/components/SurveyPullQuote.tsx
apps/www/app/state-of-startups/components/SurveyPullQuoteCarousel.tsx
apps/www/app/state-of-startups/components/SurveyPullQuoteGrid.tsx
apps/www/app/state-of-startups/components/SurveyRankedAnswersPair.tsx
apps/www/app/state-of-startups/components/SurveyStatCard.tsx
apps/www/app/state-of-startups/components/SurveySummarizedAnswer.tsx
apps/www/app/state-of-startups/components/SurveyWordCloud.tsx
apps/www/app/state-of-startups/components/TwoOptionToggle.tsx
apps/www/app/state-of-startups/components/YearToggle.tsx
apps/www/app/state-of-startups/components/surveyResults.css
blocks/vue/registry/default/dropzone/nuxtjs/app/components/dropzone-content.vue
blocks/vue/registry/default/dropzone/nuxtjs/app/components/dropzone-empty-state.vue
blocks/vue/registry/default/dropzone/nuxtjs/app/components/dropzone.vue
blocks/vue/registry/default/password-based-auth/nuxtjs/app/components/forgot-password-form.vue
Motion
apps/www/app/state-of-startups/components/DecorativeProgressBar.tsx:33
Progress bar animation loops forever with no reduced-motion guard
The animated foreground bar in ProgressBar sets animation: terminalLine 10s steps(8, end) ... infinite directly in inline style, with no prefers-reduced-motion check anywhere in the component or its callers.
Why it matters
Users with vestibular disorders who set prefers-reduced-motion: reduce still get an infinite stepped animation running indefinitely on every render of this bar, which is exactly the class of motion that guideline exists to stop.
Fix
Gate any infinite or auto-playing animation behind a prefers-reduced-motion media query that disables or freezes it.
style={{
maskImage: 'url("/images/state-of-startups/pattern-checker.svg")',
maskSize: '4px',
maskRepeat: 'repeat',
maskPosition: 'top left',
clipPath: 'inset(0 100% 0 0)',
animation: `terminalLine 10s steps(8, end) ${animationDelay} infinite ${reverse ? 'reverse' : ''}`,
animationFillMode: 'both',
}}style={{
maskImage: 'url("/images/state-of-startups/pattern-checker.svg")',
maskSize: '4px',
maskRepeat: 'repeat',
maskPosition: 'top left',
clipPath: 'inset(0 100% 0 0)',
animation: `terminalLine 10s steps(8, end) ${animationDelay} infinite ${reverse ? 'reverse' : ''}`,
animationFillMode: 'both',
}}
className="motion-reduce:animate-none motion-reduce:[clip-path:inset(0)]"apps/www/app/state-of-startups/components/ParticipantsCarousel.tsx:54
Marquee only pauses on mouse hover, so keyboard focus keeps sliding away
surveyResults.css defines a hover pause on .marquee-row, but the row and its Link children in ParticipantsCarousel have no focus-within equivalent, so the CSS only stops the scroll on :hover.
Why it matters
A keyboard user who tabs onto a company link inside the marquee gets a focus ring that immediately scrolls out of view as the track keeps animating, making it impossible to read or reliably activate the focused link.
Fix
Pause continuously animating content on keyboard focus the same way it pauses on hover.
className="marquee-row group relative w-full overflow-hidden"className="marquee-row group relative w-full overflow-hidden focus-within:[&_.marquee-track]:[animation-play-state:paused]"UX
blocks/vue/registry/default/password-based-auth/nuxtjs/app/pages/protected/index.vue:23
Logout button gives no feedback while signOut is in flight
The "Logout" button in protected/index.vue calls the async handleLogout, which awaits supabase.auth.signOut() before redirecting, but the button never disables or shows a pending state during that await.
Why it matters
A user who taps "Logout" twice before the network call resolves fires signOut() twice and can hit a race between the second call and the router.replace redirect, producing an inconsistent auth state or a confusing flash before landing on /auth/login.
Fix
Disable the trigger and show pending state for any button that starts an async action with a visible side effect.
const handleLogout = async () => {
await supabase.auth.signOut()
router.replace("/auth/login")
}const loggingOut = ref(false)
const handleLogout = async () => {
if (loggingOut.value) return
loggingOut.value = true
await supabase.auth.signOut()
router.replace("/auth/login")
}blocks/vue/registry/default/password-based-auth/nuxtjs/app/pages/protected/index.vue:30
Root wrapper uses h-screen, breaking centering under mobile browser chrome
The outer div wrapping the "Checking authentication..." and logged-in states uses class="flex h-screen w-full items-center justify-center". h-screen resolves to the static 100vh, which does not account for mobile Safari's collapsing address bar.
Why it matters
On iOS Safari the content can render taller than the visible viewport when the browser chrome is expanded, pushing the centered "Hello {{ email }}" block and Logout button partly under the address bar or nav controls.
Fix
Use min-h-screen or the dvh unit for full-bleed vertical centering on mobile viewports.
<div class="flex h-screen w-full items-center justify-center"><div class="flex min-h-dvh w-full items-center justify-center">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 freeAccessibility
apps/www/app/state-of-startups/components/CohortToggle.tsx:30
Cohort toggle pills fall well under the 44px touch target minimum
The cohort toggle buttons (rendered from CohortToggleOption.label, e.g. any option text) use className with px-3 py-1 and text-xs, which renders roughly 24-26px tall.
Why it matters
Touch targets under ~26px height are hard to hit precisely on a phone, increasing mis-taps between adjacent pills and slowing down cohort selection on mobile.
Fix
Give tappable pill controls a minimum height of 44px through padding or a min-h utility.
className={`px-3 py-1 text-xs font-mono uppercase tracking-wider border rounded-full transition-colors ${
isActive
? 'border-brand-500/40 bg-brand-300/40 text-brand-link dark:text-brand'
: 'border-overlay text-foreground-light hover:text-foreground hover:bg-surface-100'
}`}className={`min-h-[44px] px-3 py-2.5 text-xs font-mono uppercase tracking-wider border rounded-full transition-colors flex items-center ${
isActive
? 'border-brand-500/40 bg-brand-300/40 text-brand-link dark:text-brand'
: 'border-overlay text-foreground-light hover:text-foreground hover:bg-surface-100'
}`}Components
apps/www/app/state-of-startups/components/CohortToggle.tsx:29
Toggle button variant logic is a raw ternary template literal, not a shared helper
The active/inactive className on the cohort toggle button is built with a hand-rolled template literal ternary instead of a cn()/clsx style class-merge helper.
Why it matters
As more variants get added (disabled, hover-only, loading), string concatenation like this becomes error-prone to edit and hard to diff, raising the maintenance cost of every future style change to this component.
Fix
Compose conditional Tailwind classes through a cn() helper instead of manual template literal ternaries.
className={`px-3 py-1 text-xs font-mono uppercase tracking-wider border rounded-full transition-colors ${
isActive
? 'border-brand-500/40 bg-brand-300/40 text-brand-link dark:text-brand'
: 'border-overlay text-foreground-light hover:text-foreground hover:bg-surface-100'
}`}className={cn(
'px-3 py-1 text-xs font-mono uppercase tracking-wider border rounded-full transition-colors',
isActive
? 'border-brand-500/40 bg-brand-300/40 text-brand-link dark:text-brand'
: 'border-overlay text-foreground-light hover:text-foreground hover:bg-surface-100'
)}Typography
Color
Spacing
Craft
Working well
- The cohort toggle's active state (border-brand-500/40 bg-brand-300/40 text-brand-link) versus the inactive outline (border-overlay text-foreground-light) gives a real tinted-fill versus plain-outline distinction, and the label text itself still identifies the selected filter, so the signal isn't color-only.
- The participants shuffle uses shuffleSeeded(participants, 1) with a comment explaining it exists to keep SSR and client renders deterministic, which is a clean, deliberate fix for a real hydration-mismatch risk rather than an arbitrary Math.random call.
- Redirecting with router.replace("/auth/login") instead of router.push in both the auth check and handleLogout correctly prevents the back button from returning a signed-out user to the protected page.
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 supabase/supabase’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.