heroui-inc on GitHub

heroui-inc/heroui

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

30 files reviewed·August 1, 2026

View on GitHub

Elevated

Design risk in this codebase.

8issues
Critical, Serious & Moderate · top 6 shown below

Top fix

Make the Pro banner close control visible against its background

See the fix

Verdict

Promotional Pro surfaces get the gradient treatment while losing sight of who can see or reach them. The biggest risk is a close control and a link that are both literally invisible yet still interactive, a pattern that undermines trust in the whole design system.

Files Rams reviewed

apps/docs/src/app/docs/native-showcase/components/[[...slug]]/page.tsx

apps/docs/src/app/[lang]/(home)/components/demo-showcase.tsx

apps/docs/src/app/[lang]/(home)/components/pro-badge.tsx

apps/docs/src/app/[lang]/(home)/components/pro-banner.tsx

apps/docs/src/app/[lang]/(home)/components/pro-title.tsx

apps/docs/src/app/[lang]/(home)/components/release-badges.tsx

apps/docs/src/app/[lang]/themes/components/accent-color-selector.tsx

apps/docs/src/app/[lang]/themes/components/bottom-sheet.tsx

apps/docs/src/app/[lang]/themes/components/builder-header.tsx

apps/docs/src/app/[lang]/themes/components/chroma-slider.tsx

apps/docs/src/app/[lang]/themes/components/custom-fonts.tsx

apps/docs/src/app/[lang]/themes/components/font-family-popover.tsx

apps/docs/src/app/[lang]/themes/components/lockable-label.tsx

apps/docs/src/app/[lang]/themes/components/mobile-footer.tsx

apps/docs/src/app/[lang]/themes/components/onboarding.tsx

apps/docs/src/app/[lang]/themes/components/preview-container.tsx

apps/docs/src/app/[lang]/themes/components/radius-popover.tsx

apps/docs/src/app/[lang]/themes/components/reset-button.tsx

apps/docs/src/app/[lang]/themes/components/shuffle-button.tsx

apps/docs/src/app/[lang]/themes/components/suggested-fonts.tsx

apps/docs/src/app/[lang]/themes/components/switch-mode.tsx

apps/docs/src/app/[lang]/themes/components/theme-builder-content.tsx

apps/docs/src/app/[lang]/themes/components/theme-code-panel.tsx

apps/docs/src/app/[lang]/themes/components/theme-input.tsx

apps/docs/src/app/[lang]/themes/components/theme-popover.tsx

apps/docs/src/app/[lang]/themes/components/themes-list.tsx

apps/docs/src/app/[lang]/(home)/layout.tsx

apps/docs/src/app/[lang]/(home)/page.tsx

apps/docs/src/app/[lang]/(home)/showcase/layout.tsx

apps/docs/src/app/[lang]/blog/[slug]/page.tsx

93/100

Accessibility

1 critical2 serious
AccessibilityCritical

apps/docs/src/app/[lang]/(home)/components/pro-banner.tsx:254

Pro banner close control is invisible against its own background

