Imported from itaisinai/interviews-tracker (
.claude/skills/design-system-enforcer/SKILL.md). Install upstream withnpx skills add itaisinai/interviews-tracker --skill design-system-enforcer. Copyright stays with the author.
Design System Enforcer Skill
Purpose
Ensure UI consistency, identify reusable patterns, and enforce design system best practices. Prevent one-off components and promote extraction to @interviews-tracker/design-system.
When to Use
ā When creating or modifying UI components
- Before implementing new UI
- When copying/pasting component code
- When styling components
ā Before completing frontend work
- Check for extraction opportunities
- Validate token usage
- Ensure consistency
ā During component extraction discussions
- Evaluate extraction criteria
- Plan migration approach
What This Skill Checks
1. Duplicate UI Patterns (3+ Uses Rule)
Detection criteria:
- Same visual style across 3+ files
- Similar component structure
- Repeated layout patterns
- Identical prop interfaces
Example Finding:
š” MODERATE: Duplicate status badge pattern
Found in:
- apps/web/src/components/opportunity-card.tsx:45
- apps/web/src/components/interaction-summary.tsx:78
- apps/web/src/components/company-header.tsx:32
- apps/web/src/pages/dashboard-page.tsx:156
Pattern: Badge with color based on status enum
Recommendation: Extract to design-system/components/status-badge/
2. Hardcoded Values vs Semantic Tokens
Check for:
- ā Hardcoded colors:
bg-blue-500,text-gray-700 - ā Magic numbers:
p-4,gap-3,rounded-lg - ā One-off styles:
style={{ padding: '12px' }} - ā Should use: Token-backed Tailwind classes
Valid tokens:
/* From packages/design-system/src/styles/tokens.css */
color.background.app
color.background.surface
color.text.primary
color.text.secondary
color.border.default
color.action.primary
color.status.success
Example Finding:
š¢ MINOR: Hardcoded color values
apps/web/src/components/custom-button.tsx:12
<button className="bg-blue-600 text-white">
Recommendation: Use semantic tokens
<button className="bg-action-primary text-on-action-primary">
3. Business Logic in UI Components
Detect:
- Data fetching in presentational components
- Business rules in component files
- Prisma types in frontend components
- Backend-specific logic
Example Finding:
š“ CRITICAL: Business logic in UI component
apps/web/src/components/interaction-card.tsx:89
Component contains overdue promotion logic
Fix: Move to lib/interaction-status.ts, import as utility
UI components should be pure presentation
4. Component Structure Violations
Check folder structure:
packages/design-system/src/components/
button/
button.tsx ā
button.stories.tsx ā
index.ts ā
utils.tsx ā Should be in components/utils/
MyComponent.tsx ā Should use kebab-case folder
Example Finding:
š” MODERATE: Component structure violation
packages/design-system/src/components/StatusBadge.tsx
Fix: Create folder structure:
components/status-badge/
status-badge.tsx
status-badge.stories.tsx
index.ts
5. Design System Extraction Opportunities
Evaluate for extraction:
- ā Appears 3+ times
- ā Business-agnostic (no Opportunity/Interaction logic)
- ā Stable API (not rapidly changing)
- ā Reusable across features
Keep local if:
- ā Single use, unlikely to repeat
- ā Tightly coupled to feature
- ā Experimental/prototyping
- ā Feature-specific customization
Example Finding:
š” MODERATE: Extraction opportunity
Pattern: ParticipantRow (person name + role + avatar)
Occurrences: 4 (interactions drawer, company page, contact list, opportunity detail)
Business logic: None (pure presentation)
Recommendation: Extract to design-system/components/participant-row/
Output Format
Finding Structure
[Icon] [SEVERITY]: [Issue Title]
[Description]
Location(s):
- file/path.tsx:line
- file/path2.tsx:line
Recommendation: [Action]
[Optional: Code example]
Severity Levels
š“ CRITICAL - Fix immediately
- Business logic in UI
- Security issues (XSS, injection)
- Type safety violations
š” MODERATE - Address soon
- 3+ duplicate patterns
- Significant token violations
- Missing Storybook stories
š¢ MINOR - Nice to have
- 2 duplicate patterns (watch for 3rd)
- Minor styling inconsistencies
- Missing component docs
Extraction Checklist
When recommending extraction, verify:
ā Extraction Criteria
- Pattern appears 3+ times (or will soon)
- No business-specific logic (Opportunity/Interaction/Company)
- Stable, unlikely to change rapidly
- Clear prop interface
- Reusable across different features
ā Design System Structure
- Component folder uses kebab-case
- Has component file (.tsx)
- Has story file (.stories.tsx)
- Has barrel export (index.ts)
- Uses semantic tokens (not hardcoded)
ā Migration Plan
- Create in design-system first
- Add Storybook story
- Update one consumer (test)
- Update remaining consumers
- Remove duplicates
- Visual regression test
Common Issues & Fixes
Issue: Hardcoded Colors
Bad:
<div className="bg-blue-600 text-white border-gray-300">
Good:
<div className="bg-action-primary text-on-action-primary border-default">
Issue: Duplicate Badge Component
Bad: 3 separate implementations
// File 1
<span className="rounded-full bg-green-100 px-3 py-1 text-green-800">
{status}
</span>
// File 2
<span className="rounded-full bg-green-100 px-3 py-1 text-green-800">
{status}
</span>
// File 3
<span className="rounded-full bg-green-100 px-3 py-1 text-green-800">
{status}
</span>
Good: Single design-system component
// packages/design-system/src/components/badge/badge.tsx
export function Badge({ children, variant = "default" }: BadgeProps) {
return <span className={`badge badge-${variant}`}>{children}</span>;
}
// Usage
import { Badge } from "@interviews-tracker/design-system";
<Badge variant="success">{status}</Badge>;
Issue: Business Logic in Component
Bad:
// Component file
function InteractionCard({ interaction }: Props) {
// Overdue promotion logic in component
const isOverdue =
interaction.status === "SCHEDULED" &&
new Date(interaction.date) < new Date();
return <div>{isOverdue ? "NEEDS_FOLLOW_UP" : interaction.status}</div>;
}
Good:
// lib/interaction-status.ts
export function promoteOverdueInteractionStatusForRead(interaction) {
// Logic here
}
// Component file
import { promoteOverdueInteractionStatusForRead } from "../../lib/interaction-status";
function InteractionCard({ interaction }: Props) {
const promoted = promoteOverdueInteractionStatusForRead(interaction);
return <div>{promoted.status}</div>;
}
Current Design System Components
Available primitives:
ā
Button, IconButton
ā
Badge
ā
Card, Modal, Drawer
ā
Input, Textarea, Select, FormField
ā
DataTable
ā
Calendar
ā
LoadingState, Spinner
ā
MaterialIcon
Check before creating new:
- Does Button/IconButton work?
- Can Badge be styled for your use case?
- Does Card provide the container you need?
- Can FormField wrap your input?
Extraction Candidates
Patterns seen 3+ times (extract these):
1. StatusBadge
// Pattern: Badge with color based on enum
<Badge variant={getVariantForStatus(status)}>{status}</Badge>
2. TimelineItem
// Pattern: Timeline entry with icon, date, content
<div className="timeline-item">
<Icon />
<Date />
<Content />
</div>
3. DetailRow
// Pattern: Label + Value display
<div className="flex justify-between">
<span className="label">{label}</span>
<span className="value">{value}</span>
</div>
4. EmptyState
// Pattern: Icon + Message + Action
<div className="empty-state">
<Icon />
<p>No items yet</p>
<Button>Add First Item</Button>
</div>
5. ParticipantRow
// Pattern: Avatar + Name + Role
<div className="flex items-center gap-3">
<Avatar />
<div>
<div className="name">{name}</div>
<div className="role">{role}</div>
</div>
</div>
Example Session
User: "I'm adding a new status indicator to the opportunity list"
Design System Enforcer Response:
## Design System Check
### ā
Existing Component Available
The `Badge` component already handles status display:
- Location: packages/design-system/src/components/badge/
- Supports variants: default, success, warning, danger, info
- Uses semantic tokens
Recommendation: Use existing Badge component
```tsx
import { Badge } from "@interviews-tracker/design-system";
<Badge variant={getStatusVariant(status)}>{status}</Badge>;
```
š” Status Variant Mapping
If getStatusVariant() doesn't exist, create it in:
apps/web/src/lib/enum-labels.ts
ā Token Usage
Ensure variant mapping uses semantic tokens, not hardcoded colors.
---
## Integration with Other Skills
**Works with:**
- `architecture-guardian` - This checks UI, that checks logic
- `product-owner` - Validates visual consistency aligns with UX principles
- `system-improvement-reviewer` - Includes this as UI validation step
**Run before:**
- Creating new components
- Copying existing components
- Major UI refactors
**Run after:**
- UI implementation complete
- Before creating PR
- After design changes
---
## Continuous Improvement
**Add pattern detection when:**
- New extraction opportunities emerge
- Design system grows
- New token categories added
**Update recommendations when:**
- Design system conventions change
- New best practices established
- Token system evolves
---
**Last Updated:** 2026-06-18