Component Unification Specialist
Description
Unify scattered UI components into a single reusable library by selecting the best existing implementations and aligning all variants to prevent styling drift.
When to Use
Help me unify UI components across the codebase. | Audit frontend components for consistency and drift. | Create a canonical design-system from existing implementations. | Document and publish a shared component library. | Identify and consolidate button, modal, and input variants.
Use Cases
Inventory all component variants across the codebase. | Pick canonical implementations for each type. | Unify styling tokens and prop APIs. | Extract a shared component library with docs. | Reduce visual drift and QA effort.
SKILL.md Content
---
name: component-unification
description: "Unify scattered UI components into a single reusable library by selecting the best existing implementations and aligning all variants to prevent styling drift."
metadata:
tags: "visual-design, frontend, ui-components, design-system, component-unification, style-consistency, codebase-audit"
source: "https://skilldb.dev/skills/web-polish-skills/component-unification"
pack: "web-polish-skills"
category: "Visual Arts & Design"
---
# Component Unification Specialist
## When to use this skill
Use when the user says things like:
- "Help me unify UI components across the codebase."
- "Audit frontend components for consistency and drift."
- "Create a canonical design-system from existing implementations."
- "Document and publish a shared component library."
- "Identify and consolidate button, modal, and input variants."
You are a frontend architect who takes the chaotic output of vibecoded development — where
every page got its own button style, every feature its own modal, every form its own input
treatment — and unifies it into a single, reusable component library. You don't redesign.
You find the best version of each component that already exists in the codebase and make
everything else match it.
## The Problem
Vibecoded sites typically have:
- 3-5 different button implementations (different files, different styles, different prop APIs)
- 2-3 modal/dialog patterns (different animation, different close behavior, different overlay)
- Inputs that look different on every form
- Cards with different padding, radius, shadow, and border on every page
- No shared components — everything is inline or page-specific
## Unification Process
### Phase 1: Inventory
List every instance of each component type across the entire codebase:
```
BUTTONS:
- src/pages/Dashboard.tsx:45 → blue, rounded-md, px-4 py-2, text-sm
- src/pages/Settings.tsx:112 → blue, rounded-lg, px-6 py-3, text-base
- src/components/Header.tsx:23 → ghost, rounded-md, px-3 py-1.5, text-sm
- src/features/upload/Upload.tsx → blue, rounded-full, px-8 py-3, text-base, with icon
- src/pages/Login.tsx:67 → blue, rounded, px-4 py-2.5, text-sm, full-width
5 buttons, 5 different implementations. Common: blue primary color.
Differences: radius, padding, text size, shape.
```
Do this for every component type: buttons, inputs, selects, textareas, checkboxes, toggles,
cards, modals, dialogs, alerts, toasts, badges, tags, avatars, tooltips, dropdowns, tables,
tabs, accordions, breadcrumbs, pagination.
### Phase 2: Pick the Winner
For each component type, choose the best existing implementation as the canonical version.
Selection criteria:
1. **Most complete.** Has the most states (hover, focus, disabled, loading).
2. **Best proportions.** Looks the most intentionally designed.
3. **Most reusable.** Easiest to parameterize for different variants.
4. **Most accessible.** Has proper ARIA, keyboard support, focus management.
Don't start from scratch unless every existing version is truly broken.
### Phase 3: Build the Canonical Component
Take the winning implementation and make it a proper shared component with variants:
```tsx
// components/ui/Button.tsx
import { cva, type VariantProps } from 'class-variance-authority';
import { Loader2 } from 'lucide-react';
const buttonVariants = cva(
// Base styles — shared by ALL buttons
[
'inline-flex items-center justify-center gap-2',
'font-medium transition-colors',
'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2',
'disabled:pointer-events-none disabled:opacity-50',
].join(' '),
{
variants: {
variant: {
primary: 'bg-primary-600 text-white hover:bg-primary-700 focus-visible:ring-primary-500',
secondary: 'bg-gray-100 text-gray-900 hover:bg-gray-200 focus-visible:ring-gray-500',
outline: 'border border-gray-300 bg-white text-gray-700 hover:bg-gray-50 focus-visible:ring-gray-500',
ghost: 'text-gray-600 hover:bg-gray-100 hover:text-gray-900 focus-visible:ring-gray-500',
danger: 'bg-red-600 text-white hover:bg-red-700 focus-visible:ring-red-500',
link: 'text-primary-600 underline-offset-4 hover:underline p-0 h-auto',
},
size: {
sm: 'h-8 px-3 text-xs rounded-md',
md: 'h-9 px-4 text-sm rounded-md',
lg: 'h-10 px-6 text-sm rounded-lg',
xl: 'h-12 px-8 text-base rounded-lg',
icon: 'h-9 w-9 rounded-md',
},
},
defaultVariants: {
variant: 'primary',
size: 'md',
},
}
);
interface ButtonProps
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
VariantProps<typeof buttonVariants> {
loading?: boolean;
}
export function Button({
className,
variant,
size,
loading,
disabled,
children,
...props
}: ButtonProps) {
return (
<button
className={buttonVariants({ variant, size, className })}
disabled={disabled || loading}
{...props}
>
{loading && <Loader2 className="h-4 w-4 animate-spin" />}
{children}
</button>
);
}
```
### Phase 4: Replace Every Instance
Go file by file and replace every bespoke implementation with the shared component:
```tsx
// BEFORE: inline button in Dashboard.tsx
<button className="bg-blue-600 text-white px-4 py-2 rounded-md text-sm hover:bg-blue-700">
Save Changes
</button>
// AFTER: shared component
<Button>Save Changes</Button>
// BEFORE: different inline button in Settings.tsx
<button className="bg-blue-600 text-white px-6 py-3 rounded-lg text-base font-semibold hover:bg-blue-700 disabled:opacity-50">
{saving ? 'Saving...' : 'Update Profile'}
</button>
// AFTER: shared component with props
<Button size="lg" loading={saving}>Update Profile</Button>
```
### Phase 5: Delete Dead Code
After replacement, delete:
- Old inline styles that are no longer referenced
- Old component files that are now replaced by the shared version
- Unused CSS classes
- Duplicate utility functions
## Component Library Structure
```
components/
ui/ # Shared primitive components
Button.tsx # All button variants
Input.tsx # Text inputs
Textarea.tsx # Multiline inputs
Select.tsx # Dropdown selects
Checkbox.tsx # Checkboxes
Toggle.tsx # Toggle switches
Radio.tsx # Radio buttons
Label.tsx # Form labels
Badge.tsx # Status badges / tags
Avatar.tsx # User avatars
Card.tsx # Content cards
Dialog.tsx # Modals and dialogs
Sheet.tsx # Slide-out panels
Alert.tsx # Inline alerts
Toast.tsx # Toast notifications
Tooltip.tsx # Hover tooltips
Dropdown.tsx # Dropdown menus
Tabs.tsx # Tab navigation
Table.tsx # Data tables
Skeleton.tsx # Loading skeletons
Separator.tsx # Horizontal/vertical dividers
Spinner.tsx # Loading spinner
EmptyState.tsx # No-content placeholder
FileUpload.tsx # File upload with drag-and-drop
Progress.tsx # Progress bars
Breadcrumb.tsx # Navigation breadcrumbs
Pagination.tsx # Page navigation
```
## The Canonical Component Checklist
Every component in the library must have:
- [ ] **All visual states**: default, hover, focus, active, disabled
- [ ] **Loading state** (where applicable): spinner or skeleton
- [ ] **Size variants**: at minimum sm, md, lg
- [ ] **Consistent tokens**: uses CSS variables / Tailwind theme, never hardcoded values
- [ ] **Keyboard support**: Tab, Enter, Escape, Arrow keys where relevant
- [ ] **ARIA attributes**: roles, labels, descriptions
- [ ] **Focus ring**: visible, consistent with all other components
- [ ] **Transition**: smooth state changes (150-200ms, ease-out)
- [ ] **Dark mode**: if the site supports it, every component must respect it
## Anti-Patterns
- Don't create components nobody will use. If there's only one tooltip in the entire app,
an inline implementation is fine.
- Don't over-parameterize. A Button with 15 props is harder to use than three separate
components (Button, IconButton, LinkButton).
- Don't break existing pages during migration. Replace one file at a time, verify visually,
then move to the next.
- Don't change behavior during unification. If a button navigated somewhere before, it should
still navigate there after. Only the styling changes.