name: add-shadcn-primitive description: Add a new shadcn-style UI primitive to components/ui/ — CVA variants, Radix Slot for asChild, cn() merging, GoRun design tokens. Use when the user asks for a new generic input/button/card-like primitive.
Add shadcn Primitive
Reference
The canonical example is components/ui/button.tsx. Read it first.
Procedure
Confirm the primitive doesn't already exist in
components/ui/. Don't duplicate.Decide the API surface:
- Variants (visual style) and sizes — defined via
cva() - Polymorphism: support
asChildvia@radix-ui/react-slotif the primitive should be able to render as a different tag - Wrapped Radix component (for primitives like Select, Dialog, DropdownMenu, Tabs)
- Variants (visual style) and sizes — defined via
File shape:
import * as React from "react"; import { Slot } from "@radix-ui/react-slot"; // if polymorphic import { cva, type VariantProps } from "class-variance-authority"; import { cn } from "@/lib/utils"; const fooVariants = cva("base utility classes", { variants: { variant: { brand: "GoRun design — pill, --gr-* tokens", // legacy shadcn variants only if needed for back-compat }, size: { sm: "h-9 px-3.5 text-sm", md: "h-12 px-[18px] text-[15px]", lg: "h-14 px-[22px] text-base", }, }, defaultVariants: { variant: "brand", size: "md" }, }); function Foo({ className, variant, size, asChild = false, ...props }: React.ComponentProps<"…"> & VariantProps<typeof fooVariants> & { asChild?: boolean }) { const Comp = asChild ? Slot : "…"; return ( <Comp className={cn(fooVariants({ variant, size, className }))} {...props} /> ); } export { Foo, fooVariants };Design tokens: prefer
var(--gr-brand),var(--gr-ink),var(--gr-surface-2),var(--gr-line)etc. defined inapp/globals.css. Don't introduce new hardcoded colors.GoRun sizes (
sm | md | lg) are h-9 / h-12 / h-14. Keep new primitives consistent with those.Export
<Foo>Variantsso consumers can pullVariantPropsif they wrap it.
Anti-patterns
- Hardcoded hex colors instead of
--gr-*tokens - Local class-merging instead of
cn() - Skipping
asChildwhen polymorphism makes sense (text-as-link, etc.) - Inventing a new size scale instead of
sm/md/lg - Adding a primitive to
components/<domain>/instead ofcomponents/ui/