---
name: strongtie-component
description: "Create React components for @strongtie/design-system using shadcn/ui and Radix UI primitives first, following the two-layer architecture with CVA variants and Tailwind CSS v4. Use when: (1) Creating new components, (2) Modifying existing components, (3) Adding component variants."
---

# Component Creation Skill

Create React components following the Simpson Strong-Tie design system patterns.

## When to Use This Skill

- Creating new React components for @strongtie/design-system
- Modifying existing components
- Adding new variants to components
- Understanding the two-layer architecture

## Workflow Overview

1. **Check Existing Primitives** - Look for shadcn/ui or Radix UI components first
2. **Create Base UI Component** - In `src/components/ui/` with CVA variants
3. **Create Wrapper Component** - In `src/components/` with semantic class
4. **Add Tests** - In `tests/` with accessibility checks
5. **Add Story** - In `stories/` with all variants
6. **Update Exports** - In `package.json` exports

## Step 1: Check Existing Primitives

Before creating a new component:

1. **Check shadcn/ui**: `npx shadcn@latest add <component>`
2. **Check Radix UI**: https://www.radix-ui.com/primitives
3. **Check existing components**: `packages/design-system/src/components/`

Only create custom implementations when no primitive exists.

## Step 2: Two-Layer Architecture

### Layer 1: Base UI Component (`src/components/ui/`)

Core implementation using Radix UI primitives with CVA variants:

```typescript
// src/components/ui/button.tsx
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 gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50",
  {
    variants: {
      variant: {
        default: "bg-primary text-primary-foreground shadow hover:bg-primary/90",
        destructive: "bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90",
        outline: "border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground",
        secondary: "bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80",
        ghost: "hover:bg-accent hover:text-accent-foreground",
        link: "text-primary underline-offset-4 hover:underline",
      },
      size: {
        default: "h-9 px-4 py-2",
        sm: "h-8 rounded-md px-3 text-xs",
        lg: "h-10 rounded-md px-8",
        icon: "h-9 w-9",
      },
    },
    defaultVariants: {
      variant: "default",
      size: "default",
    },
  }
)

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
        data-slot="button"
        data-variant={variant}
        className={cn(buttonVariants({ variant, size, className }))}
        ref={ref}
        {...props}
      />
    )
  }
)
Button.displayName = "Button"

export { Button, buttonVariants }
```

### Layer 2: Wrapper Component (`src/components/`)

Public API with JSDoc documentation and semantic class:

```typescript
// src/components/button.tsx
import * as React from "react"
import { Button as BaseButton, buttonVariants } from "./ui/button"
import { cn } from "../lib/utils"

/**
 * Button is a clickable interactive element that triggers actions or navigates within an application.
 *
 * Buttons are fundamental UI components used to initiate actions, submit forms, or navigate to different
 * views. They come in various visual styles (variants) to indicate importance and different sizes to
 * accommodate various layouts.
 *
 * @example
 * <Button variant="default" size="lg">
 *   Click me
 * </Button>
 */
const Button = ({
  className,
  ...props
}: React.ComponentProps<typeof BaseButton>) => {
  return <BaseButton className={cn("button", className)} {...props} />
}

export { Button, buttonVariants }
```

## Step 3: Required Patterns

### Data Attributes
- `data-slot="component-name"` on root element
- `data-variant={variant}` when variants exist

### Semantic CSS Classes
- Add semantic class via wrapper: `cn("button", className)`
- Class name matches component name (lowercase, hyphenated)

### Type Inference
- Use `React.ComponentProps<typeof BaseComponent>` for props
- No need for `forwardRef` in React 19

### JSDoc Documentation
- Add comprehensive JSDoc to wrapper component
- Include `@example` with usage

## Step 4: Testing Requirements

Create test file in `tests/component-name.test.tsx`:

