---
name: frontend-artifact-builder
description: Create elaborate multi-component web artifacts using React 18, TypeScript, Tailwind CSS, and shadcn/ui. Use when building complex artifacts requiring state management, routing, multiple components, or the full shadcn/ui component library. Generates production-grade single-file HTML bundles.
---

# Frontend Artifact Builder Skill

## Overview

Build sophisticated web artifacts using modern frontend technologies. This skill handles project setup, component scaffolding, and bundling into shareable single-file HTML artifacts.

**Stack:** React 18 + TypeScript + Vite + Tailwind CSS + shadcn/ui (40+ components) + Parcel (bundling)

**Token Savings:** ~15,000 tokens (vs manual setup and bundling)

## When to Use

Use this skill when:

- Building complex UI artifacts with multiple components
- Need shadcn/ui components (Dialog, Table, Form, etc.)
- Require state management or React hooks
- Want production-quality styling with Tailwind
- Need to share as a single HTML file

**NOT for:** Simple single-file HTML/JSX artifacts (use inline code instead)

## Design Guidelines Integration

This skill automatically applies the [frontend-design skill](../frontend-design/SKILL.md) principles:

### ❌ AVOID (AI Slop)

- Excessive centered layouts
- Purple gradients on white backgrounds
- Uniform rounded corners everywhere
- Inter, Roboto, Arial fonts
- Clichéd color schemes
- Predictable cookie-cutter patterns

### ✅ EMBRACE

- Distinctive typography (Space Grotesk, Outfit, Clash Display)
- Cohesive aesthetic with sharp accent colors
- CSS variables for consistency
- Meaningful animations and micro-interactions
- Atmospheric backgrounds (gradients, patterns, grids)
- Unexpected, creative design choices

## Quick Start

### Step 1: Initialize Project

```bash
# From project root
bash scripts/init-artifact.sh my-dashboard
cd my-dashboard
```

### Step 2: Edit Your Artifact

Edit `src/App.tsx` with your component code:

```tsx
import { Card, CardHeader, CardTitle, CardContent } from "@/components/ui/card";
import { Button } from "@/components/ui/button";

function App() {
  return (
    <div className="min-h-screen bg-[#0a0a0f] text-white p-8">
      <Card className="max-w-md mx-auto bg-[#13131a] border-white/10">
        <CardHeader>
          <CardTitle className="font-display text-2xl">Dashboard</CardTitle>
        </CardHeader>
        <CardContent>
          <Button className="w-full bg-cyan-500 hover:bg-cyan-600">
            Get Started
          </Button>
        </CardContent>
      </Card>
    </div>
  );
}

export default App;
```

### Step 3: Bundle & Share

```bash
bash scripts/bundle-artifact.sh
# Output: bundle.html (single file, shareable)
```

## Available Components

All shadcn/ui components are pre-installed:

### Layout Components

| Component | Import                      | Use Case                             |
| --------- | --------------------------- | ------------------------------------ |
| Card      | `@/components/ui/card`      | Container with header/content/footer |
| Dialog    | `@/components/ui/dialog`    | Modal dialogs                        |
| Sheet     | `@/components/ui/sheet`     | Slide-out panels                     |
| Tabs      | `@/components/ui/tabs`      | Tabbed interfaces                    |
| Accordion | `@/components/ui/accordion` | Collapsible sections                 |
| Separator | `@/components/ui/separator` | Visual dividers                      |

### Form Components

| Component | Import                     | Use Case             |
| --------- | -------------------------- | -------------------- |
| Button    | `@/components/ui/button`   | Click actions        |
| Input     | `@/components/ui/input`    | Text inputs          |
| Select    | `@/components/ui/select`   | Dropdown selection   |
| Checkbox  | `@/components/ui/checkbox` | Boolean toggles      |
| Switch    | `@/components/ui/switch`   | On/off toggles       |
| Slider    | `@/components/ui/slider`   | Range selection      |
| Form      | `@/components/ui/form`     | Form with validation |
| Label     | `@/components/ui/label`    | Form labels          |
| Textarea  | `@/components/ui/textarea` | Multi-line input     |

### Data Display

