āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā ā š shadcn/directory/crafter-station/elements/development ā āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā
This document provides instructions for Claude (AI assistant) to help developers manage the Elements registry efficiently.
When a developer creates a new component and moves it to a package using bun run move-component, Claude should help by automatically updating the package's registry.json file.
The Elements project uses an automated, convention-based system for component previews. Components are automatically detected and previewed on provider pages (/l/{provider}) based on their registry metadata.
Previewable Components:
type: "registry:ui" ā Always previewable (standalone UI components)type: "registry:block" with registry:component files ā Previewable (feature blocks)Non-Previewable:
type: "registry:lib" ā Library/utility code (middleware, API routes, helpers)type: "registry:hook" ā React hookstype: "registry:page" ā Page files (have app-specific imports)registry.json filesIf your component requires props (like Polar's SponsorTiers), you must create a preview wrapper:
Example: sponsor-tiers-preview.tsx
"use client";
import { useState } from "react";
import { SponsorTiers } from "./sponsor-tiers";
// Demo data for preview
const mockTiers = [
{ name: "Supporter", price: 10, description: "...", perks: [...] },
// ... more tiers
];
export function SponsorTiersPreview() {
const [selectedTier, setSelectedTier] = useState<string | null>(null);
return (
<SponsorTiers
tiers={mockTiers}
onSponsor={(tier) => console.log("Selected:", tier)}
selectedTier={selectedTier}
onTierSelect={setSelectedTier}
isPending={false}
/>
);
}
Then in registry.json:
{
"files": [
{
"path": "registry/default/{provider}/{name}-preview.tsx",
"type": "registry:component",
"target": "app/elements/{provider}/{name}-preview.tsx"
},
{
"path": "registry/default/{provider}/{name}.tsx",
"type": "registry:lib",
"target": "app/elements/{provider}/{name}.tsx"
}
]
}
After adding/updating components:
# Automatically runs during build
npm run build
# Or manually trigger
npm run preview:generate
This generates apps/web/lib/component-preview-map.generated.tsx with all component imports.
ā ļø DO NOT EDIT the generated file manually - it will be overwritten!
Ask the user to confirm:
registry:component - Reusable UI component (for preview wrappers or actual components)registry:ui - Standalone UI element (logos, buttons, theme switchers)registry:block - Complete feature block (auth flow, checkout, etc)registry:page - Full page component (test pages, not for preview)registry:lib - Utility/helper functions (middleware, API routes, base components)Location pattern: packages/{provider}/registry/{provider}/{component-name}.tsx
Analyze the file for:
Template for UI Components (logos, buttons, etc):
{
"name": "component-name",
"type": "registry:ui",
"title": "Display Title",
"description": "Brief description of what it does",
"registryDependencies": [],
"dependencies": [],
"files": [
{
"path": "registry/default/{provider}/{component-name}.tsx",
"type": "registry:component",
"target": "components/ui/{provider}/{component-name}.tsx"
}
],
"categories": ["category1", "category2"],
"docs": "Usage documentation and notes"
}
Template for Components That Need Props:
{
"name": "component-name",
"type": "registry:block",
"title": "Display Title",
"description": "Complete feature description",
"registryDependencies": ["button", "card"],
"dependencies": ["@some/package"],
"files": [
{
"path": "registry/default/{provider}/{component-name}-preview.tsx",
"type": "registry:component",
"target": "app/elements/{provider}/{component-name}-preview.tsx"
},
{
"path": "registry/default/{provider}/{component-name}.tsx",
"type": "registry:lib",
"target": "app/elements/{provider}/{component-name}.tsx"
},
{
"path": "registry/default/{provider}/{component-name}/page.tsx",
"type": "registry:page",
"target": "app/elements/{provider}/{component-name}/page.tsx"
}
],
"envVars": {
"API_KEY": "key_..."
},
"docs": "Setup and usage instructions"
}
Template for Complex Blocks (multi-file):
{
"name": "component-name",
"type": "registry:block",
"title": "Display Title",
"description": "Complete feature description",
"registryDependencies": ["button", "card", "@elements/some-logo"],
"dependencies": ["@clerk/nextjs"],
"files": [
{
"path": "registry/default/{provider}/{component-name}/main.tsx",
"type": "registry:component",
"target": "app/elements/{provider}/{component-name}/main.tsx"
},
{
"path": "registry/default/{provider}/{component-name}/actions.ts",
"type": "registry:lib",
"target": "app/elements/{provider}/{component-name}/actions.ts"
},
{
"path": "registry/default/{provider}/{component-name}/page.tsx",
"type": "registry:page",
"target": "app/elements/{provider}/{component-name}/page.tsx"
}
],
"envVars": {
"API_KEY": "key_..."
},
"docs": "Detailed setup and usage instructions"
}
From import statements:
import X from "package-name" ā Add to dependenciesimport X from "@registry/provider/name" ā Add @elements/name to registryDependenciesimport { Button } from "@/components/ui/button" ā Add "button" to registryDependenciesCommon patterns:
@clerk/* packages ā Clerk integration@polar-sh/* ā Polar integrationuploadthing ā UploadThing integrationnext-themes ā Theme switchingmotion or framer-motion ā AnimationsIMPORTANT: File Type Guidelines
registry:component ā Use for preview wrappers or standalone components
sponsor-tiers-preview.tsx, theme-switcher.tsx, apple-logo.tsx@/app/...)registry:lib ā Use for base components that need props, utilities, middleware
sponsor-tiers.tsx (base component), middleware.ts, checkout-route.tsregistry:page ā Use for test pages and usage examples
page.tsx, success-page.tsxPath format: registry/default/{provider}/{component-name}.tsx
For multi-file components:
// Directory structure
packages/{provider}/registry/{provider}/{component-name}/
āāā {component-name}-preview.tsx # Preview wrapper (registry:component)
āāā {component-name}.tsx # Base component (registry:lib)
āāā actions.ts # Server actions (registry:lib)
āāā page.tsx # Test page (registry:page)
// Registry paths:
"files": [
{
"path": "registry/default/{provider}/{component-name}/{component-name}-preview.tsx",
"type": "registry:component",
"target": "app/elements/{provider}/{component-name}/{component-name}-preview.tsx"
},
{
"path": "registry/default/{provider}/{component-name}/{component-name}.tsx",
"type": "registry:lib",
"target": "app/elements/{provider}/{component-name}/{component-name}.tsx"
},
{
"path": "registry/default/{provider}/{component-name}/actions.ts",
"type": "registry:lib",
"target": "app/elements/{provider}/{component-name}/actions.ts"
},
{
"path": "registry/default/{provider}/{component-name}/page.tsx",
"type": "registry:page",
"target": "app/elements/{provider}/{component-name}/page.tsx"
}
]
If component requires props, create a preview wrapper:
"use client";
import { useState } from "react";
import { BaseComponent } from "./base-component";
// Mock data for preview
const mockData = [
// ... demo data
];
export function BaseComponentPreview() {
const [state, setState] = useState(null);
const handleAction = (data: string) => {
console.log("Preview action:", data);
// Demo implementation
};
return (
<BaseComponent
data={mockData}
onAction={handleAction}
selectedItem={state}
onSelect={setState}
/>
);
}
Location: packages/{provider}/registry.json
Process:
registry.jsonitems arrayAfter updating registry:
bun run scripts/generate-preview-map.ts
This updates the preview map with the new component.
Show the generated entry and ask:
ā
Generated registry entry for "{component-name}":
{
"name": "component-name",
...
}
ā
Created preview wrapper: {component-name}-preview.tsx
ā
Added to packages/{provider}/registry.json
ā
Regenerated preview map
Test the preview at: /docs/{provider}
User: "add anthropic-logo to logos registry"
Claude Response:
ā Reading: packages/logos/registry/logos/anthropic.tsx
ā Type: registry:ui (no props needed)
ā Dependencies: None
ā Files: 1 file
ā Preview: Will auto-preview (standalone component)
Generated entry:
{
"name": "anthropic-logo",
"type": "registry:ui",
"title": "Anthropic Logo",
"description": "Anthropic logo component with theme-adaptive colors",
"registryDependencies": [],
"dependencies": [],
"files": [
{
"path": "registry/default/logos/anthropic.tsx",
"type": "registry:component",
"target": "components/ui/logos/anthropic.tsx"
}
],
"categories": ["logo", "brand"],
"docs": "Anthropic logo component that adapts to your theme. Uses currentColor to match your design system."
}
Add to packages/logos/registry.json? (Y/n)
User: "add checkout-form to polar registry"
Claude Response:
ā Reading: packages/polar/registry/polar/checkout-form/checkout-form.tsx
ā Type: registry:block (requires props: products, onCheckout)
ā Dependencies: ["@polar-sh/nextjs"]
ā Registry deps: ["button", "card", "badge"]
ā Files: 3 files
ā ļø Component needs props - creating preview wrapper
Creating preview wrapper:
ā Created: checkout-form-preview.tsx with mock data
Generated entry:
{
"name": "checkout-form",
"type": "registry:block",
"title": "Polar Checkout Form",
"description": "Complete checkout form with product selection and Polar integration",
"registryDependencies": ["button", "card", "badge", "@elements/polar-logo"],
"dependencies": ["@polar-sh/nextjs"],
"files": [
{
"path": "registry/default/polar/checkout-form/checkout-form-preview.tsx",
"type": "registry:component",
"target": "app/elements/polar/checkout-form/checkout-form-preview.tsx"
},
{
"path": "registry/default/polar/checkout-form/checkout-form.tsx",
"type": "registry:lib",
"target": "app/elements/polar/checkout-form/checkout-form.tsx"
},
{
"path": "registry/default/polar/checkout-form/page.tsx",
"type": "registry:page",
"target": "app/elements/polar/checkout-form/page.tsx"
}
],
"envVars": {
"POLAR_ACCESS_TOKEN": "polar_oat_..."
},
"docs": "Polar checkout form with product selection. Requires Polar account and API token."
}
Add to packages/polar/registry.json? (Y/n)
registry:block for auth flows, registry:ui for UI components["@clerk/nextjs", "@clerk/types"] (sometimes @clerk/backend)button, input, label, card, alertCLERK_SECRET_KEY and NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY@elements/clerk-middleware if neededregistry:block["@polar-sh/nextjs"]button, badge, card, @elements/polar-logoPOLAR_ACCESS_TOKEN, POLAR_SUCCESS_URL, POLAR_BASE_URLregistry:block or registry:ui["uploadthing", "@uploadthing/react"]button, card (for dropzone)UPLOADTHING_TOKENregistry:ui["next-themes"]switch, button, dropdown-menu (depending on variant)registry:ui["logo", "brand"]Before updating registry.json, verify:
name matches component filename (without extension)type is appropriate (ui/block for previewable, lib for utilities)title is human-readable and descriptivedescription clearly explains what it doesdependencies includes all npm packages importedregistryDependencies includes all @elements/* and @/components/ui/* importsfiles[].path follows pattern: registry/default/{provider}/{name}.tsxfiles[].type is correct:
registry:component for preview wrappers and standalone componentsregistry:lib for base components, utilities, middlewareregistry:page for test pagesenvVars includes all required environment variables (if any)docs provides setup instructions and important notesComponent not showing in preview:
registry:ui or registry:block with registry:component fileregistry:component (not registry:page or registry:lib)bun run scripts/generate-preview-map.tsComponent preview shows error:
@/app/...)? ā Move to registry:lib, create preview wrapperHow to check what's previewable:
bun run scripts/generate-preview-map.ts
This shows which components are detected and which are skipped.
The preview system is fully integrated into the build pipeline:
Build Process:
npm run build triggers turbo tasks//#registry:merge ā Merges all registry files//#preview:generate ā Generates component preview map@elements/web#build ā Builds Next.js app with previewsScripts:
npm run preview:generate - Manually regenerate preview mapnpm run registry:merge - Manually merge registriesnpm run build - Full build (includes both)Generated Files:
apps/web/registry.json - Merged registry (all providers)apps/web/lib/component-preview-map.generated.tsx - Preview importsā ļø Never edit generated files manually!
If component file doesn't exist:
ā Component not found at packages/{provider}/registry/{provider}/{name}.tsx
Did you run `bun run move-component` first?
If registry.json is malformed:
ā Invalid JSON in packages/{provider}/registry.json
Please fix the syntax errors first.
If dependencies can't be auto-detected:
ā ļø Could not auto-detect all dependencies.
Please review and add manually if needed:
- Check imports in the component file
- Verify all npm packages are listed
If component needs props but no wrapper:
ā ļø Component requires props but no preview wrapper found.
Create {name}-preview.tsx with demo data, or component won't show in preview.
registry:component for preview-ready filesregistry:lib for components that need wrappersregistry:page for test pages only/l/{provider} to verifyCommands:
bun run move-component # Move component to package
bun run preview:generate # Regenerate preview map
bun run registry:merge # Merge all registries
bun run build # Full build (merges + preview + build)
Registry file locations:
packages/clerk/registry.jsonpackages/polar/registry.jsonpackages/uploadthing/registry.jsonpackages/theme/registry.jsonpackages/logos/registry.jsonComponent file locations:
packages/{provider}/registry/{provider}/{name}.tsxpackages/{provider}/registry/{provider}/{name}/{name}.tsx (multi-file)packages/{provider}/registry/{provider}/{name}/{name}-preview.tsx (preview wrapper)Path patterns:
registry/default/{provider}/{name}.tsxregistry/default/{provider}/{name}/{file}.tsxregistry/default/{provider}/{name}/{name}-preview.tsxregistry/default/logos/{name}.tsxFile types:
registry:component ā Preview-ready (standalone or wrapper)registry:ui ā Standalone UI (always previewable)registry:block ā Feature block (previewable if has component file)registry:lib ā Utilities, base components (not previewed)registry:page ā Test pages (not previewed)Preview conventions:
registry:ui ā Auto-previewregistry:block + registry:component file ā Auto-previewregistry:lib ā Not previewedregistry:page ā Not previewedFor more details, see:
scripts/README.md - Preview system architecture and conventionsCLAUDE.md - Project structure and technologiesscripts/generate-preview-map.ts - Preview map generation logicWhen creating a completely new Element (like Tinte, Polar, Clerk, etc.), you need to:
packages/{provider}/
āāā package.json # Package metadata
āāā tsconfig.json # TypeScript config
āāā registry.json # Registry configuration
āāā registry/ # Registry files directory
ā āāā {provider}/ # Provider-specific components
ā āāā component.tsx # Component files
ā āāā ...
āāā src/ # Source files (optional)
āāā components/ # Source components
Example package.json:
{
"name": "@elements/{provider}",
"version": "0.0.0",
"private": true,
"type": "module",
"scripts": {
"build": "echo 'No build script for {provider} package'"
},
"dependencies": {
"react": "^19.0.0",
"react-dom": "^19.0.0"
}
}
Example tsconfig.json:
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/*": ["./registry/*"],
"@/registry/*": ["./registry/*"]
}
},
"include": ["registry/**/*", "src/**/*"],
"exclude": ["node_modules"]
}
Create packages/{provider}/registry.json:
{
"name": "{provider}",
"type": "registry:ui",
"items": []
}
This will be populated as you add components.
Create components in packages/{provider}/registry/{provider}/:
For components that DON'T need props (standalone):
component-name.tsxregistry:ui type in registryFor components that NEED props:
component-name.tsxcomponent-name-preview.tsxregistry:block type in registryregistry:lib, preview as registry:componentImportant: Avoid File Path Conflicts
registry/{provider}/ are for the registry systemsrc/ are for local development/testingregistry/{provider}/ ONLYFor each component, add entry to registry.json:
{
"items": [
{
"name": "component-name",
"type": "registry:ui",
"title": "Component Display Name",
"description": "AI-powered component that does X with Y in Z color space",
"registryDependencies": ["button", "dialog"],
"dependencies": ["@uiw/react-color-wheel", "culori"],
"files": [
{
"path": "registry/{provider}/component-name.tsx",
"type": "registry:component",
"target": "components/{provider}/component-name.tsx"
}
],
"categories": ["theme", "editor"],
"docs": "Usage instructions and setup notes"
}
]
}
Create docs page at apps/web/content/docs/{provider}.mdx:
Template structure:
---
title: {Provider} - Full Stack Components
description: AI-powered components for {provider} integration with semantic patterns
---
import { TintePreview } from "@/components/elements/tinte-preview";
# {Provider}
<Callout>
AI-powered components for {Provider} with [key feature] in [color
space/technology]
</Callout>
## Overview
Brief description of what this Element provides.
## Components
### Component Name
<TintePreview />
Description of the component and what it does.
**Features:**
- Feature 1
- Feature 2
- Feature 3
## Installation
```bash
npx shadcn@latest add https://elements.crafter.so/r/{provider}/component-name
```
import { ComponentName } from "@/components/{provider}/component-name";
export default function Page() {
return <ComponentName />;
}
Environment variables, setup steps, etc.
Props, methods, events, etc.
#### 6. Create Preview Component (if needed)
Create preview wrapper at `apps/web/components/elements/{provider}-preview.tsx`:
```tsx
"use client";
import ThemeEditor from "@/../../packages/{provider}/src/components/theme-editor";
export function TintePreview() {
return (
<div className="relative w-full min-h-[300px] rounded-lg border bg-gradient-to-br from-purple-500/5 via-pink-500/5 to-orange-500/5 p-6">
<div className="space-y-4">
<h3 className="font-semibold text-foreground">
Live Component Demo
</h3>
<p className="text-sm text-muted-foreground">
Interactive demo showing the component in action
</p>
{/* Demo UI elements */}
<div className="grid grid-cols-2 md:grid-cols-4 gap-3 mt-6">
{/* Example color tokens, buttons, etc */}
</div>
</div>
{/* Actual component */}
<ThemeEditor />
</div>
);
}
Key points for preview components:
"use client" directive@/../../packages/{provider}/...)If this is a major Element, add to main navigation in apps/web/lib/layout.shared.tsx:
const links = [
{ title: "Home", href: "/" },
{ title: "Docs", href: "/docs" },
{ title: "{Provider}", href: "/docs/{provider}" }, // Add here
];
# Build everything
bun run build
# Start dev server
bun run dev
# Visit pages:
# - Main docs: http://localhost:3000/docs/{provider}
# - Component preview: Should show inline in docs
# - Registry: http://localhost:3000/r/{provider}/component-name
registry:ui typeExample: Logo Element
packages/logos/
āāā registry.json
āāā registry/logos/
āāā anthropic.tsx
āāā openai.tsx
āāā ...
registry:block and registry:uiExample: Service Element
packages/clerk/
āāā registry.json
āāā registry/clerk/
āāā sign-in/
ā āāā sign-in.tsx # Base (registry:lib)
ā āāā sign-in-preview.tsx # Preview (registry:component)
ā āāā page.tsx # Test page (registry:page)
āāā middleware.ts # Middleware (registry:lib)
āāā ...
src/ and registry/ directoriesExample: Tool Element
packages/tinte/
āāā package.json
āāā registry.json
āāā src/
ā āāā components/ # Source files for development
ā āāā theme-editor.tsx
ā āāā color-input.tsx
ā āāā logo.tsx
āāā registry/tinte/ # Registry files (copied from src)
āāā theme-editor.tsx
āāā color-input.tsx
āāā logo.tsx
IMPORTANT: When writing component files, prevent corruption by:
Delete before rewrite if file exists and may be corrupted:
rm /path/to/file.tsx
# Then write fresh file
Verify file after write by reading first 20 lines:
head -20 /path/to/file.tsx
Check for common corruption patterns:
} from "package"import { (missing newline)If corruption detected:
Common corruption causes:
Named exports vs default exports:
// Component file (color-input.tsx)
export function ColorInput({ value, onChange }: Props) {
// ...
}
// Importing file (theme-editor.tsx)
import { ColorInput } from "./color-input"; // ā
Correct
// NOT:
import ColorInput from "./color-input"; // ā Wrong - expects default export
Always ensure:
When using external packages:
{
"dependencies": {
"@uiw/react-color-wheel": "^2.1.0",
"@uiw/color-convert": "^2.1.0",
"culori": "^4.0.1"
}
}
Path aliases in tsconfig.json:
{
"compilerOptions": {
"paths": {
"@/*": ["./registry/*"],
"@/components/ui/*": ["../../apps/web/components/ui/*"]
}
}
}
In components, use:
@/components/ui/button for shadcn components@uiw/react-color-wheel for npm packages./color-input for local importspackage.json with proper name and dependenciestsconfig.json with path aliasesregistry.json initialized and populated/r/{provider}/component)1. Created package structure:
packages/tinte/
āāā package.json # Added culori, @uiw/* dependencies
āāā tsconfig.json # Path aliases configured
āāā registry.json # Registry with theme-editor entry
āāā src/components/ # Source components
ā āāā theme-editor.tsx # Main editor component
ā āāā color-input.tsx # Color input with format cycling
ā āāā logo.tsx # Tinte branding logo
āāā registry/tinte/ # Copied from src for registry
āāā theme-editor.tsx
āāā color-input.tsx
āāā logo.tsx
2. Created docs page:
apps/web/content/docs/tinte.mdx
3. Created preview:
// apps/web/components/elements/tinte-preview.tsx
"use client";
import ThemeEditor from "@/../../packages/tinte/src/components/theme-editor";
export function TintePreview() {
return (
<div className="...">
<ThemeEditor />
</div>
);
}
4. Added to docs page:
import { TintePreview } from "@/components/elements/tinte-preview";
# Tinte
<TintePreview />
5. Built and verified:
bun run build # ā
Build successful
# Visited /docs/tinte - preview showing
/l/{provider}The goal is to make adding components to the registry fast, accurate, and effortless for developers. The automated preview system means components automatically show up on provider pages with zero manual configuration! š
ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā