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

dubinc/dub

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

30 files reviewed·July 9, 2026

Elevated

Design risk in this codebase.

6issues
Critical & Serious

Top fix

Add a real, visible submit button to every admin form

See the fix

Verdict

Admin utilities repeat the same gap across every file: solid async logic, loading states, and confirm dialogs, but no visible submit button or input labels anywhere. The biggest risk is these tools only work for whoever already knows the hidden Enter-key shortcut.

Files Rams reviewed

apps/web/app/(ee)/admin.dub.co/(dashboard)/components/ban-link.tsx

apps/web/app/(ee)/admin.dub.co/(dashboard)/components/delete-partner-account.tsx

apps/web/app/(ee)/admin.dub.co/(dashboard)/components/impersonate-user.tsx

apps/web/app/(ee)/admin.dub.co/(dashboard)/components/impersonate-workspace.tsx

apps/web/app/(ee)/admin.dub.co/(dashboard)/components/reset-login-attempts.tsx

apps/web/app/(ee)/admin.dub.co/(dashboard)/components/slack-support-invite.tsx

apps/web/app/(ee)/admin.dub.co/(dashboard)/components/user-info.tsx

apps/web/app/(ee)/admin.dub.co/(dashboard)/domains/components/refresh-domain.tsx

apps/web/app/(ee)/admin.dub.co/(dashboard)/domains/components/register-premium-domain.tsx

apps/web/app/(ee)/admin.dub.co/(dashboard)/domains/components/renew-domain.tsx

apps/web/app/(ee)/admin.dub.co/(dashboard)/commissions/page.tsx

apps/web/app/(ee)/admin.dub.co/(dashboard)/domains/page.tsx

apps/web/app/(ee)/admin.dub.co/(dashboard)/page.tsx

apps/web/app/(ee)/admin.dub.co/(dashboard)/partners/fraud/page.tsx

apps/web/app/(ee)/admin.dub.co/(dashboard)/partners/network/page.tsx

apps/web/app/(ee)/admin.dub.co/(dashboard)/payouts/paypal/page.tsx

apps/web/app/(ee)/admin.dub.co/(dashboard)/payouts/stablecoin/page.tsx

apps/web/app/(ee)/admin.dub.co/(dashboard)/programs/sales/page.tsx

apps/web/app/(ee)/admin.dub.co/(dashboard)/revenue/page.tsx

apps/web/app/(ee)/app.dub.co/embed/referrals/page.tsx

apps/web/app/(ee)/partners.dub.co/(apply)/[programSlug]/(default)/apply/page.tsx

apps/web/app/(ee)/partners.dub.co/(apply)/[programSlug]/(default)/apply/success/page.tsx

apps/web/app/(ee)/partners.dub.co/(apply)/[programSlug]/(default)/layout.tsx

apps/web/app/(ee)/partners.dub.co/(apply)/[programSlug]/(default)/page.tsx

apps/web/app/(ee)/partners.dub.co/(auth-login-register)/(generic)/layout.tsx

apps/web/app/(ee)/partners.dub.co/(auth-login-register)/(generic)/login/page.tsx

apps/web/app/(ee)/partners.dub.co/(auth-login-register)/(generic)/register/page.tsx

apps/web/app/(ee)/partners.dub.co/(auth-other)/invite/page.tsx

apps/web/app/(ee)/partners.dub.co/(auth-other)/layout.tsx

apps/web/app/(ee)/partners.dub.co/(dashboard)/messages/layout.tsx

90/100

Accessibility

2 critical2 serious
Accessibility·apps/web/app/(ee)/admin.dub.co/(dashboard)/components/impersonate-user.tsx:75Critical

Email input has no label, only a placeholder that vanishes on input

The `email` input in `Form` has `id="email"` and `placeholder="panic@thedis.co"` but no `<label htmlFor="email">` anywhere in the tree.

Why it matters

Screen reader users get no announced name for the field once they focus it, and the placeholder disappears the moment a value is typed, so sighted users lose the field's purpose too.

Fix

Add a visible label element tied to the input via htmlFor/id.

<div className="relative flex w-full rounded-md shadow-sm">
  <input
    name="email"
    id="email"
    type="email"
<div className="relative flex w-full rounded-md shadow-sm">
  <label htmlFor="email" className="sr-only">
    User email
  </label>
  <input
    name="email"
    id="email"
    type="email"
Accessibility·apps/web/app/(ee)/admin.dub.co/(dashboard)/components/slack-support-invite.tsx:121Critical

Submit button loses all focus indication for keyboard users

The "Send invite" button sets `focus:outline-none` but never adds a `ring` width utility, only `hover:bg-neutral-700`. Tabbing to the primary action leaves no visible focus state at all.

Why it matters

Keyboard-only users tabbing through the form can't tell where focus is when they reach the submit button, so they can't confirm the action they're about to trigger before pressing Enter.

Fix

Never remove the outline without a visible focus-visible replacement.

className={cn(
  "rounded-md bg-neutral-900 px-4 py-2 text-sm font-medium text-white hover:bg-neutral-700 focus:outline-none",
  pending && "opacity-50",
)}
className={cn(
  "rounded-md bg-neutral-900 px-4 py-2 text-sm font-medium text-white hover:bg-neutral-700 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-neutral-500 focus-visible:ring-offset-2",
  pending && "opacity-50",
)}
Accessibility·apps/web/app/(ee)/admin.dub.co/(dashboard)/components/ban-link.tsx:63Serious

aria-invalid is hardcoded to true regardless of the input's real state

The input carries `aria-invalid="true"` as a static prop, not tied to any validation state check.

Why it matters

Screen readers announce every valid entry as an error, so assistive tech users get false failure signals on every interaction and stop trusting the field's feedback.