The dismiss control on the pro banner (CloseButton, absolute end-2 top-2) uses text-white/50 over the light lavender-to-blue gradient (#E9E9FF to #CCE5F1) that fills the banner header. White at 50% opacity over a near-white background computes to roughly 1.2:1 contrast, so the icon is effectively invisible against its own fill.

Why it matters

Users who want to dismiss the banner can't find the control by sight, and the ones who do find it by trial-and-error location have no visual confirmation it's interactive. A permanently fixed, unreadable close button on a floating banner blocks a core exit path.

Fix

Set close-icon contrast to at least 4.5:1 against the header gradient using a dark neutral token instead of a translucent white.

className="absolute end-2 top-2 bg-transparent text-white/50 hover:bg-white/10 hover:text-white"
className="absolute end-2 top-2 bg-transparent text-foreground/70 hover:bg-black/10 hover:text-foreground"
AccessibilitySerious

apps/docs/src/app/[lang]/(home)/components/demo-showcase.tsx:236

Invisible 'Pro template' link stays reachable by keyboard on the components tab

The "Pro template" anchor in demo-showcase.tsx toggles to opacity-0 pointer-events-none when selectedTab is 'components', but it stays in the DOM and in tab order. A keyboard user tabbing through the toolbar lands on a link with no visible focus target.

Why it matters

Keyboard users hit a focus stop with nothing to see, breaking the expected correspondence between visible UI and tab order and making the toolbar feel broken.

Fix

Remove hidden interactive elements from the tab order with tabIndex={-1} and aria-hidden whenever they're visually suppressed.

<a
  className={`text-[13px] font-medium text-muted transition-opacity duration-300 hover:text-foreground ${selectedTab !== "components" ? "opacity-100" : "pointer-events-none opacity-0"}`}
  rel="noopener noreferrer"
  target="_blank"
  href={getProUrl({ campaign: "demo-showcase", content: `template-${selectedTab}`, medium: "homepage" })}
>
  {demo.proTemplate}
</a>
<a
  className={`text-[13px] font-medium text-muted transition-opacity duration-300 hover:text-foreground ${selectedTab !== "components" ? "opacity-100" : "pointer-events-none opacity-0"}`}
  rel="noopener noreferrer"
  target="_blank"
  tabIndex={selectedTab !== "components" ? 0 : -1}
  aria-hidden={selectedTab === "components"}
  href={getProUrl({ campaign: "demo-showcase", content: `template-${selectedTab}`, medium: "homepage" })}
>
  {demo.proTemplate}
</a>
AccessibilitySerious

apps/docs/src/app/[lang]/(home)/components/demo-showcase.tsx:253

Color swatches in the theme picker have no accessible names

The ColorSwatchPicker.Item elements in demo-showcase.tsx render only a Swatch and an Indicator for each hex value in colors, with no aria-label or visible text tied to the color name.

Why it matters

A screen reader user navigating the picker hears identical unlabeled controls for every option and can't tell 'pink' from 'red' from 'orange', making the accent-color choice unusable without sight.

Fix

Give every non-text control a computed accessible name so it doesn't rely on color perception alone.

{colors.map((color) => (
  <ColorSwatchPicker.Item key={color} color={color}>
    <ColorSwatchPicker.Swatch />
    <ColorSwatchPicker.Indicator />
  </ColorSwatchPicker.Item>
))}
{colors.map((color) => (
  <ColorSwatchPicker.Item key={color} color={color} aria-label={color}>
    <ColorSwatchPicker.Swatch />
    <ColorSwatchPicker.Indicator />
  </ColorSwatchPicker.Item>
))}
98/100

Color

1 serious
ColorSerious

apps/docs/src/app/[lang]/(home)/components/pro-banner.tsx:236

Countdown label text fails contrast against the banner's gradient header

The "Ends in" countdown label in pro-banner.tsx uses text-[#4E75A5] at text-xs size over a gradient header that runs from #E9E9FF to #CCE5F1. That combination computes to roughly 4:1 contrast, which fails WCAG AA's 4.5:1 threshold for small text.

Why it matters

Low-vision users reading the discount countdown at 12px see faint blue-gray text against a near-white background, making time-sensitive promotional copy hard to read at the moment it matters most.

Fix

Darken small text to reach at least 4.5:1 contrast against its lightest background stop.

<span className="relative text-xs font-medium text-[#4E75A5]">
<span className="relative text-xs font-medium text-[#33507A]">

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

Components

1 serious
Design SystemSerious

apps/docs/src/app/[lang]/(home)/components/pro-banner.tsx:322

Pro CTA overrides its own primary variant with hardcoded black/white

The "Explore Pro" / "Get Pro deal" link passes variant: "primary" into buttonVariants but then overrides it with className: "bg-black text-white dark:bg-white dark:text-black" in pro-banner.tsx, bypassing whatever fill and text pairing the primary variant defines.

Why it matters

When this button's primary variant changes anywhere else in the design system, this instance silently stays black/white, so future rebrands or accent updates miss this CTA and the button drifts out of sync with every other primary action.

Fix

Let the primary variant supply its own background and text color instead of overriding it locally.

className={buttonVariants({
  className: "bg-black text-white dark:bg-white dark:text-black",
  variant: "primary",
})}
className={buttonVariants({
  variant: "primary",
})}
98/100

UX

1 serious
UXSerious

apps/docs/src/app/[lang]/(home)/components/release-badges.tsx:107

Touch users have no way to pause the badge carousel before it rotates away

The badge carousel pauses only on onMouseEnter and onFocusCapture in release-badges.tsx. Touch devices have neither persistent hover nor a tap-to-focus equivalent, so a mobile user reading a badge has no way to stop the interval before it advances.

Why it matters

Mobile visitors, a large share of homepage traffic, lose the ability to read a badge on their own schedule, and the content the carousel exists to show becomes read-only-if-you're-fast.

Fix

Give touch users an explicit pause affordance instead of relying solely on hover and focus events.

onMouseEnter={() => setPaused(true)}
onMouseLeave={() => setPaused(false)}
onMouseEnter={() => setPaused(true)}
onMouseLeave={() => setPaused(false)}
onTouchStart={() => setPaused((p) => !p)}

Typography

No issues found

Spacing

No issues found

Motion

No issues found

Craft

No issues found

Working well

  • This is a static SVG icon component with hardcoded gradient/fill hex values, which is standard and correct practice for brand icon assets: icons like this are logo-like and not part of the themeable UI token system.
  • The four gradient stops all reuse the exact same three-color ramp (#617BF8 to #6AC6F7 to #90CAF6) across every path, which keeps the mark visually coherent instead of drifting hue per letterform.
  • The comments explaining why dynamicParams is true and why the native app intercepts this route before the browser loads it are genuinely useful context, not narration filler restating the code.
  • The theme builder link uses Tooltip with delay={0} paired with an isIconOnly ghost button and Palette icon, giving an icon-only control a discoverable label on hover.

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 heroui-inc/heroui’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.

Or get a design review on every pull requestInstall Rams