From harness-claude
Builds type-safe component variants using class-variance-authority (CVA) with Tailwind CSS. Replaces complex conditional className logic with declarative variant APIs and TypeScript autocompletion.
How this skill is triggered — by the user, by Claude, or both
Slash command
/harness-claude:css-component-variantsThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
> Build type-safe component variants with class-variance-authority for consistent, composable styling APIs
Build type-safe component variants with class-variance-authority for consistent, composable styling APIs
class-variance-authority: npm install class-variance-authority.cva() — base classes first, then variant definitions.defaultVariants to avoid requiring every prop.compoundVariants for styles that apply only when specific variant combinations are active.VariantProps<typeof variantFn> for TypeScript.tailwind-merge (via cn() helper) to allow className overrides.// components/button.tsx
import { cva, type VariantProps } from 'class-variance-authority';
import { cn } from '@/lib/utils';
const buttonVariants = cva(
// Base styles — always applied
'inline-flex items-center justify-center rounded-md font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50',
{
variants: {
variant: {
primary: 'bg-blue-600 text-white hover:bg-blue-700 focus-visible:ring-blue-500',
secondary: 'bg-gray-100 text-gray-900 hover:bg-gray-200 focus-visible:ring-gray-500',
destructive: 'bg-red-600 text-white hover:bg-red-700 focus-visible:ring-red-500',
ghost: 'hover:bg-gray-100 hover:text-gray-900',
link: 'text-blue-600 underline-offset-4 hover:underline',
},
size: {
sm: 'h-8 px-3 text-xs',
md: 'h-10 px-4 text-sm',
lg: 'h-12 px-6 text-base',
icon: 'h-10 w-10',
},
},
compoundVariants: [
// Ghost + sm gets tighter padding
{ variant: 'ghost', size: 'sm', className: 'px-2' },
],
defaultVariants: {
variant: 'primary',
size: 'md',
},
}
);
interface ButtonProps
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
VariantProps<typeof buttonVariants> {
asChild?: boolean;
}
function Button({ className, variant, size, ...props }: ButtonProps) {
return (
<button
className={cn(buttonVariants({ variant, size }), className)}
{...props}
/>
);
}
export { Button, buttonVariants };
// Usage
<Button>Default Primary Medium</Button>
<Button variant="destructive" size="lg">Delete Account</Button>
<Button variant="ghost" size="sm">Cancel</Button>
<Button className="w-full">Full Width Override</Button>
The cn() helper combines clsx and tailwind-merge for safe class merging:
// lib/utils.ts
import { clsx, type ClassValue } from 'clsx';
import { twMerge } from 'tailwind-merge';
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}
Why cva over conditional strings: Without cva:
// Messy, error-prone, no type safety
className={`btn ${variant === 'primary' ? 'bg-blue-600 text-white' : ''} ${variant === 'secondary' ? 'bg-gray-100' : ''} ${size === 'lg' ? 'h-12 px-6' : 'h-10 px-4'}`}
compoundVariants: Apply classes only when a specific combination of variants is active. Useful for design exceptions:
compoundVariants: [
{ variant: 'primary', size: 'lg', className: 'text-lg font-bold' },
{ variant: 'destructive', size: 'sm', className: 'font-bold' },
],
Extending variants: If a component wraps another, extend its variants:
const alertVariants = cva('rounded-lg p-4 border', {
variants: {
severity: {
info: 'bg-blue-50 border-blue-200 text-blue-800',
warning: 'bg-yellow-50 border-yellow-200 text-yellow-800',
error: 'bg-red-50 border-red-200 text-red-800',
},
},
defaultVariants: { severity: 'info' },
});
shadcn/ui pattern: shadcn/ui uses this exact pattern (cva + cn + VariantProps) for all components. If you are building a component library, this is the industry standard approach.
npx claudepluginhub intense-visions/harness-engineering --plugin harness-claudeBuild reusable styled components with Tailwind, CVA variants, and polymorphic prop patterns. Provides a production pattern for Button and similar components with className merging and asChild support.
Builds reusable Tailwind CSS component patterns using template abstraction, @apply directive, CVA, and plugins for React/TypeScript apps.
Provides Tailwind CSS advanced UI components using CVA for variants, sizes, compounds like buttons and cards. Useful for React apps needing variant management.