Fix

Bind aria-invalid to actual validation state or remove it.

placeholder="IG47WZs"
aria-invalid="true"
placeholder="IG47WZs"
Accessibility·apps/web/app/(ee)/admin.dub.co/(dashboard)/components/impersonate-workspace.tsx:43Serious

Slug input has no associated label for screen reader users

The `<input id="slug" name="slug" />` sits next to a visual `app.dub.co` prefix span, but nothing announces what the field is for. There is no `<label htmlFor="slug">` and no `aria-label`.

Why it matters

A screen reader user tabs into this field and hears only 'edit text, required' with no indication it expects a workspace slug, so they have to guess or abandon the lookup.

Fix

Associate every form input with a label, using sr-only text when a visual label isn't wanted.

<div className="relative flex w-full rounded-md shadow-sm">
  <span className="inline-flex items-center rounded-l-md border border-r-0 border-neutral-300 bg-neutral-50 px-5 text-neutral-500 sm:text-sm">
    app.dub.co
  </span>
<div className="relative flex w-full rounded-md shadow-sm">
  <label htmlFor="slug" className="sr-only">
    Workspace slug
  </label>
  <span className="inline-flex items-center rounded-l-md border border-r-0 border-neutral-300 bg-neutral-50 px-5 text-neutral-500 sm:text-sm">
    app.dub.co
  </span>
97/100

UX

1 critical
UX·apps/web/app/(ee)/admin.dub.co/(dashboard)/components/reset-login-attempts.tsx:47Critical

Form has no visible submit button, so mouse-only users can't reset attempts

The `<form>` in `ResetLoginAttempts` wraps a single email `<input>` and a `Form` component with no `<button type="submit">` anywhere. The only way to trigger the reset is pressing Enter inside the input.

Why it matters

Anyone relying on a mouse, touch, or an assistive device that doesn't submit forms via Enter has no way to trigger the action at all: this is a hard lockout for that group, not just an awkward affordance.

Fix

Add a visible, labeled submit button so the primary action is discoverable and clickable, not implicit.

      {pending && (
        <LoadingSpinner className="absolute inset-y-0 right-2 my-auto h-full w-5 text-neutral-400" />
      )}
    </div>
  );
};
      {pending && (
        <LoadingSpinner className="absolute inset-y-0 right-2 my-auto h-full w-5 text-neutral-400" />
      )}
      <button
        type="submit"
        disabled={pending}
        className="ml-2 rounded-md bg-neutral-900 px-4 py-2 text-sm font-medium text-white disabled:opacity-50"
      >
        Reset
      </button>
    </div>
  );
};

Want this on your repo?

Rams reviews your next PR automatically and posts inline fix suggestions.

Review my public repo for free
98/100

Components

1 serious
Components·apps/web/app/(ee)/admin.dub.co/(dashboard)/components/delete-partner-account.tsx:90Serious

Destructive checkbox has a 16px hit area with no extended tap zone

The `<input type="checkbox" className="h-4 w-4 ..." />` for "Delete partner account as well" is 16x16px with no padding or extended hit area, sitting in a `flex items-center space-x-2` row.

Why it matters

This checkbox controls whether a partner's entire account is deleted versus just their Stripe connection, one of the highest-consequence toggles on the page, yet its tap target falls well under the 24px WCAG 2.2 AA minimum for touch.

Fix

Wrap the checkbox and label in a single clickable label element with padding to enlarge the effective hit area.

<div className="flex items-center space-x-2">
  <input
    name="deletePartnerAccount"
    id="deletePartnerAccount"
    type="checkbox"
    disabled={pending}
    className="h-4 w-4 rounded border-neutral-300 text-neutral-600 focus:ring-neutral-500"
  />
  <label
    htmlFor="deletePartnerAccount"
    className="text-sm text-neutral-700"
  >
    Delete partner account as well
  </label>
</div>
<label
  htmlFor="deletePartnerAccount"
  className="flex cursor-pointer items-center gap-2 py-2"
>
  <input
    name="deletePartnerAccount"
    id="deletePartnerAccount"
    type="checkbox"
    disabled={pending}
    className="h-4 w-4 rounded border-neutral-300 text-neutral-600 focus:ring-neutral-500"
  />
  <span className="text-sm text-neutral-700">
    Delete partner account as well
  </span>
</label>

Typography

No issues found

Color

No issues found

Spacing

No issues found

Motion

No issues found

Craft

No issues found

Working well

  • The confirm() message correctly branches on the checkbox state, telling the admin explicitly whether they're about to delete the full partner account plus Stripe, or just the Stripe express account. Naming the exact consequence before the destructive action fires is exactly what UX-022 asks for, even though it's done through a native dialog rather than a custom one.
  • The `Form` component disables the input and swaps in a `LoadingSpinner` while `pending` is true, and `BanButton` passes `loading={pending}` to its own button. This is the right pattern: it prevents double-submits and gives the user visible feedback that the async request is in flight, satisfying both the loading-state and double-submit rules in one move.
  • Wrapping the ban action in a native `confirm()` before the fetch fires means a destructive action (banning a user and deleting their workspaces) can't happen from a single accidental click. Pairing that confirmation with a `variant="danger"` button keeps the destructive intent visually distinct from the neutral impersonate action above it.
  • The submit button swaps its own label between "Send invite" and "Sending…" while disabling itself and dropping to opacity-50 during the request. This is the correct pattern: state, label, and interactivity all change together so there's no window where a user can double-submit or wonder if the click registered.

Score your own repo with Rams.

Free on public repos. Rams reviews every pull request for accessibility, color, type, spacing, components, motion, UX, and craft, and posts inline fixes.