From harness-claude
Builds reusable styled React components using Tailwind, CVA variants, cn merging, forwardRef, polymorphic asChild props, and shadcn/ui patterns for component libraries.
npx claudepluginhub intense-visions/harness-engineering --plugin harness-claudeThis skill uses the workspace's default tool permissions.
> Build reusable styled components with Tailwind, CVA variants, and polymorphic prop patterns
Builds reusable Tailwind CSS component patterns using template abstraction, @apply directive, CVA, and plugins for React/TypeScript apps.
Generates responsive, performant Tailwind CSS styles for React components. Use when styling components, building design systems, or implementing responsive layouts.
Provides Tailwind CSS advanced UI components using CVA for variants, sizes, compounds like buttons and cards. Useful for React apps needing variant management.
Share bugs, ideas, or general feedback.
Build reusable styled components with Tailwind, CVA variants, and polymorphic prop patterns
as prop)components/ui/ directory following shadcn/ui patternsclassName so consumers can override styles.React.forwardRef for components that wrap native elements (needed for Radix, tooltips, focus management).asChild pattern (via Radix's Slot) for polymorphic rendering.// components/ui/button.tsx — complete production pattern
import * as React from 'react';
import { Slot } from '@radix-ui/react-slot';
import { cva, type VariantProps } from 'class-variance-authority';
import { cn } from '@/lib/utils';
const buttonVariants = cva(
'inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50',
{
variants: {
variant: {
default: 'bg-primary text-primary-foreground hover:bg-primary/90',
destructive: 'bg-destructive text-white hover:bg-destructive/90',
outline: 'border border-border bg-background hover:bg-muted/50',
secondary: 'bg-muted/30 text-foreground hover:bg-muted/50',
ghost: 'hover:bg-muted/50',
link: 'text-primary underline-offset-4 hover:underline',
},
size: {
default: 'h-10 px-4 py-2',
sm: 'h-9 rounded-md px-3',
lg: 'h-11 rounded-md px-8',
icon: 'h-10 w-10',
},
},
defaultVariants: { variant: 'default', size: 'default' },
}
);
export interface ButtonProps
extends React.ButtonHTMLAttributes<HTMLButtonElement>, VariantProps<typeof buttonVariants> {
asChild?: boolean;
}
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
({ className, variant, size, asChild = false, ...props }, ref) => {
const Comp = asChild ? Slot : 'button';
return (
<Comp className={cn(buttonVariants({ variant, size, className }))} ref={ref} {...props} />
);
}
);
Button.displayName = 'Button';
export { Button, buttonVariants };
// Usage
<Button>Default</Button>
<Button variant="destructive" size="lg">Delete</Button>
<Button variant="ghost" className="w-full">Full Width Ghost</Button>
{/* asChild — renders as a link, not a button */}
<Button asChild variant="link">
<a href="/docs">Documentation</a>
</Button>
// components/ui/input.tsx — simple styled wrapper
import * as React from 'react';
import { cn } from '@/lib/utils';
export interface InputProps extends React.InputHTMLAttributes<HTMLInputElement> {}
const Input = React.forwardRef<HTMLInputElement, InputProps>(
({ className, type, ...props }, ref) => {
return (
<input
type={type}
className={cn(
'flex h-10 w-full rounded-md border border-border bg-background px-3 py-2 text-sm ring-offset-background',
'file:border-0 file:bg-transparent file:text-sm file:font-medium',
'placeholder:text-muted',
'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2',
'disabled:cursor-not-allowed disabled:opacity-50',
className
)}
ref={ref}
{...props}
/>
);
}
);
Input.displayName = 'Input';
export { Input };
The asChild pattern: Instead of an as prop (which breaks TypeScript), asChild uses Radix's Slot component to merge props onto the child element. The Button renders as whatever element is its child:
<Button asChild>
<Link href="/dashboard">Go to Dashboard</Link>
</Button>
// Renders as <a> with all Button styles + Link behavior
Component file structure:
components/
ui/
button.tsx # CVA + forwardRef + cn
input.tsx # forwardRef + cn
dialog.tsx # Radix Dialog + Tailwind
card.tsx # Compound component (Card, CardHeader, CardContent, CardFooter)
Compound components:
function Card({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) {
return <div className={cn('rounded-lg border bg-card shadow-sm', className)} {...props} />;
}
function CardHeader({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) {
return <div className={cn('flex flex-col space-y-1.5 p-6', className)} {...props} />;
}
function CardContent({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) {
return <div className={cn('p-6 pt-0', className)} {...props} />;
}
export { Card, CardHeader, CardContent };
Rules for component APIs:
classNameforwardRef for DOM elementscn() for single-style components