shadcn/ui expert — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited shadcn/ui expert (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
Use shadcn/ui as a composable, copy-owned component baseline. Customise design tokens to match the product; never treat the library as a locked black box.
npx shadcn@latest init@/components, @/lib) and Tailwind config are correct.src/components/uisrc/components/features or src/components/<domain>components.json committed and stable across teammates.cn merge helper in src/lib/utils.ts:import { clsx, type ClassValue } from 'clsx'
import { twMerge } from 'tailwind-merge'
export function cn(...inputs: ClassValue[]) { return twMerge(clsx(inputs)) }Override CSS variables in globals.css — this is how shadcn separates colour from component code:
:root {
--background: 0 0% 100%;
--foreground: 222.2 84% 4.9%;
--primary: 221.2 83.2% 53.3%;
--muted: 210 40% 96.1%;
--muted-foreground: 215.4 16.3% 46.9%;
--border: 214.3 31.8% 91.4%;
--ring: 221.2 83.2% 53.3%;
--radius: 0.5rem;
}
.dark {
--background: 222.2 84% 4.9%;
--foreground: 210 40% 98%;
}Maintain one source of truth for radius, spacing, and typography rhythm.
shadcn Card is a composition — always use Card/CardHeader/CardTitle/CardContent:
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
function StatCard({ icon: Icon, label, value, variant = 'slate' }: {
icon: React.ElementType
label: string
value: string | number
variant?: 'slate' | 'red' | 'green'
}) {
const colors = {
slate: 'text-slate-600',
red: 'text-red-500',
green: 'text-green-600',
}
return (
<Card>
<CardContent className="pt-4">
<div className="flex items-center gap-3">
<Icon className={cn('h-5 w-5', colors[variant])} />
<div>
<p className="text-xs text-muted-foreground uppercase tracking-wide">{label}</p>
<p className={cn('text-2xl font-semibold tabular-nums', colors[variant])}>{value}</p>
</div>
</div>
</CardContent>
</Card>
)
}<div className="grid grid-cols-2 gap-3 sm:grid-cols-4">
<StatCard icon={TrendingUp} label="Meetings/mo" value={kpis.monthly_meetings ?? '—'} />
<StatCard icon={Target} label="Reply rate" value={kpis.target_reply_rate ? `${kpis.target_reply_rate}%` : '—'} variant="green" />
<StatCard icon={AlertTriangle} label="Max bounce" value={kpis.max_bounce_rate ? `${kpis.max_bounce_rate}%` : '—'} variant="red" />
<StatCard icon={DollarSign} label="Budget" value={campaign.budget ?? '—'} />
</div>Render string arrays as pill badges with optional muted/strikethrough variant:
function TagPills({ tags, muted = false }: { tags: string[]; muted?: boolean }) {
if (!tags?.length) return null
return (
<div className="flex flex-wrap gap-1.5">
{tags.map((t) => (
<span
key={t}
className={cn(
'rounded-full px-2 py-0.5 text-xs font-medium',
muted
? 'bg-slate-100 text-slate-400 line-through'
: 'bg-blue-50 text-blue-700 border border-blue-200'
)}
>
{t}
</span>
))}
</div>
)
}Use Set<string> state to track which sections are collapsed; Chevron icon flips on toggle:
import { ChevronDown, ChevronRight } from 'lucide-react'
const [collapsed, setCollapsed] = useState<Set<string>>(new Set(['advanced', 'testing']))
function toggleSection(key: string) {
setCollapsed(prev => {
const next = new Set(prev)
next.has(key) ? next.delete(key) : next.add(key)
return next
})
}
// In render
<div className="border rounded-lg overflow-hidden">
<button
onClick={() => toggleSection('linkedin')}
className="w-full flex items-center justify-between px-4 py-3 bg-sky-50 border-b border-sky-200"
>
<span className="text-sm font-semibold text-sky-700 flex items-center gap-2">
<LinkedinIcon className="h-4 w-4" /> LinkedIn Sourcing
</span>
{collapsed.has('linkedin')
? <ChevronRight className="h-4 w-4 text-sky-500" />
: <ChevronDown className="h-4 w-4 text-sky-500" />}
</button>
{!collapsed.has('linkedin') && <div className="p-4">{/* content */}</div>}
</div>Use Fragment to inject a second detail row without breaking table semantics:
import { Fragment, useState } from 'react'
function ExpandableRow({ run }: { run: ExecutionRun }) {
const [open, setOpen] = useState(false)
return (
<Fragment>
<TableRow
className="cursor-pointer hover:bg-muted/50"
onClick={() => setOpen(o => !o)}
>
<TableCell>{open ? <ChevronDown /> : <ChevronRight />}</TableCell>
<TableCell>{run.skill_name}</TableCell>
<TableCell>{run.status}</TableCell>
<TableCell>{run.created_at}</TableCell>
</TableRow>
{open && (
<TableRow>
<TableCell colSpan={4} className="bg-muted/30 px-6 py-3">
<pre className="text-xs whitespace-pre-wrap font-mono text-muted-foreground">
{run.error_message || JSON.stringify(run.output_data, null, 2)}
</pre>
</TableCell>
</TableRow>
)}
</Fragment>
)
}import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from '@/components/ui/dialog'
import { Button } from '@/components/ui/button'
export function DeleteDialog() {
return (
<Dialog>
<DialogTrigger asChild>
<Button variant="destructive">Delete</Button>
</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle>Delete this item?</DialogTitle>
</DialogHeader>
</DialogContent>
</Dialog>
)
}import { Input } from '@/components/ui/input'
import { Label } from '@/components/ui/label'
export function EmailField() {
return (
<div className="grid gap-2">
<Label htmlFor="email">Work email</Label>
<Input id="email" type="email" placeholder="[email protected]" />
</div>
)
}For dashboards with multiple functional categories, define a colour map keyed to category strings:
const categoryColor: Record<string, string> = {
setup: 'bg-blue-50 border-blue-200 text-blue-700',
enrichment: 'bg-purple-50 border-purple-200 text-purple-700',
outbound: 'bg-green-50 border-green-200 text-green-700',
linkedin_sourcing:'bg-sky-50 border-sky-200 text-sky-700',
analytics: 'bg-orange-50 border-orange-200 text-orange-700',
testing: 'bg-amber-50 border-amber-200 text-amber-700',
}
// Usage: className={cn('border rounded-lg', categoryColor[category])}motion.div while preserving shadcn semantics and focus management.src/components/ui._Last reviewed: 2026-05-14 — automated polish pass per issue #88._
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.