Rams MCP · The full engine, now in your coding agent
skyvern-ai on GitHub

skyvern-ai/skyvern

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 accessible name and pressed state to password toggle button

See the fix

Verdict

Solid form plumbing keeps getting undercut by the same two unfixed details everywhere: icon-only buttons with no accessible name, and animations that never stop or check contrast. Credit where due, the underlying architecture (resize logic, form state) is genuinely thoughtful, but polish gaps repeat across files instead of being fixed once.

Files Rams reviewed

skyvern-frontend/src/components/PageLayout.tsx

skyvern-frontend/src/components/AnimatedWave.tsx

skyvern-frontend/src/components/ApiWebhookActionsMenu.tsx

skyvern-frontend/src/components/AutoResizingTextarea/AutoResizingTextarea.tsx

skyvern-frontend/src/components/AzureClientSecretCredentialTokenForm.tsx

skyvern-frontend/src/components/BitwardenCredentialForm.tsx

skyvern-frontend/src/components/ClearCredentialDialog.tsx

skyvern-frontend/src/components/CopyApiCommandDropdown.tsx

skyvern-frontend/src/components/CustomCredentialServiceConfigForm.tsx

skyvern-frontend/src/components/DataSchemaInputGroup/WorkflowDataSchemaInputGroup.tsx

skyvern-frontend/src/components/DeleteConfirmationDialog.tsx

skyvern-frontend/src/components/EngineSelector.tsx

skyvern-frontend/src/components/FileUpload.tsx

skyvern-frontend/src/components/Flippable.tsx

skyvern-frontend/src/components/GeoTargetSelector.tsx

skyvern-frontend/src/components/GlobalNotificationListener.tsx

skyvern-frontend/src/components/ImprovePrompt.tsx

skyvern-frontend/src/components/KeyValueInput.tsx

skyvern-frontend/src/components/ModelSelector.tsx

skyvern-frontend/src/components/NavLinkGroup.tsx

skyvern-frontend/src/components/NoticeMe.tsx

skyvern-frontend/src/components/OnePasswordTokenForm.tsx

skyvern-frontend/src/components/Orgwalled.tsx

skyvern-frontend/src/components/ParameterAutocompleteDropdown.tsx

skyvern-frontend/src/components/ParameterGhostText.tsx

skyvern-frontend/src/components/ProxySelector.tsx

skyvern-frontend/src/components/PushTotpCodeForm.tsx

skyvern-frontend/src/components/RadialMenu.tsx

skyvern-frontend/src/components/RotateThrough.tsx

skyvern-frontend/src/components/SelectionBar.tsx

94/100

UX

3 serious
UX·skyvern-frontend/src/components/ApiWebhookActionsMenu.tsx:74Serious

Clipboard copy failures leave the user with no feedback at all

Both "Copy cURL (Unix/Linux/macOS)" and "Copy PowerShell (Windows)" call `copyText(...).then(...)` with no `.catch()`. If the clipboard write throws (denied permission, insecure context, unsupported browser), the promise rejects silently and no toast fires.

Why it matters

The user clicks the menu item, sees no toast, and has no way to know whether the copy worked or failed. They either assume it worked and paste garbage, or retry the same broken action indefinitely.

Fix

Add a `.catch()` that shows an error toast so failure is as visible as success.

const { curl } = generateApiCommands(getOptions());
copyText(curl).then(() => {
  toast({
    variant: "success",
    title: "Copied to Clipboard",
    description:
      "The cURL command has been copied to your clipboard.",
  });
});
const { curl } = generateApiCommands(getOptions());
copyText(curl)
  .then(() => {
    toast({
      variant: "success",
      title: "Copied to Clipboard",
      description:
        "The cURL command has been copied to your clipboard.",
    });
  })
  .catch(() => {
    toast({
      variant: "destructive",
      title: "Copy Failed",
      description: "Couldn't copy the command. Try again.",
    });
  });
UX·skyvern-frontend/src/components/AutoResizingTextarea/AutoResizingTextarea.tsx:65Serious

Resize logic has no guard against runaway height on huge pasted content

`measureAndSize` sets `style.height` directly to `scrollHeight + 2` with no upper bound. A user pasting a very large block of text (logs, JSON, a long prompt) grows the textarea to match the full content height with nothing capping it.

Why it matters

An unbounded textarea can push surrounding page content far below the fold or off-screen inside a modal/drawer, forcing users to scroll the whole page just to see the rest of a form.

Fix

Cap the computed height with a max-height and let overflow-y switch to auto once the cap is hit, rather than growing without limit.