| Component | Import                     | Use Case          |
| --------- | -------------------------- | ----------------- |
| Table     | `@/components/ui/table`    | Data tables       |
| Badge     | `@/components/ui/badge`    | Status indicators |
| Avatar    | `@/components/ui/avatar`   | User avatars      |
| Progress  | `@/components/ui/progress` | Progress bars     |
| Skeleton  | `@/components/ui/skeleton` | Loading states    |

### Feedback & Overlay

| Component | Import                    | Use Case         |
| --------- | ------------------------- | ---------------- |
| Alert     | `@/components/ui/alert`   | Alert messages   |
| Toast     | `@/components/ui/toast`   | Notifications    |
| Tooltip   | `@/components/ui/tooltip` | Hover info       |
| Popover   | `@/components/ui/popover` | Floating content |

### Navigation

| Component      | Import                            | Use Case         |
| -------------- | --------------------------------- | ---------------- |
| NavigationMenu | `@/components/ui/navigation-menu` | Nav menus        |
| Breadcrumb     | `@/components/ui/breadcrumb`      | Breadcrumbs      |
| DropdownMenu   | `@/components/ui/dropdown-menu`   | Dropdown menus   |
| Command        | `@/components/ui/command`         | Command palettes |

## Complete Example: Analytics Dashboard

```tsx
"use client";
import { useState } from "react";
import {
  Card,
  CardHeader,
  CardTitle,
  CardContent,
  CardDescription,
} from "@/components/ui/card";
import { Tabs, TabsList, TabsTrigger, TabsContent } from "@/components/ui/tabs";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import { Progress } from "@/components/ui/progress";

function App() {
  const [activeTab, setActiveTab] = useState("overview");

  return (
    <div className="min-h-screen bg-[#0a0a0f] relative">
      {/* Atmospheric background */}
      <div className="absolute inset-0 bg-[radial-gradient(ellipse_at_top_right,_var(--tw-gradient-stops))] from-cyan-900/20 via-transparent to-transparent" />
      <div className="absolute inset-0 bg-grid opacity-5" />

      <div className="relative z-10 p-8 max-w-7xl mx-auto">
        {/* Header */}
        <div className="flex justify-between items-center mb-8">
          <div>
            <h1 className="text-3xl font-bold text-white font-display">
              Analytics Dashboard
            </h1>
            <p className="text-zinc-400 mt-1">Real-time performance metrics</p>
          </div>
          <Button className="bg-cyan-500 hover:bg-cyan-600">
            Export Report
          </Button>
        </div>

        {/* Stats Grid */}
        <div className="grid grid-cols-1 md:grid-cols-4 gap-4 mb-8">
          {[
            {
              label: "Total Revenue",
              value: "$47,230",
              change: "+12.5%",
              positive: true,
            },
            {
              label: "Active Users",
              value: "2,847",
              change: "+8.2%",
              positive: true,
            },
            {
              label: "Conversion Rate",
              value: "3.24%",
              change: "-0.4%",
              positive: false,
            },
            {
              label: "Avg. Session",
              value: "4m 32s",
              change: "+1.2%",
              positive: true,
            },
          ].map((stat, i) => (
            <Card
              key={i}
              className="bg-[#13131a] border-white/5 hover:border-cyan-500/30 transition-colors"
            >
              <CardHeader className="pb-2">
                <CardDescription className="text-zinc-500">
                  {stat.label}
                </CardDescription>
              </CardHeader>
              <CardContent>
                <div className="text-2xl font-bold text-white font-mono">
                  {stat.value}
                </div>
                <Badge
                  variant={stat.positive ? "default" : "destructive"}
                  className={
                    stat.positive
                      ? "bg-emerald-500/20 text-emerald-400"
                      : "bg-red-500/20 text-red-400"
                  }
                >
                  {stat.change}
                </Badge>
              </CardContent>
            </Card>
          ))}
        </div>

        {/* Tabs Section */}
        <Tabs value={activeTab} onValueChange={setActiveTab}>
          <TabsList className="bg-[#13131a] border border-white/5">
            <TabsTrigger value="overview">Overview</TabsTrigger>
            <TabsTrigger value="analytics">Analytics</TabsTrigger>
            <TabsTrigger value="reports">Reports</TabsTrigger>
          </TabsList>

          <TabsContent value="overview" className="mt-6">
            <Card className="bg-[#13131a] border-white/5">
              <CardHeader>
                <CardTitle>Performance Overview</CardTitle>
              </CardHeader>
              <CardContent>
                <div className="space-y-4">
                  {[
                    { label: "Server Uptime", value: 99.9 },
                    { label: "API Response", value: 85 },
                    { label: "Cache Hit Rate", value: 92 },
                  ].map((metric, i) => (
                    <div key={i}>
                      <div className="flex justify-between text-sm mb-1">
                        <span className="text-zinc-400">{metric.label}</span>
                        <span className="text-white font-mono">
                          {metric.value}%
                        </span>
                      </div>
                      <Progress value={metric.value} className="h-2" />
                    </div>
                  ))}
                </div>
              </CardContent>
            </Card>
          </TabsContent>
        </Tabs>
      </div>
    </div>
  );
}

export default App;
```