```typescript
import { render, screen } from "@testing-library/react"
import userEvent from "@testing-library/user-event"
import { axe } from "vitest-axe"
import { describe, expect, it, vi } from "vitest"
import { Button } from "@/components/button"

describe("Button", () => {
  // Rendering tests
  it("renders correctly with default props", () => {
    render(<Button>Click me</Button>)
    expect(screen.getByRole("button", { name: "Click me" })).toBeInTheDocument()
  })

  it("renders with different variants", () => {
    const { rerender } = render(<Button variant="default">Button</Button>)
    expect(screen.getByRole("button")).toHaveAttribute("data-variant", "default")
    
    rerender(<Button variant="destructive">Button</Button>)
    expect(screen.getByRole("button")).toHaveAttribute("data-variant", "destructive")
  })

  // Props tests
  it("accepts and applies custom className", () => {
    render(<Button className="custom-class">Button</Button>)
    expect(screen.getByRole("button")).toHaveClass("custom-class")
  })

  it("forwards ref", () => {
    const ref = vi.fn()
    render(<Button ref={ref}>Button</Button>)
    expect(ref).toHaveBeenCalled()
  })

  // Interaction tests
  it("handles click events", async () => {
    const onClick = vi.fn()
    render(<Button onClick={onClick}>Click me</Button>)
    await userEvent.click(screen.getByRole("button"))
    expect(onClick).toHaveBeenCalledTimes(1)
  })

  // Accessibility tests
  it("should have no accessibility violations", async () => {
    const { container } = render(<Button>Accessible Button</Button>)
    const results = await axe(container)
    expect(results.violations).toHaveLength(0)
  })

  it("should be focusable with keyboard", async () => {
    render(<Button>Focus me</Button>)
    await userEvent.tab()
    expect(screen.getByRole("button")).toHaveFocus()
  })
})
```

## Step 5: Storybook Story

Create story in `stories/components/component-name.stories.tsx`:

```typescript
import type { Meta, StoryObj } from "@storybook/react"
import { Button } from "@/components/button"

const meta: Meta<typeof Button> = {
  title: "Components/Button",
  component: Button,
  tags: ["autodocs"],
  argTypes: {
    variant: {
      control: { type: "select" },
      options: ["default", "secondary", "outline", "ghost", "destructive", "link"],
      description: "Visual style of the button",
    },
    size: {
      control: { type: "select" },
      options: ["default", "sm", "lg", "icon"],
      description: "Size of the button",
    },
    disabled: {
      control: { type: "boolean" },
      description: "Disable button interactions",
    },
  },
}

export default meta
type Story = StoryObj<typeof Button>

export const Default: Story = {
  args: { children: "Button" },
}

export const Variants: Story = {
  render: () => (
    <div className="flex gap-4">
      <Button variant="default">Default</Button>
      <Button variant="secondary">Secondary</Button>
      <Button variant="outline">Outline</Button>
      <Button variant="ghost">Ghost</Button>
      <Button variant="destructive">Destructive</Button>
      <Button variant="link">Link</Button>
    </div>
  ),
}

export const Sizes: Story = {
  render: () => (
    <div className="flex items-center gap-4">
      <Button size="sm">Small</Button>
      <Button size="default">Default</Button>
      <Button size="lg">Large</Button>
    </div>
  ),
}
```

## Step 6: Package Exports

Add to `packages/design-system/package.json` exports:

```json
"./component-name": {
  "import": {
    "types": "./dist/component-name.d.mts",
    "default": "./dist/component-name.mjs"
  },
  "require": {
    "types": "./dist/component-name.d.cts",
    "default": "./dist/component-name.js"
  }
}
```

## Component Checklist

Before completing any component:

- [ ] Check for existing shadcn/ui or Radix primitive
- [ ] Base UI component in `src/components/ui/`
- [ ] CVA variants with `data-variant` attribute
- [ ] `data-slot` attribute on root element
- [ ] Wrapper component in `src/components/`
- [ ] Semantic CSS class via `cn("component-name", className)`
- [ ] Comprehensive JSDoc documentation
- [ ] Export from `src/components/index.tsx`
- [ ] Add to `package.json` exports
- [ ] Test file in `tests/`
- [ ] Accessibility tests with vitest-axe
- [ ] Storybook story in `stories/`

## Key File Locations

| Purpose | Location |
|---------|----------|
| Base UI components | `packages/design-system/src/components/ui/` |
| Wrapper components | `packages/design-system/src/components/` |
| Tests | `packages/design-system/tests/` |
| Stories | `packages/design-system/stories/` |
| Global styles | `packages/design-system/src/styles/globals.css` |

## Reference

- Component patterns: `packages/design-system/src/components/button.tsx`
- CVA variants: `packages/design-system/src/components/ui/button.tsx`
- Test patterns: `packages/design-system/tests/button.test.tsx`
- Story patterns: `packages/design-system/stories/components/button.stories.tsx`
