name: new-component description: Scaffold a new AI element or UI component following project conventions. Use when asked to create a new component, add a component, scaffold a component, or build a new UI element. disable-model-invocation: true
New Component Skill
Scaffold new components in this project. There are two component categories with distinct conventions.
Step 1: Determine Component Category
Ask the user (if not already clear) which category:
- AI Element (
components/ai-elements/) - Domain-specific components for the generative UI chat experience (messages, code blocks, tools, panels, etc.) - UI Primitive (
components/ui/) - Reusable, generic UI primitives built on Radix UI / shadcn patterns (buttons, inputs, dialogs, etc.)
Step 2: Scaffold the Component
AI Element Convention
File: components/ai-elements/{kebab-case-name}.tsx
Pattern to follow:
"use client";
import type { ComponentProps, HTMLAttributes } from "react";
// Import UI primitives from @/components/ui/
import { Button } from "@/components/ui/button";
import { cn } from "@/lib/utils";
// Import icons from lucide-react
import { SomeIcon } from "lucide-react";
// Import React hooks as needed
import { memo, useCallback, useMemo, useState } from "react";
// --- Type exports ---
// Export prop types using `export type`
// Extend HTMLAttributes<HTMLDivElement> or ComponentProps<typeof SomeComponent>
export type MyComponentProps = HTMLAttributes<HTMLDivElement> & {
// Component-specific props
};
// --- Component exports ---
// Export as named `const` arrow functions (not default exports)
// Use `cn()` for className merging with Tailwind
// Spread remaining props with `...props`
export const MyComponent = ({ className, children, ...props }: MyComponentProps) => (
<div className={cn("base-tailwind-classes", className)} {...props}>
{children}
</div>
);
// --- Sub-components ---
// For compound components, export multiple named components from the same file
// e.g., MyComponentHeader, MyComponentContent, MyComponentFooter
// --- Memoization ---
// Use `memo()` for components with expensive rendering
// Provide custom comparison when needed:
// export const MyComponent = memo(MyComponentInner, (prev, next) => prev.x === next.x);
// MyComponent.displayName = "MyComponent";
// --- Context ---
// For compound components that share state, use React Context:
// const MyContext = createContext<MyContextType>(defaultValue);
// const useMyContext = () => { ... };
Key conventions for AI elements:
- Always start with
"use client"; - Use
cn()from@/lib/utilsfor all className merging - Export both the prop type (
export type XProps) and the component (export const X) - Compose with UI primitives from
@/components/ui/(Button, Card, Badge, etc.) - Use
lucide-reactfor icons - Use
motion(frommotion/react) for animations - For interactive state, use Zustand hooks from
@/lib/store.tswhen the state needs to be shared across the app - Use React hooks (
useState,useCallback,useMemo,useRef) for local state
UI Primitive Convention
File: components/ui/{kebab-case-name}.tsx
For UI primitives, prefer using the shadcn CLI:
npx shadcn@latest add <component-name>
This auto-generates components in components/ui/ following the project's components.json config (new-york style, Tailwind CSS variables, lucide icons).
If creating a custom UI primitive (not available in shadcn), follow this pattern:
import * as React from "react"
import { cva, type VariantProps } from "class-variance-authority"
import { Slot } from "radix-ui"
import { cn } from "@/lib/utils"
// Use cva() for variant-based styling
const myComponentVariants = cva(
"base-classes",
{
variants: {
variant: {
default: "default-classes",
secondary: "secondary-classes",
},
size: {
default: "default-size-classes",
sm: "sm-classes",
lg: "lg-classes",
},
},
defaultVariants: {
variant: "default",
size: "default",
},
}
)
// Use function declaration style (not arrow functions) for UI primitives
function MyComponent({
className,
variant = "default",
size = "default",
asChild = false,
...props
}: React.ComponentProps<"div"> &
VariantProps<typeof myComponentVariants> & {
asChild?: boolean
}) {
const Comp = asChild ? Slot.Root : "div"
return (
<Comp
data-slot="my-component"
data-variant={variant}
data-size={size}
className={cn(myComponentVariants({ variant, size, className }))}
{...props}
/>
)
}
export { MyComponent, myComponentVariants }
Key conventions for UI primitives:
- Use
functiondeclarations (not arrow functions) - Use
data-slotattributes for styling hooks - Use
cva()for variants - Support
asChildpattern viaSlot.Rootfromradix-ui - Export both the component and its variants
- No
"use client"unless the component uses hooks/state
Step 3: Verify
After creating the component:
- Read back the created file to confirm correctness
- Run
npx tsc --noEmitto verify no type errors - Run
npx eslint <file-path>to verify lint passes
Available UI Primitives
These exist in components/ui/ and can be imported by AI elements:
accordion, alert, avatar, badge, button, button-group, card, carousel, collapsible, command, dialog, dropdown-menu, hover-card, input, input-group, popover, progress, scroll-area, select, separator, spinner, switch, tabs, textarea, tooltip
Existing AI Elements
These exist in components/ai-elements/ for reference:
agent, artifact, attachments, audio-player, canvas, chain-of-thought, checkpoint, code-block, commit, confirmation, connection, context, controls, conversation, edge, environment-variables, file-tree, generative-message, image, inline-citation, jsx-preview, message, mic-selector, model-selector, node, open-in-chat, package-info, panel, persona, plan, prompt-input, queue, reasoning, sandbox, schema-display, shimmer, snippet, sources, speech-input, stack-trace, suggestion, task, terminal, test-results, tool, toolbar, transcription, voice-selector, web-preview