## Styling Patterns

### CSS Variables Setup

```css
/* In your App.tsx or global styles */
:root {
  --font-display: "Outfit", sans-serif;
  --font-mono: "JetBrains Mono", monospace;

  --bg-primary: #0a0a0f;
  --bg-secondary: #13131a;
  --border: rgba(255, 255, 255, 0.05);

  --accent-cyan: #22d3ee;
  --accent-glow: rgba(34, 211, 238, 0.15);
}
```

### Grid Background Pattern

```css
.bg-grid {
  background-image:
    linear-gradient(to right, rgba(255, 255, 255, 0.05) 1px, transparent 1px),
    linear-gradient(to bottom, rgba(255, 255, 255, 0.05) 1px, transparent 1px);
  background-size: 40px 40px;
}
```

### Glow Effects

```css
/* Card with glow on hover */
.card-glow {
  position: relative;
}
.card-glow::before {
  content: "";
  position: absolute;
  inset: -1px;
  background: linear-gradient(to right, var(--accent-cyan), transparent);
  border-radius: inherit;
  opacity: 0;
  transition: opacity 0.3s;
  z-index: -1;
}
.card-glow:hover::before {
  opacity: 1;
}
```

## Scripts Reference

### init-artifact.sh

Creates a fully configured React + TypeScript + Tailwind + shadcn/ui project.

```bash
bash scripts/init-artifact.sh <project-name>
```

**Creates:**

- Vite + React + TypeScript config
- Tailwind CSS 3.4.1 with shadcn/ui theming
- Path aliases (@/) configured
- 40+ shadcn/ui components pre-installed
- All Radix UI dependencies
- Parcel configured for bundling

### bundle-artifact.sh

Bundles the entire project into a single HTML file.

```bash
cd <project-name>
bash scripts/bundle-artifact.sh
```

**Output:** `bundle.html` - Self-contained, shareable artifact

## Programmatic API

```typescript
import {
  initArtifact,
  bundleArtifact,
  getDesignGuidelines,
  getComponentReference,
} from "./.claude/mcp/skills/frontend-artifact-builder";

// Initialize a new project
const init = await initArtifact({
  projectName: "my-dashboard",
  description: "Analytics dashboard with charts",
  components: ["Card", "Tabs", "Table", "Button"],
});

// Get component reference
const components = getComponentReference();
console.log(components.Card.import); // '@/components/ui/card'

// Get design guidelines
const guidelines = getDesignGuidelines();
console.log(guidelines.fontSuggestions);

// Bundle when ready
const result = await bundleArtifact("./my-dashboard");
console.log(`Bundle size: ${result.bundleSize} bytes`);
```

## Troubleshooting

### Common Issues

**"Module not found" errors:**

- Ensure you're using `@/components/ui/*` import paths
- Check component name matches shadcn/ui naming

**Styles not applying:**

- Verify Tailwind classes are in `tailwind.config.js` content paths
- Check CSS variables are defined

**Bundle fails:**

- Run `npm install` in project directory first
- Ensure Node.js 18+ is installed

### Debug Mode

```bash
# Verbose bundling
DEBUG=1 bash scripts/bundle-artifact.sh
```

## Mawidi Editorial Design System for Artifacts

**CRITICAL**: All Mawidi artifacts MUST follow the Editorial Design System for visual consistency.

### Brand Color Variables (Updated)

