product-design-craft — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited product-design-craft (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.
Transform interfaces from "works correctly" to "feels like a funded startup built this." This skill encodes the design DNA of elite products: Linear, Stripe, Superhuman, Figma, Notion, and Slack.
Never use default type scales. Create intentional hierarchy:
/* Recommended scale: 1.2 ratio (minor third) */
--text-xs: 0.694rem; /* 11px - metadata, timestamps */
--text-sm: 0.833rem; /* 13px - secondary content */
--text-base: 1rem; /* 16px - body text */
--text-lg: 1.2rem; /* 19px - subheadings */
--text-xl: 1.44rem; /* 23px - section headers */
--text-2xl: 1.728rem; /* 28px - page titles */
--text-3xl: 2.074rem; /* 33px - hero text */
/* Line heights tighten as size increases */
--leading-tight: 1.25; /* headings */
--leading-snug: 1.375; /* subheadings */
--leading-normal: 1.5; /* body */
--leading-relaxed: 1.625; /* small text */Font pairing strategy:
Build semantic, not static palettes:
/* Foundation: Neutral scale (10 steps minimum) */
--gray-50 through --gray-950
/* Semantic tokens - the actual interface */
--color-bg-primary: var(--gray-50);
--color-bg-secondary: var(--gray-100);
--color-bg-tertiary: var(--gray-200);
--color-bg-inverse: var(--gray-900);
--color-text-primary: var(--gray-900);
--color-text-secondary: var(--gray-600);
--color-text-tertiary: var(--gray-500);
--color-text-inverse: var(--gray-50);
--color-border-default: var(--gray-200);
--color-border-strong: var(--gray-300);
--color-border-focus: var(--brand-500);
/* Brand: Primary action color */
--brand-50 through --brand-950
/* Status colors: Each needs full scale for backgrounds, text, borders */
--success-*, --warning-*, --error-*, --info-*Dark mode strategy: Don't just invert. Dark backgrounds should be warm grays (not pure black), text should be slightly off-white (not #fff), and colors need different saturation levels.
Use a base-4 or base-8 system consistently:
/* Base-4 system */
--space-1: 0.25rem; /* 4px - tight internal */
--space-2: 0.5rem; /* 8px - default internal */
--space-3: 0.75rem; /* 12px - comfortable internal */
--space-4: 1rem; /* 16px - component padding */
--space-6: 1.5rem; /* 24px - section spacing */
--space-8: 2rem; /* 32px - group separation */
--space-12: 3rem; /* 48px - major sections */
--space-16: 4rem; /* 64px - page-level breaks *//* Hover: Subtle, immediate */
.button:hover {
background: var(--color-bg-hover);
transition: background 100ms ease-out;
}
/* Focus: Visible, accessible */
.button:focus-visible {
outline: 2px solid var(--color-border-focus);
outline-offset: 2px;
}
/* Active: Tactile feedback */
.button:active {
transform: scale(0.98);
transition: transform 50ms ease-out;
}
/* Disabled: Clearly non-interactive */
.button:disabled {
opacity: 0.5;
cursor: not-allowed;
pointer-events: none;
}shadcn provides excellent foundations. The craft is in customization:
1. Override the default theme in globals.css:
@layer base {
:root {
/* Replace default slate with your gray scale */
--background: 0 0% 98%; /* Warm off-white, not pure white */
--foreground: 240 10% 10%; /* Slightly warm black */
/* Custom radius - smaller feels more refined */
--radius: 0.375rem; /* 6px instead of default 8px */
/* Custom primary that's not blue */
--primary: 262 83% 58%; /* Purple example */
--primary-foreground: 0 0% 100%;
}
.dark {
--background: 240 10% 8%; /* Warm dark, not pure black */
--foreground: 0 0% 95%; /* Off-white, not pure white */
}
}2. Extend component variants in your component files:
// Add custom variants beyond default/destructive/outline
const buttonVariants = cva(
"inline-flex items-center justify-center...",
{
variants: {
variant: {
default: "...",
// Add: subtle variant for secondary actions
subtle: "bg-secondary/50 text-secondary-foreground hover:bg-secondary/80",
// Add: ghost with colored hover
"ghost-primary": "hover:bg-primary/10 hover:text-primary",
},
size: {
default: "h-9 px-4 py-2",
// Add: compact for dense UIs
compact: "h-7 px-2 text-xs",
}
}
}
)3. Add motion to shadcn components:
// Wrap Dialog content with motion
<Dialog>
<DialogContent asChild>
<motion.div
initial={{ opacity: 0, scale: 0.95, y: 10 }}
animate={{ opacity: 1, scale: 1, y: 0 }}
exit={{ opacity: 0, scale: 0.95, y: 10 }}
transition={{ duration: 0.15, ease: "easeOut" }}
>
{/* content */}
</motion.div>
</DialogContent>
</Dialog>4. Custom focus rings (replace default):
@layer base {
/* Remove default focus, add custom */
*:focus {
outline: none;
}
*:focus-visible {
outline: 2px solid hsl(var(--ring));
outline-offset: 2px;
border-radius: calc(var(--radius) + 2px);
}
}| Action | Duration | Easing |
|---|---|---|
| Micro-feedback (hover, press) | 50-100ms | ease-out |
| Small transitions (tooltips, dropdowns) | 100-150ms | ease-out |
| Medium transitions (modals, panels) | 150-250ms | ease-out or spring |
| Large transitions (page, view change) | 250-400ms | ease-in-out |
| Stagger delay between items | 30-50ms | - |
/* Utility classes for common transitions */
.transition-fast { transition: all 100ms ease-out; }
.transition-base { transition: all 150ms ease-out; }
.transition-slow { transition: all 250ms ease-out; }
/* Entrance animations */
@keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
@keyframes slideUp {
from { opacity: 0; transform: translateY(8px); }
to { opacity: 1; transform: translateY(0); }
}
@keyframes scaleIn {
from { opacity: 0; transform: scale(0.95); }
to { opacity: 1; transform: scale(1); }
}
/* Apply with stagger */
.stagger-item {
animation: slideUp 200ms ease-out backwards;
}
.stagger-item:nth-child(1) { animation-delay: 0ms; }
.stagger-item:nth-child(2) { animation-delay: 30ms; }
.stagger-item:nth-child(3) { animation-delay: 60ms; }
/* ... */Always provide reduced motion alternatives:
@media (prefers-reduced-motion: reduce) {
*, *::before, *::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
}
}Never show blank space. Every empty state needs:
┌─────────────────────────────────────┐
│ │
│ [Illustration] │
│ │
│ No projects yet │
│ │
│ Projects help you organize work │
│ into focused collections. │
│ │
│ [+ Create project] │
│ │
└─────────────────────────────────────┘Match the content shape (skeleton screens):
.skeleton {
background: linear-gradient(
90deg,
var(--gray-200) 0%,
var(--gray-100) 50%,
var(--gray-200) 100%
);
background-size: 200% 100%;
animation: skeleton-shimmer 1.5s ease-in-out infinite;
border-radius: var(--radius);
}
@keyframes skeleton-shimmer {
0% { background-position: 200% 0; }
100% { background-position: -200% 0; }
}Errors should help, not blame:
For fast-feeling UIs:
For power user tools:
Before considering a component/page complete:
For detailed micro-interaction patterns, animation recipes, and delight techniques, see references/delight-catalog.md.
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.