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

expo/expo

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

30 files reviewed·August 1, 2026

View on GitHub

Top fix

Group the six buttons under headings for screen reader navigation

See the fix

Verdict

Six buttons in a row work fine for sighted testers but tell assistive tech nothing about structure. The theme half-commits to tokens, hardcoding '#fff' right next to a token-based primary, and a failed permission fetch just disappears.

Files Rams reviewed

apps/notification-tester/src/app/_layout.tsx

apps/native-component-list/src/components/Page.tsx

apps/brownfield-tester/expo-app/src/app/_layout.tsx

apps/brownfield-tester/expo-app/src/app/apis/_layout.tsx

apps/brownfield-tester/expo-app/src/app/apis/index.tsx

apps/notification-tester/src/app/index.tsx

apps/observe-tester/app/_layout.tsx

apps/router-e2e/__e2e__/01-rsc/app/_layout.tsx

apps/router-e2e/__e2e__/headless/app/_layout.tsx

apps/router-e2e/__e2e__/native-navigation/app/modals/_layout.tsx

apps/router-e2e/__e2e__/native-navigation/app/tabs/_layout.tsx

apps/router-e2e/__e2e__/native-tabs/app/_layout.tsx

apps/router-e2e/__e2e__/split-view/app/_layout.tsx

apps/router-e2e/__e2e__/stack/app/_layout.tsx

apps/router-e2e/__e2e__/web-modal/app/_layout.tsx

apps/router-e2e/__e2e__/web-modal/app/nested/page.tsx

packages/expo-sqlite/dev-plugin-webui/src/app/_layout.tsx

packages/expo-sqlite/dev-plugin-webui/src/app/index.tsx

templates/expo-template-default/src/app/_layout.tsx

templates/expo-template-default/src/app/index.tsx

templates/expo-template-tabs/app/(tabs)/_layout.tsx

templates/expo-template-tabs/app/_layout.tsx

apps/brownfield-tester/expo-app/src/app/apis/communication.tsx

apps/brownfield-tester/expo-app/src/app/apis/native-modules.tsx

apps/brownfield-tester/expo-app/src/app/apis/navigation.tsx

apps/brownfield-tester/expo-app/src/app/apis/state.tsx

apps/expo-go/ios/Client/SwiftUI/Views/AvatarView.swift

apps/expo-go/ios/Client/SwiftUI/Views/BranchDetailsView.swift

apps/expo-go/ios/Client/SwiftUI/Views/BranchesListView.swift

apps/expo-go/ios/Client/SwiftUI/Views/EmptyStateView.swift

98/100

Accessibility

1 serious
AccessibilitySerious

apps/notification-tester/src/app/index.tsx:11

Six sequential buttons with no heading leave screen reader users guessing at grouping

The index screen stacks six React Native Buttons in a row: "Run on-device tests", "Go to NCL NotificationScreen", "Go to playground", "See Expo ui", "Go to test scenarios", and "Get Notification permissions", plus a Notifier component. Each Button carries its own visible title as its accessible name, but there's no heading or accessibility grouping distinguishing navigation actions from the permission-check action.

Why it matters

A screen reader user has to read through all six titles in sequence with no landmark or section cue, so understanding that "Get Notification permissions" is a different kind of action from the five navigation buttons takes longer than it should.

Fix

Add an accessibilityRole="header" label or wrap related buttons in a labeled View to give assistive tech a grouping structure.

<ScrollView contentContainerStyle={{ rowGap: 10, padding: 10 }}>
  <Button title="Run on-device tests" onPress={() => router.push('/run')} />
<ScrollView contentContainerStyle={{ rowGap: 10, padding: 10 }}>
  <Text accessibilityRole="header">Navigation</Text>
  <Button title="Run on-device tests" onPress={() => router.push('/run')} />
98/100

Color

1 serious
ColorSerious

apps/notification-tester/src/app/_layout.tsx:48

Dark theme text and notification colors hardcode '#fff' beside a token-based primary

CustomNavigationDarkTheme sets primary from ThemeColors.dark.tint, but text and notification two lines later are raw '#fff' literals instead of any ThemeColors reference.

Why it matters

Mixing tokenized and hardcoded values in the same object means a future dark-mode palette change (e.g. an off-white text color for warmth) updates primary but leaves text and notification stuck on pure white, producing inconsistent contrast across the theme.

Fix

Source text and notification colors from ThemeColors.dark to keep every value in the theme object on the same token layer.

const CustomNavigationDarkTheme = {
  ...DarkTheme,
  colors: {
    ...DarkTheme.colors,
    primary: ThemeColors.dark.tint,
    text: '#fff',
    notification: '#fff',
  },
};
const CustomNavigationDarkTheme = {
  ...DarkTheme,
  colors: {
    ...DarkTheme.colors,
    primary: ThemeColors.dark.tint,
    text: ThemeColors.dark.text,
    notification: ThemeColors.dark.tint,
  },
};

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

UX

1 serious
UXSerious

apps/notification-tester/src/app/index.tsx:24

Permission fetch failure on "Get Notification permissions" vanishes silently

The onPress handler for "Get Notification permissions" calls getPermissionsAsync().then(...).catch((error) => console.error(error)). Success shows an Alert with the JSON payload, but failure only writes to the console.

Why it matters

A tester who taps the button and sees nothing happen has no way to tell if the request failed, is still pending, or if permissions were denied, so they can't distinguish a real bug from a dead tap.

Fix

Surface fetch errors to the user with the same Alert mechanism used for the success path.

getPermissionsAsync()
  .then((permissions) => Alert.alert(JSON.stringify(permissions, null, 2)))
  .catch((error) => console.error(error));
getPermissionsAsync()
  .then((permissions) => Alert.alert(JSON.stringify(permissions, null, 2)))
  .catch((error) => Alert.alert('Failed to get permissions', String(error)));

Typography

No issues found

Spacing

No issues found

Components

No issues found

Motion

No issues found

Craft

No issues found

Working well

  • Every navigation destination uses native Button components, which keep built-in press feedback and accessible names for free instead of hand-rolled Pressable divs that need manual role and label wiring.
  • Forcing light mode with setColorScheme('light') is explained inline with the comment "it just looks better", so a future reader understands the override is deliberate rather than a bug to fix.
  • CustomNavigationDarkTheme.colors.primary pulling from ThemeColors.dark.tint shows the token layer is set up correctly; the gaps are in the fields that don't use it yet.

Scored August 1, 2026 with Rams Engine v0.0.3 · Engine changelog
First scored July 29, 2026: 98/100. This rescore on v0.0.3: 94/100.

This page is an automated design review of expo/expo’s UI code: 30 files read against 291 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