```css
:root {
  /* Primary Brand Green */
  --mawidi-green: #10b981;
  --mawidi-green-hover: #059669;
  --mawidi-green-light: rgba(16, 185, 129, 0.1);

  /* Ink - For text */
  --mawidi-ink: #1e293b;
  --mawidi-ink-light: #334155;

  /* Sand - For backgrounds */
  --mawidi-sand: #fafafa;
  --mawidi-sand-dark: #f5f5f5;

  /* Muted text */
  --mawidi-muted: #64748b;

  /* Map to shadcn components */
  --primary: var(--mawidi-green);
  --primary-foreground: white;
  --background: var(--mawidi-sand);
  --foreground: var(--mawidi-ink);
}
```

### Section Header Pattern (REQUIRED)

Every major section must have a numbered header:

```tsx
{
  /* Section Header - use throughout all artifacts */
}
<div className="flex items-center gap-4 mb-8">
  <div className="flex items-center gap-3">
    <span className="text-[#10B981] font-mono text-sm tracking-wider">01</span>
    <div className="w-12 h-px bg-[#10B981]" />
  </div>
  <h2 className="text-4xl font-bold text-[#1E293B]">Section Title</h2>
</div>;
```

### Editorial Grid Layout

```tsx
{
  /* Grid with thin separators */
}
<div className="grid grid-cols-1 md:grid-cols-3 gap-px bg-slate-200">
  {items.map((item, idx) => (
    <div
      key={idx}
      className="group bg-white p-8 hover:bg-[#FAFAFA] transition-colors"
    >
      <div className="flex items-center gap-3 mb-4">
        <span className="text-[#10B981] font-mono text-xs">
          {String(idx + 1).padStart(2, "0")}
        </span>
        <h3 className="font-semibold text-[#1E293B]">{item.title}</h3>
      </div>
      <p className="text-[#64748B] mb-6">{item.description}</p>
      <div className="w-0 group-hover:w-12 h-0.5 bg-[#10B981] transition-all duration-300" />
    </div>
  ))}
</div>;
```

### Button Patterns

```tsx
{
  /* Primary Button with Fill-Up Effect */
}
<button className="group relative px-8 py-4 bg-[#10B981] text-white font-semibold overflow-hidden">
  <span className="relative z-10">Get Started</span>
  <div className="absolute inset-0 bg-[#059669] transform translate-y-full group-hover:translate-y-0 transition-transform duration-300" />
</button>;

{
  /* Secondary Button */
}
<button className="px-8 py-4 border-2 border-[#10B981] text-[#10B981] font-semibold hover:bg-[#10B981]/10 transition-colors">
  Learn More
</button>;
```

### Feature Lists - Square Bullets

```tsx
{
  /* Square bullets, NOT checkmarks */
}
<ul className="space-y-3">
  {features.map((f, idx) => (
    <li key={idx} className="flex items-start gap-3">
      <div className="w-1.5 h-1.5 bg-[#10B981] mt-2 flex-shrink-0" />
      <span className="text-[#64748B]">{f}</span>
    </li>
  ))}
</ul>;
```

### Background Rules

**NEVER use dark backgrounds** for main content:

✅ **Allowed:**

- `bg-white` - Primary areas
- `bg-[#FAFAFA]` - Alternating sections
- `bg-[#F5F5F5]` - Secondary sections
- `bg-[#10B981]/[0.03]` - Subtle green tint

❌ **Forbidden:**

- `bg-[#1E293B]`
- `bg-[#0F172A]`
- Any dark backgrounds

### Complete Mawidi Editorial Dashboard

