skyvern-ai/skyvern
Reviewed against Rams quality heuristics: accessibility, color, typography, spacing, components, motion, UX, and craft.
30 files reviewed·July 25, 2026
More findings
Verdict
Solid component plumbing keeps failing to tell users when something actually failed. Copy buttons lie with success toasts, webhook tests invite double-clicks, and one missing min-w-0 lets a workflow ID overflow its box.
Files Rams reviewed
skyvern-frontend/src/components/PageLayout.tsx
skyvern-frontend/src/components/AgentFilterDropdown.tsx
skyvern-frontend/src/components/AnimatedWave.tsx
skyvern-frontend/src/components/ApiWebhookActionsMenu.tsx
skyvern-frontend/src/components/ArtifactDownloadLink.tsx
skyvern-frontend/src/components/ArtifactVideo.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/GoogleOAuthClientConfigForm.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
UX
skyvern-frontend/src/components/ArtifactDownloadLink.tsx:49
A failed URL mint leaves an open tab or click with zero feedback
handleClick opens a blank tab synchronously, then calls void freshArtifactUrl(credentialGetter, href).then((url) => { newTab.location.href = url } or window.location.assign(url) }). There is no .catch, so if freshArtifactUrl rejects, the opened tab stays permanently blank (or the click does nothing in the same-tab case) with no error surfaced.
Why it matters
A user clicking a download link that fails to mint sees an empty tab or an inert link and has no way to know whether to retry or report a problem.
Fix
Attach a .catch to async navigation promises and surface a visible error state instead of leaving the tab blank.
void freshArtifactUrl(credentialGetter, href).then((url) => {
if (newTab) {
newTab.location.href = url;
} else {
window.location.assign(url);
}
});void freshArtifactUrl(credentialGetter, href)
.then((url) => {
if (newTab) {
newTab.location.href = url;
} else {
window.location.assign(url);
}
})
.catch(() => {
if (newTab) {
newTab.close();
}
toast({
variant: "destructive",
title: "Download Failed",
description: "Could not open this artifact. Try again.",
});
});skyvern-frontend/src/components/ApiWebhookActionsMenu.tsx:71
Copy cURL and PowerShell items show a success toast even when the copy fails
Both "Copy cURL (Unix/Linux/macOS)" and "Copy PowerShell (Windows)" call copyText(...).then(() => toast({variant: "success", ...})) with no .catch. If the clipboard write rejects (permissions, insecure context, browser quirk), no toast fires at all and the user has no signal the copy didn't happen.
Why it matters
A user who believes the command copied will paste nothing into their terminal, then debug a mystery empty paste instead of retrying the copy immediately.
Fix
Attach a .catch to clipboard promises and show an error toast so failures are as visible as successes.
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: "Could not copy the cURL command to your clipboard.",
});
});skyvern-frontend/src/components/ApiWebhookActionsMenu.tsx:103
"Test Webhook" gives no in-flight feedback, inviting repeat triggers
The "Test Webhook" DropdownMenuItem runs setTimeout(() => onTestWebhook(), 0) on select, but the item never disables or shows a pending state while the webhook call is running. Only webhookDisabled (an external prop) can disable it.
Why it matters
A user unsure whether the click registered can reopen the menu and fire Test Webhook multiple times in a row, triggering duplicate webhook deliveries with no visual cue anything happened.
Fix
Disable the trigger and show a pending state for the duration of an in-flight async action.
<DropdownMenuItem
disabled={webhookDisabled}
onSelect={() => {
setTimeout(() => onTestWebhook(), 0);
}}
>
Test Webhook
</DropdownMenuItem><DropdownMenuItem
disabled={webhookDisabled || isTestingWebhook}
onSelect={() => {
setIsTestingWebhook(true);
setTimeout(() => {
Promise.resolve(onTestWebhook()).finally(() =>
setIsTestingWebhook(false),
);
}, 0);
}}
>
{isTestingWebhook ? "Testing…" : "Test Webhook"}
</DropdownMenuItem>Accessibility
skyvern-frontend/src/components/AgentFilterDropdown.tsx:122
"Clear all" button renders under the 44px minimum touch target
The "Clear all" button uses className="h-7 px-2 text-xs", rendering at 28px tall inside a py-1 container. That's well under the 44px minimum interactive target recommended by WCAG 2.5.5.
Why it matters
On mobile, a 28px target is easy to miss or mis-tap, especially next to the search input directly above it, causing accidental dismiss or repeated failed taps.
Fix
Size interactive controls to at least 44x44px, or pad the hit area even if the visual chip stays small.
<div className="flex justify-end border-b px-2 py-1">
<Button
type="button"
variant="ghost"
size="sm"
className="h-7 px-2 text-xs"
onClick={() => onChange([])}
>
Clear all
</Button>
</div><div className="flex justify-end border-b px-2 py-2">
<Button
type="button"
variant="ghost"
size="sm"
className="h-9 px-3 text-xs"
onClick={() => onChange([])}
>
Clear all
</Button>
</div>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 freeTypography
skyvern-frontend/src/components/AgentFilterDropdown.tsx:166
Truncated workflow ID overflows because the flex child has no min-w-0
In the "Selected" CommandGroup, the workflowPermanentId span uses className="truncate font-mono text-xs text-muted-foreground" as a direct child of a flex CommandItem (className="gap-2"). Flex items default to min-width: auto, so a truncated child can't shrink below its content width and the ellipsis never triggers, letting long IDs spill past the row edge.
Why it matters
Long workflow IDs break the dropdown's fixed-width layout, pushing or clipping adjacent rows and making the selected-agents list look broken instead of a clean single-line list.
Fix
Add min-w-0 to any flex child that needs its truncate class to actually clamp width.
<CommandItem
key={workflowPermanentId}
value={workflowPermanentId}
className="gap-2"
onSelect={() => toggleWorkflow(workflowPermanentId)}
>
<Checkbox
checked
tabIndex={-1}
className="pointer-events-none"
/>
<span className="truncate font-mono text-xs text-muted-foreground">
{workflowPermanentId}
</span>
</CommandItem><CommandItem
key={workflowPermanentId}
value={workflowPermanentId}
className="gap-2"
onSelect={() => toggleWorkflow(workflowPermanentId)}
>
<Checkbox
checked
tabIndex={-1}
className="pointer-events-none"
/>
<span className="min-w-0 flex-1 truncate font-mono text-xs text-muted-foreground">
{workflowPermanentId}
</span>
</CommandItem>Motion
skyvern-frontend/src/components/AnimatedWave.tsx:20
Infinite per-character wave animation ignores prefers-reduced-motion
Each character span gets className="animate-wave inline-block" with animationIterationCount: "infinite" and no media query guard in the injected <style> block. The wave keeps running for every visitor regardless of their OS motion setting.
Why it matters
Vestibular-sensitive users who have set reduced motion at the OS level still get a continuous bobbing animation with no way to opt out short of leaving the page.
Fix
Wrap infinite or repeating animations in a prefers-reduced-motion: no-preference guard, or disable animation-iteration-count under prefers-reduced-motion: reduce.
.animate-wave {
animation-name: wave;
}
`}</style> .animate-wave {
animation-name: wave;
}
@media (prefers-reduced-motion: reduce) {
.animate-wave {
animation: none;
}
}
`}</style>Color
Spacing
Components
Craft
Working well
- ArtifactDownloadLink opens the new tab synchronously inside the click handler before the async URL mint resolves, then assigns location.href once ready. That ordering is the correct fix for popup blockers in Safari/Firefox while preserving new-tab behavior.
- AgentFilterDropdown debounces search input with a separate isTyping flag to drive the skeleton loading state, which avoids skeleton flicker between fast keystrokes and real fetches: a subtle but correct pattern for async filter UIs.
- AnimatedWave staggers each character's animation-delay by index * 0.1s, producing a per-letter ripple without extra markup or JS timers: a clean, readable way to sequence character-level motion.
Scored July 25, 2026 with Rams Engine v0.0.3 · Engine changelog
First scored July 9, 2026: 59/100. This rescore on v0.0.3: 86/100.
This page is an automated design review of skyvern-ai/skyvern’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.