merchify-ui
Version:
React components for merchandise visualization and customization
664 lines (507 loc) • 19.5 kB
Markdown
# merchify-ui
**React components for merchandise visualization and customization**
Drop-in React components for displaying products, generating mockups, and building print-on-demand experiences. Works with the `merchify` SDK.
---
## 🤖 FOR AI ASSISTANTS - READ THIS FIRST!
**If you are an AI assistant (Claude, ChatGPT, Copilot, etc.) helping implement merchify-ui:**
### ALWAYS fetch and read the complete implementation guide:
```
/api/ai-guide
```
(Fetch from the same domain as the docs site you're viewing)
**This guide contains:**
- ✅ Complete installation (including critical Tailwind CSS setup)
- ✅ Common patterns and working code examples
- ✅ Component API reference
- ✅ Common mistakes and how to avoid them
**DO NOT implement merchify-ui without reading AI_GUIDE.md first!** Many issues (especially Tailwind CSS configuration) are documented there.
---
## 🤖 Quick Reference for LLMs
| Task | Pattern | Components (in order!) |
|------|---------|------------------------|
| **Display product with artwork** | Context-based (REQUIRED) | `<ArtSelector />` → `<ProductImage />` (ArtSelector MUST come first!) |
| **Artwork customization** | Context-based | `<ArtSelector />` → `<ProductImage />` + `<ArtworkCustomizer />` |
| **Variant selection** | Wrap in Product | `<Product><ProductOptions /></Product>` |
| **Add to cart** | Wrap in Product | `<Product><AddToCart /></Product>` |
**🚨 Critical Rules:**
1. **ProductImage REQUIRES ArtSelector**: `<ProductImage />` by itself shows placeholder - always use `<ArtSelector />` first!
2. **Context-only architecture**: `<ProductImage />` does NOT accept artwork as a prop - use `<ArtSelector />` to set artwork in context
3. `<ArtworkCustomizer />` reads from context (set by `<ArtSelector>`), NOT from props
4. `<ArtAlignment>` and `<TileCount>` are NOT exported - use `<ArtworkCustomizer>` instead
5. Most components require `<Product>` wrapper - check JSDoc for context requirements
6. Seamless patterns REQUIRE `tileCount` (TypeScript enforces this)
**❌ Most common mistake:**
```tsx
// WRONG - ProductImage without ArtSelector shows placeholder!
<Product productId="BEEB77">
<ProductImage /> {/* ❌ No artwork = shows "Select artwork to preview" */}
</Product>
// CORRECT - ArtSelector provides artwork
<Product productId="BEEB77">
<ArtSelector artworks={['https://example.com/art.png']} /> {/* ✅ Sets artwork */}
<ProductImage /> {/* ✅ Reads artwork from context */}
</Product>
```
## Installation
### ⚡ One-Command Setup (Recommended)
**Run this single command to install and configure everything:**
```bash
npx merchify-cli init
```
**That's it!** The CLI will:
- ✅ Install `merchify-ui` and `zod` packages automatically
- ✅ Auto-detect your project structure
- ✅ Configure Tailwind CSS v4 `` paths correctly
- ✅ Verify your setup works
**Then restart your dev server:**
```bash
npm run dev # or pnpm dev, yarn dev, bun dev
```
---
### Manual Installation (Advanced)
If you prefer manual setup:
```bash
npm install merchify-ui zod
# or
yarn add merchify-ui zod
# or
pnpm add merchify-ui zod
```
**Peer Dependencies:**
- React 18+ or 19+
- React DOM 18+ or 19+
- Zod 3+ or 4+
**Important:** You'll need to manually configure Tailwind CSS `` paths (see "Styling" section below).
## Quick Start
**⚠️ IMPORTANT:** ProductImage REQUIRES artwork via `ArtSelector` - they work as a pair!
```tsx
import { Shop, Product, ProductImage, AddToCart, ArtSelector } from 'merchify-ui';
function MyStore() {
return (
<Shop mode="mock">
<Product productId="BEEB77">
{/* Step 1: ArtSelector provides artwork (REQUIRED!) */}
<ArtSelector artworks={['https://example.com/art.png']} />
{/* Step 2: ProductImage reads artwork from context */}
<ProductImage />
{/* Step 3: Add to cart */}
<AddToCart onAddToCart={(item) => console.log(item)} />
</Product>
</Shop>
);
}
```
**What this does:**
1. `<ArtSelector>` - Sets artwork in Design context (required for ProductImage to work)
2. `<ProductImage>` - Reads artwork from context and displays product mockup
3. `<AddToCart>` - Handles cart actions
**❌ Common mistake:**
```tsx
// This WON'T work - ProductImage needs artwork from ArtSelector!
<Product productId="BEEB77">
<ProductImage /> {/* Shows "Select artwork to preview" placeholder */}
</Product>
```
## Core Components
### `<Shop>` - Root Provider
Wraps your app and provides configuration for all child components.
```tsx
<Shop
mode="mock" // 'mock' | 'live' | 'meilisearch'
endpoint="https://..." // Optional: API endpoint
mockupUrl="https://..." // Optional: Mockup service URL
accountId="acc_123" // Optional: Your account ID
>
{/* Your app */}
</Shop>
```
**Environment Variables (Next.js):**
```bash
NEXT_PUBLIC_MERCHIFY_ENDPOINT=https://api.merchify.com
NEXT_PUBLIC_MERCHIFY_MOCKUP_URL=https://mockup.merchify.com
NEXT_PUBLIC_MERCHIFY_ACCOUNT_ID=your-account-id
```
### `<Product>` - Product Context
Provides product data to child components. Fetches product automatically.
```tsx
<Product productId="BEEB77">
<ProductImage />
<ProductOptions />
<ProductPrice />
</Product>
```
### `<ProductImage>` - Mockup Visualization
**🚨 REQUIRES ARTSELECTOR:** ProductImage will NOT work alone - you MUST use `<ArtSelector>` first!
**What happens without ArtSelector:**
- ❌ Shows "Select artwork to preview" placeholder
- ❌ No product mockup is generated
- ❌ Component is essentially non-functional
**How to use it correctly:**
```tsx
// ✅ CORRECT - ArtSelector first, then ProductImage
<Product productId="BEEB77">
{/* Step 1: ArtSelector provides artwork (REQUIRED!) */}
<ArtSelector artworks={[
{ type: 'regular', src: 'https://example.com/art.jpg' },
{ type: 'pattern', src: 'https://example.com/pattern.jpg', tileCount: 2 }
]} />
{/* Step 2: ProductImage reads artwork from context */}
<ProductImage className="w-full" />
</Product>
// ❌ WRONG - ProductImage alone shows placeholder!
<Product productId="BEEB77">
<ProductImage /> {/* ❌ No artwork = placeholder */}
</Product>
// ❌ WRONG - Artwork prop doesn't exist!
<ProductImage artwork={artwork} /> {/* ❌ Not supported */}
```
**Why this design?**
ProductImage uses context-based architecture - `ArtSelector` sets artwork in Design context, and `ProductImage` reads from that context. This allows multiple components to share the same artwork state.
### `<ProductOptions>` - Variant Selector
Shows color and size options for the product.
```tsx
<ProductOptions
variant="inline" // 'inline' | 'dropdown'
showPrice={true} // Show price changes
/>
```
### `<ArtworkCustomizer>` - Smart Artwork Controls
Automatically renders the correct customization UI based on artwork type:
- Regular artwork → Positioning controls (drag to align)
- Seamless patterns → Tile density slider
⚠️ **Reads artwork from context** (set by `<ArtSelector>`), not from props!
```tsx
// Use with ArtSelector to manage artwork via context
<Product productId="BEEB77">
<ArtSelector artworks={[
{ type: 'regular', src: 'https://example.com/photo.jpg' },
{ type: 'pattern', src: 'https://example.com/pattern.jpg', tileCount: 1 }
]} />
<ArtworkCustomizer maxHeight={200} /> {/* Automatically adapts to artwork type */}
</Product>
```
### `<AddToCart>` - Purchase Button
```tsx
<AddToCart
onAddToCart={(item) => console.log('Added:', item)}
className="btn-primary"
/>
```
## All 21 Components
**Primitives:**
- `ColorSwatch` - Color selection
- `ProductPrice` - Formatted pricing
- `ThemeToggle` - Dark/light mode
- `DragHintAnimation` - Drag interaction hint
- `FloatingActionGroup` - FAB button group
**Composed:**
- `ProductCard` - Complete product card (multiple variants)
- `ProductImage` - Mockup display
- `ProductOptions` - Variant selectors
- `ProductList` - Product grid/list
- `ProductGallery` - Image gallery
- `ArtworkCustomizer` - Smart artwork controls (positioning/tile density)
- `ArtSelector` - Artwork picker gallery
- `AddToCart` - Cart button
- `Lightbox` - Full-screen image viewer
- `RealtimeMockup` - Live mockup generation
- `CurrentSelectionDisplay` - Show selected options
- `TileCount` - Tile density control (internal - use ArtworkCustomizer instead)
**Patterns (Context Providers):**
- `Shop` - Root shop provider
- `Product` - Product data provider
**Hooks:**
- `useProduct` - Access product context
- `useShop` - Access shop context
- `useProductGallery` - Gallery state management
- `usePlacementsProcessor` - Placement data processing
## Complete Example
```tsx
import {
Shop,
Product,
ProductImage,
ProductOptions,
ProductPrice,
AddToCart,
ArtSelector,
ArtworkCustomizer
} from 'merchify-ui';
function MerchStore() {
return (
<Shop mode="mock">
<div className="container mx-auto p-4">
<h1>My Merch Store</h1>
<Product productId="BEEB77">
<div className="grid md:grid-cols-2 gap-8">
{/* Product Image */}
<div>
<ProductImage className="w-full rounded-lg" />
</div>
{/* Product Details */}
<div className="space-y-4">
<h2 className="text-2xl font-bold">Custom T-Shirt</h2>
<ProductPrice className="text-xl" />
{/* Artwork Selection */}
<ArtSelector artworks={[
{ type: 'regular', src: 'https://example.com/photo.jpg' },
{ type: 'regular', src: 'https://example.com/logo.png' },
{ type: 'pattern', src: 'https://example.com/pattern.jpg', tileCount: 1 }
]} />
{/* Artwork Customization - automatically shows right controls! */}
<ArtworkCustomizer maxHeight={200} />
<ProductOptions variant="inline" />
<AddToCart
onAddToCart={(item) => {
console.log('Added to cart:', item);
}}
/>
</div>
</div>
</Product>
</div>
</Shop>
);
}
```
## Common Mistakes & How to Fix Them
### ❌ #1 Most Common Mistake: ProductImage shows "Select artwork to preview"
**Problem:** ProductImage REQUIRES ArtSelector - they are a pair, not optional!
This is the **#1 mistake** both LLMs and developers make. ProductImage is not a standalone component.
```tsx
// ❌ WRONG - ProductImage alone doesn't work!
<Product productId="BEEB77">
<ProductImage /> {/* ❌ Shows placeholder - no artwork! */}
</Product>
```
**Solution:** ALWAYS use ArtSelector before ProductImage:
```tsx
// ✅ CORRECT - ArtSelector + ProductImage together
<Product productId="BEEB77">
{/* ArtSelector comes FIRST (required!) */}
<ArtSelector artworks={[
'https://example.com/art1.jpg',
'https://example.com/art2.jpg'
]} />
{/* ProductImage comes SECOND (reads from ArtSelector) */}
<ProductImage />
</Product>
```
**Remember:** Think of ArtSelector and ProductImage as a package deal - you need both!
### ❌ Trying to use ArtAlignment or TileCount directly
**Problem:** `ArtAlignment` and `TileCount` are internal components and NOT exported.
```tsx
// ❌ WRONG - These components are not exported!
import { ArtAlignment, TileCount } from 'merchify-ui'; // ❌ Will fail!
```
**Solution:** Use `ArtworkCustomizer` instead - it handles both types automatically:
```tsx
// ✅ CORRECT - Use ArtworkCustomizer (works with context)
import { Product, ArtSelector, ArtworkCustomizer, ProductImage } from 'merchify-ui';
<Product productId="BEEB77">
<ArtSelector artworks={[
{ type: 'regular', src: 'https://example.com/photo.jpg' },
{ type: 'pattern', src: 'https://example.com/pattern.jpg', tileCount: 1 }
]} />
<ProductImage />
<ArtworkCustomizer maxHeight={200} /> {/* Automatically shows right controls! */}
</Product>
```
**How it works:**
- `ArtworkCustomizer` reads artwork from Design context (set by `ArtSelector`)
- Automatically renders positioning controls for regular artwork
- Automatically renders tile density controls for seamless patterns
- No manual conditionals needed - it switches UI automatically!
## 🎯 Common Patterns for LLMs & Developers
### Pattern 1: Context-Based Artwork Management
**The Right Way:** Use ArtSelector + ProductImage + ArtworkCustomizer
```tsx
import { Shop, Product, ArtSelector, ProductImage, ArtworkCustomizer } from 'merchify-ui';
// ✅ CORRECT - Context-based pattern
<Shop>
<Product productId="BEEB77">
{/* Step 1: ArtSelector sets artwork in context */}
<ArtSelector artworks={[
{ type: 'regular', src: 'https://example.com/photo.jpg' },
{ type: 'pattern', src: 'https://example.com/pattern.jpg', tileCount: 1 }
]} />
{/* Step 2: ProductImage reads artwork from context */}
<ProductImage />
{/* Step 3: ArtworkCustomizer reads artwork from context and shows right controls */}
<ArtworkCustomizer maxHeight={200} />
</Product>
</Shop>
```
**Why this works:**
1. `ArtSelector` → Sets `selectedArtwork` in Design context
2. `ProductImage` → Reads from context, shows mockup
3. `ArtworkCustomizer` → Reads from context, shows positioning (regular) or tile density (pattern) controls
### Pattern 2: Multi-Placement Products (Advanced)
**For products with multiple placements needing different colors/images:**
```tsx
import { Product, ProductImage } from 'merchify-ui';
// ✅ CORRECT - Use placements prop for multi-placement products
<Product productId="shirt-123">
<ProductImage placements={{
Front: '#FF0000', // Red color on front
Back: 'https://logo.jpg', // Image on back
Sleeve: 'rgb(0, 255, 0)' // Green on sleeve
}} />
</Product>
```
**When to use:** Products with multiple placements needing different colors or images per placement.
### Pattern 3: Product Context Components
**Components that require `<Product>` wrapper:**
- `<ProductOptions />` - Reads product options
- `<ProductPrice />` - Can work standalone, but best in context
- `<AddToCart />` - Requires product context
- `<ArtworkCustomizer />` - Requires Design context (from Product)
```tsx
// ✅ CORRECT - All context components wrapped
<Product productId="BEEB77">
<ProductImage />
<ProductOptions />
<ProductPrice />
<AddToCart onAddToCart={(item) => console.log(item)} />
</Product>
// ❌ WRONG - Missing Product wrapper
<ProductOptions /> // Will throw error!
```
### Pattern 4: Type-Safe Artwork with TypeScript
**Use discriminated unions for type safety:**
```tsx
import type { RegularArtwork, SeamlessPattern, Artwork } from 'merchify-ui';
// Type 1: Regular artwork
const photo: RegularArtwork = {
type: 'regular',
src: 'https://example.com/photo.jpg'
};
// Type 2: Seamless pattern (tileCount REQUIRED!)
const pattern: SeamlessPattern = {
type: 'pattern',
src: 'https://example.com/pattern.jpg',
tileCount: 1 // ✅ TypeScript enforces this!
};
// Union type
const artwork: Artwork = Math.random() > 0.5 ? photo : pattern;
```
**TypeScript will catch mistakes:**
```tsx
// ❌ TypeScript error - missing tileCount
const pattern: SeamlessPattern = {
type: 'pattern',
src: 'https://...'
// Error: Property 'tileCount' is missing
};
```
### Pattern 5: Shop-Wide State Sharing
**Share artwork across multiple products:**
```tsx
<Shop>
{/* Artwork selector shared across all products */}
<ArtSelector artworks={artworkCollection} />
<div className="grid grid-cols-2 gap-4">
{/* Both products use the same selected artwork */}
<Product productId="shirt-123">
<ProductImage />
</Product>
<Product productId="mug-456">
<ProductImage />
</Product>
</div>
</Shop>
```
### Pattern 6: Error Handling & Edge Cases
**Handle missing artwork gracefully:**
```tsx
<Product productId="BEEB77">
{/* ProductImage without artwork shows "Select artwork to preview" after 2s */}
<ProductImage />
</Product>
```
**Better: Provide artwork upfront via ArtSelector**
```tsx
<Product productId="BEEB77">
{/* ✅ Always provide artwork for immediate display */}
<ArtSelector artworks={[{ type: 'regular', src: 'https://...' }]} />
<ProductImage />
</Product>
```
## TypeScript Support
Full TypeScript definitions included with **type-safe discriminated unions**:
```typescript
import type {
Artwork, // Union type: RegularArtwork | SeamlessPattern
RegularArtwork, // { type: 'regular', src, aspectRatio? }
SeamlessPattern, // { type: 'pattern', src, aspectRatio?, tileCount }
ImageAlignment,
ProductCardVariant
} from 'merchify-ui';
// TypeScript enforces tileCount for patterns!
const pattern: SeamlessPattern = {
type: 'pattern',
src: 'https://...',
tileCount: 2 // ✅ Required - won't compile without it
};
// Regular artwork doesn't need tileCount
const artwork: RegularArtwork = {
type: 'regular',
src: 'https://...',
aspectRatio: 1
};
```
## Styling
Components use Tailwind CSS with semantic theme tokens:
```tsx
// Components respect your theme
<ProductImage className="bg-background text-foreground" />
// Theme tokens:
// - bg-background, text-foreground
// - bg-card, text-card-foreground
// - bg-primary, text-primary-foreground
// - bg-muted, text-muted-foreground
// - border-border
```
**Tailwind v4 Users:** Make sure to configure `` directives:
```css
/* app/globals.css */
"tailwindcss";
"../app/**/*.{js,ts,jsx,tsx}";
"../node_modules/merchify-ui/src/**/*.{js,ts,jsx,tsx}";
```
## Development Mode
Use mock mode to develop without API calls:
```tsx
<Shop mode="mock">
{/* Returns mock data - no API needed */}
</Shop>
```
## Shadcn-style Usage
Components ship with source code in the `src/` directory. You can:
1. **Use as npm package** (recommended)
2. **Copy source** - Copy components from `node_modules/merchify-ui/src/` to your project and customize freely
## Examples
- [Next.js Full Example](https://github.com/merchify-dev/ui-components/tree/main/examples/next-js)
- [Product Card Demo](https://github.com/merchify-dev/ui-components/tree/main/examples/next-js/app/demos/product-card)
- [Art Alignment Demo](https://github.com/merchify-dev/ui-components/tree/main/examples/next-js/app/demos/art-alignment)
## API Reference
See full component docs at: [https://docs.merchify.com/components](https://docs.merchify.com/components)
## Support
- **Issues:** [GitHub Issues](https://github.com/merchify-dev/ui-components/issues)
- **Discussions:** [GitHub Discussions](https://github.com/merchify-dev/ui-components/discussions)
- **Docs:** [docs.merchify.com](https://docs.merchify.com)
## License
MIT © [driuqzy](mailto:driuqzy.com)
---
**⚠️ Alpha Release**
This is an alpha release (v0.1.0). The API may change as we gather feedback and iterate. We're shipping early to learn from real usage.
**Breaking changes are expected pre-1.0.** Use semver (`npm install merchify-ui`) to get updates.
---
**Built for developers. Optimized for LLMs.** 🤖
> This component library is designed to work seamlessly with AI coding assistants. Each component has:
> - Clear, predictable props
> - Explicit TypeScript types
> - Complete usage examples
> - No magic or hidden behavior
>
> LLMs like Claude, GitHub Copilot, and Cursor can reliably generate correct code using these components.