```tsx
"use client";
import { useState } from "react";
import { Card, CardHeader, CardTitle, CardContent } from "@/components/ui/card";

function MawidiDashboard() {
  return (
    <div className="min-h-screen bg-[#FAFAFA]">
      {/* Header */}
      <header className="bg-white border-b border-slate-200 px-6 py-4">
        <div className="flex items-center justify-between max-w-7xl mx-auto">
          <img
            src="/brand/logo-horizontal.jpeg"
            alt="Mawidi"
            className="h-10"
          />
          <button className="group relative px-6 py-2 bg-[#10B981] text-white text-sm font-medium overflow-hidden">
            <span className="relative z-10">Settings</span>
            <div className="absolute inset-0 bg-[#059669] transform translate-y-full group-hover:translate-y-0 transition-transform duration-300" />
          </button>
        </div>
      </header>

      {/* Dashboard Content */}
      <main className="max-w-7xl mx-auto p-6">
        {/* Section Header */}
        <div className="flex items-center gap-4 mb-8">
          <div className="flex items-center gap-3">
            <span className="text-[#10B981] font-mono text-sm tracking-wider">
              01
            </span>
            <div className="w-12 h-px bg-[#10B981]" />
          </div>
          <h2 className="text-3xl font-bold text-[#1E293B]">
            Today's Overview
          </h2>
        </div>

        {/* Stats Grid - Editorial Style */}
        <div className="grid grid-cols-1 md:grid-cols-3 gap-px bg-slate-200 mb-12">
          {[
            {
              num: "01",
              label: "Total Bookings",
              value: "24",
              sub: "+3 from yesterday",
            },
            {
              num: "02",
              label: "WhatsApp Messages",
              value: "156",
              sub: "12 unread",
            },
            {
              num: "03",
              label: "Revenue Today",
              value: "QAR 4,200",
              sub: "8 appointments",
            },
          ].map((stat) => (
            <div
              key={stat.num}
              className="group bg-white p-8 hover:bg-[#FAFAFA] transition-colors"
            >
              <div className="flex items-center gap-3 mb-4">
                <span className="text-[#10B981] font-mono text-xs">
                  {stat.num}
                </span>
                <span className="text-[#64748B] text-sm">{stat.label}</span>
              </div>
              <div className="text-4xl font-bold text-[#1E293B] mb-2">
                {stat.value}
              </div>
              <p className="text-sm text-[#64748B]">{stat.sub}</p>
              <div className="w-0 group-hover:w-12 h-0.5 bg-[#10B981] mt-4 transition-all duration-300" />
            </div>
          ))}
        </div>

        {/* Features Section */}
        <div className="flex items-center gap-4 mb-8">
          <div className="flex items-center gap-3">
            <span className="text-[#10B981] font-mono text-sm tracking-wider">
              02
            </span>
            <div className="w-12 h-px bg-[#10B981]" />
          </div>
          <h2 className="text-3xl font-bold text-[#1E293B]">Quick Actions</h2>
        </div>

        <ul className="space-y-4 max-w-md">
          {[
            "View pending appointments",
            "Send reminder messages",
            "Check analytics",
          ].map((item, idx) => (
            <li key={idx} className="flex items-start gap-3">
              <div className="w-1.5 h-1.5 bg-[#10B981] mt-2 flex-shrink-0" />
              <span className="text-[#1E293B]">{item}</span>
            </li>
          ))}
        </ul>
      </main>
    </div>
  );
}

export default MawidiDashboard;
```

### RTL Support for Arabic

```tsx
function App() {
  const [isArabic, setIsArabic] = useState(false);

  return (
    <div
      dir={isArabic ? "rtl" : "ltr"}
      className={isArabic ? "font-arabic" : "font-sans"}
    >
      {/* Section numbers stay LTR even in Arabic */}
      <div className="flex items-center gap-3">
        <span className="text-[#10B981] font-mono text-sm" dir="ltr">
          01
        </span>
        <div className="w-12 h-px bg-[#10B981]" />
      </div>

      <h1>{isArabic ? "مرحباً بك في مواعيدي" : "Welcome to Mawidi"}</h1>
    </div>
  );
}
```

### Font Setup

```html
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link
  href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700;800&family=IBM+Plex+Sans+Arabic:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap"
  rel="stylesheet"
/>
```

```js
// tailwind.config.js
fontFamily: {
  sans: ['Plus Jakarta Sans', 'system-ui', 'sans-serif'],
  arabic: ['IBM Plex Sans Arabic', 'Noto Sans Arabic', 'sans-serif'],
  mono: ['JetBrains Mono', 'monospace'],
}
```

### Animation Guidelines

- **CSS transitions only** - Do NOT use Framer Motion
- **Duration**: 200-300ms for hover effects
- **Signature animation**: Expanding green underline (`w-0 group-hover:w-12`)

## See Also

- [Frontend Design Skill](../frontend-design/SKILL.md) - Design principles & aesthetics
- [Brand Guidelines Skill](../brand-guidelines/SKILL.md) - Complete Mawidi branding
- [Email Composer Skill](../email-composer/SKILL.md) - Mawidi email templates
- [shadcn/ui Docs](https://ui.shadcn.com/docs/components) - Component reference