if (lastHeightRef.current !== newHeight) {
  lastHeightRef.current = newHeight;
  textareaElement.style.height = newHeight;
const cappedHeight = `${Math.min(measured + 2, 480)}px`;
if (lastHeightRef.current !== cappedHeight) {
  lastHeightRef.current = cappedHeight;
  textareaElement.style.height = cappedHeight;
  textareaElement.style.overflowY = measured + 2 > 480 ? "auto" : "hidden";
UX·skyvern-frontend/src/components/ClearCredentialDialog.tsx:65Serious

Confirm button stays clickable while clearing, enabling double-submit

The confirm button at the bottom of the dialog shows "Clearing..." during the async call but its `disabled` prop is bound only to the `disabled` prop, not `isPending`: - Cancel button: `disabled={isPending}` (correctly locked during the operation) - Confirm button: `disabled={disabled}` (not locked during the operation) A user can click the destructive confirm button repeatedly while `onConfirm()` is in flight.

Why it matters

Repeated clicks during a pending destructive action can fire `onConfirm` multiple times, risking duplicate clear requests or race conditions in whatever credential-clearing logic sits upstream.

Fix

Disable the action button whenever the async operation is pending, not only when the caller disables it.

disabled={disabled}
disabled={disabled || isPending}
97/100

Accessibility

1 critical
Accessibility·skyvern-frontend/src/components/AzureClientSecretCredentialTokenForm.tsx:165Critical

Icon-only password toggle has no accessible name or pressed state

The visibility toggle inside the "Client Secret" field renders only `<EyeOpenIcon />` / `<EyeClosedIcon />` with no `aria-label` and no `aria-pressed`.

Why it matters

Screen reader users hear an unlabeled button with no indication of what it does or whether the secret is currently shown, so they can't safely reveal or re-hide a credential value.

Fix

Give icon-only toggle controls a stable aria-label plus aria-pressed reflecting the visibility state.

<Button
  type="button"
  variant="ghost"
  size="sm"
  className="absolute right-0 top-0 h-full px-3 py-2 hover:bg-transparent"
  onClick={toggleClientSecretVisibility}
  disabled={isLoading || isMutating}
>
<Button
  type="button"
  variant="ghost"
  size="sm"
  className="absolute right-0 top-0 h-full px-3 py-2 hover:bg-transparent"
  onClick={toggleClientSecretVisibility}
  disabled={isLoading || isMutating}
  aria-label="Show client secret"
  aria-pressed={showClientSecret}
>

Want this on your repo?

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

Review my public repo for free
97/100

Motion

1 critical
Motion·skyvern-frontend/src/components/AnimatedWave.tsx:11Critical

Wave animation loops forever with no reduced-motion escape hatch

The `@keyframes wave` block and the inline `animationIterationCount: "infinite"` on each character span run permanently with no `prefers-reduced-motion` guard anywhere in the file.

Why it matters

Users with vestibular disorders who set reduced-motion at the OS level still get the full bouncing animation on every character, every time this component renders, with no way to opt out.

Fix

Wrap the keyframe declaration in @media (prefers-reduced-motion: no-preference) so the animation only runs when the user hasn't opted out.

  .animate-wave {
    animation-name: wave;
  }
`}</style>
  @media (prefers-reduced-motion: no-preference) {
    .animate-wave {
      animation-name: wave;
    }
  }
`}</style>
98/100

Color

1 serious
Color·skyvern-frontend/src/components/BitwardenCredentialForm.tsx:91Serious

Status label 'Active' fails contrast at 3.29:1 against white

The status `<span>` renders "Active" in `text-green-600` (#16a34a) at `text-sm` on a white card background. Computed contrast is ~3.29:1.

Why it matters

Falls below WCAG AA's 4.5:1 threshold for small text, so users with low vision struggle to read whether their Bitwarden credential is active.

Fix

Darken the green to a shade with contrast of 4.5:1 or higher against white.

<span
  className={`text-sm ${bitwardenOrganizationAuthToken.valid ? "text-green-600" : "text-red-600"}`}
>
<span
  className={`text-sm ${bitwardenOrganizationAuthToken.valid ? "text-green-700" : "text-red-600"}`}
>

Typography

No issues found

Spacing

No issues found

Components

No issues found

Craft

No issues found

Working well

  • Reusing `<ClearCredentialDialog>` for the destructive "Clear Credential" action instead of hand-rolling a confirm dialog is the right call: it keeps focus trapping and confirmation copy ("Clear Azure Client Secret Credential?") consistent with every other credential form in the app instead of inventing a one-off modal.
  • The z-index override ships with a comment explaining exactly why this dialog needs to outrank the base layer (nested inside the cloud integrations tile dialog). That's the right instinct: a magic number alone is debt, but a magic number with a documented reason is at least traceable when someone revisits it.
  • Feature-detecting `ResizeObserver` before using it (`if (typeof ResizeObserver === "undefined") return;`) is the right defensive pattern for a shared component: it keeps jsdom-based tests from crashing while still getting the real behavior in browsers, without needing a separate test-only code path.
  • The 'Update Credential' button swaps its own label to "Updating..." while `isUpdating` is true instead of showing a separate spinner element. This keeps the feedback anchored exactly where the user's eye already is, so they don't have to look elsewhere on the page to know 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.