shadcn/ui Components: Where User-Controlled HTML Sneaks In

shadcn/ui is copy-paste components, which means you own them — and any sanitization they miss. Here are the three props that vibe-coded apps pass user content to and regret.
shadcn/ui gives you control, but control means you're responsible for every render path. Three props in the default shadcn library are reliably user-controlled in vibe-coded apps and reliably un-sanitized.
1. <Toast description={...}>
The default Toast variants render description as a React child. If you pass dangerouslySetInnerHTML or unescaped error messages from your API, they render.
toast({ description: error.message }); // "error.message" came from user inputFix: strip HTML before rendering.
2. <AlertDialogDescription>
Same story. The component is a <p> tag with children. React's JSX auto-escaping saves you for text — until someone passes an element:
<AlertDialogDescription>
{response.message /* "Hello <script>alert(1)</script>" */}
</AlertDialogDescription>If message is a JSX node (server-sent), it renders. Text strings are safe; nested components aren't.
3. <Badge variant="destructive">{dynamic}</Badge>
Looks innocuous. Becomes bad when {dynamic} is <img src=x onerror=alert(1) />. React escapes text; it doesn't escape elements.
The fix
- Strip HTML with
sanitize-htmlor DOMPurify before you pass anything to a child prop. - Treat API responses like untrusted — never render a
messagefield from an error as JSX.
VibeWShield's XSS scanner enumerates form fields and replays them with payloads. Anything that round-trips into the DOM un-escaped is a reflected XSS finding.
Free security scan
Test your app for these vulnerabilities
VibeWShield automatically scans for everything covered in this article and more — 18 security checks in under 3 minutes.
Scan your app free