uniswap/interface
Reviewed against Rams quality heuristics: accessibility, color, typography, spacing, components, motion, UX, and craft.
30 files reviewed·July 26, 2026
Elevated
Design risk in this codebase.
More findings
Verdict
Real craft undercut by unfinished accessibility follow-through: a live-data feel built on animation that never checks for reduced motion. The infinite pulse with no motion guard is the single biggest risk to ship as-is.
Files Rams reviewed
apps/web/src/pages/Landing/components/TokenCloud/index.tsx
apps/extension/src/app/components/AutoLockProvider.tsx
apps/extension/src/app/components/Input.tsx
apps/extension/src/app/components/PasswordInput.tsx
apps/extension/src/app/components/Trace/TraceUserProperties.tsx
apps/extension/src/app/components/buttons/CopyButton.tsx
apps/extension/src/app/components/buttons/OptionCard.tsx
apps/extension/src/app/components/layout/ScreenHeader.tsx
apps/extension/src/app/components/loading/SelectWalletSkeleton.tsx
apps/extension/src/app/components/modal/InfoModal.tsx
apps/extension/src/app/components/modals/SmartWalletNudgeModals.tsx
apps/extension/src/app/components/tabs/ActivityTab.tsx
apps/extension/src/app/components/tabs/NftsTab.tsx
apps/extension/src/app/components/tabs/PoolsTab.tsx
apps/extension/src/app/features/settings/components/SettingsItem.tsx
apps/web/src/pages/Landing/components/Icons.tsx
apps/web/src/pages/Landing/components/StatCard.tsx
apps/web/src/pages/Landing/components/TokenCloud/Ticker.tsx
apps/web/src/pages/Landing/components/animations.tsx
apps/web/src/pages/Landing/components/cards/DownloadWalletCard.tsx
apps/web/src/pages/Landing/components/cards/LiquidityCard.tsx
apps/web/src/pages/Landing/components/cards/PillButton.tsx
apps/web/src/pages/Landing/components/cards/TradingApiCard.tsx
apps/web/src/pages/Landing/components/cards/UnichainCard.tsx
apps/web/src/pages/Landing/components/cards/UniswapXCard.tsx
apps/web/src/pages/Landing/components/cards/ValuePropCard.tsx
apps/web/src/pages/Landing/components/cards/WebappCard.tsx
apps/web/src/pages/Liquidity/CreateAuction/components/AdvancedSettingsSeparator.tsx
apps/web/src/pages/Liquidity/CreateAuction/components/AuctionAdvancedSettings.tsx
apps/web/src/pages/Liquidity/CreateAuction/components/AuctionDistributionSection.tsx
Motion
apps/web/src/pages/Landing/components/StatCard.tsx:106
Infinite live-indicator pulse never checks prefers-reduced-motion
The `LiveIcon` pulsing dot in StatCard.tsx runs a box-shadow keyframe animation with `animation-iteration-count: infinite` and a 1000ms alternate loop, and there is no `@media (prefers-reduced-motion: reduce)` guard anywhere in the file to stop or dampen it.
Why it matters
Users with vestibular disorders who have set reduced motion at the OS level still get a permanent pulsing shape on screen, which is the exact pattern reduced-motion settings exist to prevent.
Fix
Wrap the infinite pulse animation in a prefers-reduced-motion media query and disable it (or drop to a static dot) when the user has requested reduced motion.
animation-fill-mode: forwards;
animation-direction: alternate;
animation-duration: 1000ms;
animation-iteration-count: infinite;
animation-timing-function: ease-in-out;
` animation-fill-mode: forwards;
animation-direction: alternate;
animation-duration: 1000ms;
animation-iteration-count: infinite;
animation-timing-function: ease-in-out;
@media (prefers-reduced-motion: reduce) {
animation: none;
}
`apps/web/src/pages/Landing/components/TokenCloud/Ticker.tsx:32
Ticker hover shift ignores reduced-motion preference
The `$group-item-hover` state moves the ticker row with `x: 8` on hover, and nothing in the component checks a reduced-motion preference before applying that translate.
Why it matters
Vestibular-sensitive users who set reduced motion still get the full 8px horizontal shift on every hover, since the component has no path to suppress it.
Fix
Gate the x-translate behind a reduced-motion check so hovering only changes opacity when the user has requested less motion.
$group-item-hover={{
opacity: 1,
x: 8,
}}$group-item-hover={{
opacity: 1,
x: prefersReducedMotion ? 0 : 8,
}}apps/web/src/pages/Landing/components/TokenCloud/Ticker.tsx:30
Ticker hover transition animates every property, not just opacity and position
The ticker row's `transition="all 0.1s ease-in-out"` on the absolutely-positioned `Flex` fires on any property change, not just the `opacity` and `x` values it actually toggles in `$group-item-hover`.
Why it matters
Watching `all` means unrelated re-renders (color changes, layout shifts from parent resize) trigger animation on properties like color or size that were never meant to transition, producing visual glitches that are hard to trace back to this rule.
Fix
Scope the transition to the specific properties being animated (opacity and transform) instead of the catch-all `all` keyword.
opacity={0}
x={0}
transition="all 0.1s ease-in-out"
gap={20}opacity={0}
x={0}
transition="opacity 0.1s ease-in-out, transform 0.1s ease-in-out"
gap={20}Color
apps/web/src/pages/Landing/components/StatCard.tsx:68
Live-state card background hardcodes a hex value beside a theme token
`Container`'s background-color for the live state is a raw literal, `live ? '#2FBA610A' : theme.surface2`, while the non-live branch on the same line correctly reads from `theme.surface2`. The file already imports `opacify`/`parseToRgb` and uses `theme.success` for the pulse color a few lines down.
Why it matters
A second, undocumented green enters the palette outside the theme, so dark mode or a future brand color update will miss this card and the live-state background will drift out of sync with the rest of the system.
Fix
Derive the live background from the existing success token with the same opacify helper already used for the pulse animation, instead of a hardcoded hex-with-alpha literal.
background-color: ${({ theme, live }) => (live ? '#2FBA610A' : theme.surface2)};background-color: ${({ theme, live }) => (live ? opacify(4, theme.success) : theme.surface2)};Accessibility
Typography
Spacing
Components
UX
Craft
Working well
- Detecting Arabic locale and swapping the animated sprite interpolation for a plain Text component instead of forcing RTL glyphs through a left-to-right rise animation shows real attention to internationalization: the fallback trades a decorative effect for correctness where it counts.
- Formatting the percent change once via useMemo keyed on formatPercent/pricePercentChange in Ticker.tsx avoids redundant recalculation on every render, which is the right call for a value rendered inside a hover-animated list item.
- The sprite rise animation pairs opacity and transform, both cheap GPU-compositable properties, which keeps the staggered digit reveal in StatCard smooth without triggering layout recalculation.
Scored July 26, 2026 with Rams Engine v0.0.3 · Engine changelog
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.