tremorlabs on GitHub

tremorlabs/tremor

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.

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

Top fix

Add a reduced-motion guard so the spinner stops animating indefinitely

See the fix

Verdict

Good instincts, unverified numbers: this library keeps reaching for accessible patterns without ever measuring them against a real threshold. A spinner that animates forever with no reduced-motion guard is the clearest sign that motion and a11y checks were skipped, not just missed.

Files Rams reviewed

src/components/Accordion/Accordion.tsx

src/components/Badge/Badge.tsx

src/components/BarList/BarList.tsx

src/components/Button/Button.tsx

src/components/Calendar/Calendar.tsx

src/components/Callout/Callout.tsx

src/components/CategoryBar/CategoryBar.tsx

src/components/Checkbox/Checkbox.tsx

src/components/Dialog/Dialog.tsx

src/components/Divider/Divider.tsx

src/components/DonutChart/DonutChart.tsx

src/components/Drawer/Drawer.tsx

src/components/DropdownMenu/DropdownMenu.tsx

src/components/Input/Input.tsx

src/components/Popover/Popover.tsx

src/components/ProgressBar/ProgressBar.tsx

src/components/ProgressCircle/ProgressCircle.tsx

src/components/RadioCardGroup/RadioCardGroup.tsx

src/components/RadioGroup/RadioGroup.tsx

src/components/Select/Select.tsx

src/components/SelectNative/SelectNative.tsx

src/components/Slider/Slider.tsx

src/components/SparkChart/SparkChart.tsx

src/components/Switch/Switch.tsx

src/components/TabNavigation/TabNavigation.tsx

src/components/Table/Table.tsx

src/components/Tabs/Tabs.tsx

src/components/Textarea/Textarea.tsx

src/components/Toast/Toast.tsx

src/components/Toggle/Toggle.tsx

94/100

Accessibility

3 serious
AccessibilitySerious

src/components/BarList/BarList.tsx:59

aria-sort applied to a plain div instead of a table header

The outer BarList container is a <div> carrying "aria-sort={sortOrder}". aria-sort is only a valid ARIA attribute on an element with role="columnheader" (typically a <th>).

Why it matters

Applied to a non-header div, the attribute is invalid ARIA that screen readers ignore or that accessibility linters flag, so the sort state is never actually communicated to assistive tech users.

Fix

Drop aria-sort from the div and communicate sort state through visible UI or a live region instead.

<div
  ref={forwardedRef}
  className={cx("flex justify-between space-x-6", className)}
  aria-sort={sortOrder}
  tremor-id="tremor-raw"
  {...props}
>
<div
  ref={forwardedRef}
  className={cx("flex justify-between space-x-6", className)}
  tremor-id="tremor-raw"
  {...props}
>
AccessibilitySerious

src/components/Calendar/Calendar.tsx:49

Calendar navigation buttons render below the 44px touch target floor

NavigationButton (used for previous/next month and year) sizes at "size-8" (32px) and shrinks further to "sm:size-[30px]" on larger breakpoints.

Why it matters

A 30-32px hit target on a date picker falls under the 44px minimum recommended for touch targets, making the prev/next controls hard to hit accurately on mobile.

Fix

Size icon-only navigation controls to at least 44px on touch breakpoints.

"flex size-8 shrink-0 select-none items-center justify-center rounded-sm border p-1 outline-hidden transition sm:size-[30px]",
"flex size-11 shrink-0 select-none items-center justify-center rounded-sm border p-1 outline-hidden transition sm:size-11",
AccessibilitySerious

src/components/Callout/Callout.tsx:79

Overflowing callout content has no keyboard path to scroll it

The content wrapper at line 79 sets "overflow-y-auto" on {children} but has no tabIndex, so it never enters the tab order.

Why it matters

When Callout content overflows its container, mouse users can scroll it with a wheel but keyboard-only users have no way to focus the region and scroll to the rest of the content.

Fix

Give scrollable overflow regions tabIndex=0 so keyboard users can focus and scroll them.

<div className={cx("overflow-y-auto", children ? "mt-2" : "")}>
  {children}
</div>
<div
  tabIndex={0}
  className={cx("overflow-y-auto", children ? "mt-2" : "")}
>
  {children}
</div>
95/100

Motion

1 critical1 serious
MotionCritical

src/components/Button/Button.tsx:130

Loading spinner spins forever with no reduced-motion guard

The isLoading state renders RiLoader2Fill with "animate-spin" and no prefers-reduced-motion check anywhere in the component.

Why it matters

Vestibular-sensitive users get continuous, unstoppable motion on every loading button in the system with no way to opt out.

Fix

Wrap the spin animation in a motion-safe: variant so it respects the user's reduced-motion preference.

<RiLoader2Fill
  className="size-4 shrink-0 animate-spin"
  aria-hidden="true"
/>
<RiLoader2Fill
  className="size-4 shrink-0 motion-safe:animate-spin"
  aria-hidden="true"
/>
MotionSerious

src/components/Button/Button.tsx:14

transition-all on the button base forces the browser to watch every property

The shared base class list applies "transition-all duration-100 ease-in-out" to every Button variant instead of naming the properties that actually change (background, border, box-shadow, color).

Why it matters

Watching every animatable property costs more per frame and can trigger unintended transitions the next time an unrelated CSS property changes on the element, which is wasted work multiplied across every button instance.

Fix

Scope the transition to the properties that actually change on state.

"relative inline-flex items-center justify-center whitespace-nowrap rounded-md border px-3 py-2 text-center text-sm font-medium shadow-xs transition-all duration-100 ease-in-out",
"relative inline-flex items-center justify-center whitespace-nowrap rounded-md border px-3 py-2 text-center text-sm font-medium shadow-xs transition-[color,background-color,border-color,box-shadow] duration-100 ease-in-out",

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

Color

1 serious
ColorSerious

src/components/Button/Button.tsx:24

Primary button text fails contrast against its own blue fill

The primary Button variant pairs "text-white" with "bg-blue-500" for every primary CTA in the system. White (#fff) on blue-500 (#3b82f6) computes to 3.68:1.

Why it matters

3.68:1 fails WCAG AA's 4.5:1 threshold for normal-size text, so the label on every primary action across the product reads at reduced contrast for low-vision users.

Fix

Darken the primary fill to blue-600 or deepen it further so white text clears 4.5:1.

"text-white dark:text-white",
"bg-blue-500 dark:bg-blue-500",
"text-white dark:text-white",
"bg-blue-600 dark:bg-blue-600",

Typography

No issues found

Spacing

No issues found

Components

No issues found

UX

No issues found

Craft

No issues found

Working well

  • NavigationButton always ships an explicit aria-label ('Go to previous year', 'Go to next month', etc.) even though it's icon-only, which is exactly the right pattern for icon-only controls: the label carries the meaning that the icon alone can't.
  • Button's isLoading state hides children behind an sr-only announcement ('Loading' or a custom loadingText) while keeping the spinner icon aria-hidden, giving screen readers a single clean announcement instead of raw icon noise.
  • BarList's row Component switches between button and div based on whether onValueChange is passed, so keyboard focus and interactive semantics only show up when the row is actually clickable.

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 tremorlabs/tremor’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