搜索

PythonReactSEOPDF
GitHub Skill

shadcn

Implement shadcn/ui components for PhotoVault with accessibility and theming. Use when working with UI components, forms, dialogs, buttons, cards, tables, Tailwind styling, or fixing accessibility issues. Includes PhotoVault theme system and established UI patterns.

nathanielcrowell12-spec☆ 0 仓库 Stars0 仓库 Forks

安装与使用

请根据来源仓库和你的 AI 工具说明安装此技能。先阅读技能文件和依赖要求,再添加到工作流。

查看来源 ↗使用指南

技能说明

⚠️ MANDATORY WORKFLOW - DO NOT SKIP

When this skill activates, you MUST follow the expert workflow before writing any code:

  1. Spawn Domain Expert using the Task tool with this prompt:

    Read the expert prompt at: C:\Users\natha\Stone-Fence-Brain\VENTURES\PhotoVault\claude\experts\shadcn-expert.md
    
    Then research the codebase and write an implementation plan to: docs/claude/plans/ui-[task-name]-plan.md
    
    Task: [describe the user's request]
    
  2. Spawn QA Critic after expert returns, using Task tool:

    Read the QA critic prompt at: C:\Users\natha\Stone-Fence-Brain\VENTURES\PhotoVault\claude\experts\qa-critic-expert.md
    
    Review the plan at: docs/claude/plans/ui-[task-name]-plan.md
    Write critique to: docs/claude/plans/ui-[task-name]-critique.md
    
  3. Present BOTH plan and critique to user - wait for approval before implementing

DO NOT read files and start coding. DO NOT rationalize that "this is simple." Follow the workflow.


shadcn/ui Integration

Core Principles

Accessibility First, Always

Every interactive component MUST be keyboard navigable and screen reader friendly. shadcn/ui is built on Radix primitives which handle most accessibility out of the box.

<Button aria-label="Close dialog" onClick={onClose}>
  <X className="h-4 w-4" />
</Button>
Composition Over Configuration

shadcn/ui components are meant to be composed, not configured with dozens of props.

// ✅ RIGHT: Compose components
<Card>
  <CardHeader>
    <CardTitle>Title</CardTitle>
    <CardDescription>Description</CardDescription>
  </CardHeader>
  <CardContent>Content here</CardContent>
  <CardFooter>
    <Button>Action</Button>
  </CardFooter>
</Card>

// ❌ WRONG: Mega-component with too many props
<Card title="Title" description="..." content="..." footerButton="Action" />
Always Use Semantic Color Tokens

Never hardcode colors. Use semantic tokens for theming support.

// ❌ WRONG: Hardcoded colors break theming
<div className="bg-white text-black" />

// ✅ RIGHT: Semantic tokens
<div className="bg-background text-foreground" />

Anti-Patterns

Not forwarding refs on custom components

// WRONG: Refs don't work
const CustomButton = ({ className, ...props }) => {
  return <Button className={className} {...props} />
}

// RIGHT: Forward refs
const CustomButton = React.forwardRef<
  HTMLButtonElement,
  React.ComponentPropsWithoutRef<typeof Button>
>(({ className, ...props }, ref) => {
  return <Button ref={ref} className={className} {...props} />
})
CustomButton.displayName = "CustomButton"

Not using cn() for class merging

// WRONG: Classes override unpredictably
<Button className={`${baseClasses} ${conditionalClasses}`}>

// RIGHT: Use cn() for proper merging
import { cn } from "@/lib/utils"
<Button className={cn(baseClasses, conditionalClasses)}>

Nesting interactive elements

// WRONG: Button inside clickable card (accessibility violation)
<Card onClick={handleClick}>
  <Button onClick={handleAction}>Action</Button>
</Card>

// RIGHT: Separate interactive areas
<Card>
  <CardContent onClick={handleClick}>...</CardContent>
  <CardFooter>
    <Button onClick={handleAction}>Action</Button>
  </CardFooter>
</Card>

Not connecting labels to inputs

// WRONG: Label not connected
<Label>Email</Label>
<Input type="email" />

// RIGHT: Use FormField pattern
<FormField
  control={form.control}
  name="email"
  render={({ field }) => (
    <FormItem>
      <FormLabel>Email</FormLabel>
      <FormControl>
        <Input {...field} />
      </FormControl>
      <FormMessage />
    </FormItem>
  )}
/>

Button Variants

<Button>Save Changes</Button>                    // Primary
<Button variant="secondary">Cancel</Button>      // Secondary
<Button variant="destructive">Delete</Button>    // Destructive
<Button variant="ghost">Edit</Button>            // Subtle
<Button variant="outline">View Details</Button>  // Outline

// Icon button with accessibility
<Button variant="ghost" size="icon" aria-label="Settings">
  <Settings className="h-4 w-4" />
</Button>

// Loading state
<Button disabled>
  <Loader2 className="mr-2 h-4 w-4 animate-spin" />
  Please wait
</Button>

Dialog Pattern

import {
  Dialog, DialogContent, DialogDescription,
  DialogFooter, DialogHeader, DialogTitle, DialogTrigger,
} from "@/components/ui/dialog"

function ConfirmDialog({ onConfirm }: { onConfirm: () => void }) {
  const [open, setOpen] = useState(false)

  return (
    <Dialog open={open} onOpenChange={setOpen}>
      <DialogTrigger asChild>
        <Button variant="destructive">Delete Item</Button>
      </DialogTrigger>
      <DialogContent>
        <DialogHeader>
          <DialogTitle>Are you sure?</DialogTitle>
          <DialogDescription>This action cannot be undone.</DialogDescription>
        </DialogHeader>
        <DialogFooter>
          <Button variant="outline" onClick={() => setOpen(false)}>Cancel</Button>
          <Button variant="destructive" onClick={() => { onConfirm(); setOpen(false) }}>
            Delete
          </Button>
        </DialogFooter>
      </DialogContent>
    </Dialog>
  )
}

Form Pattern (react-hook-form + zod)

import { zodResolver } from "@hookform/resolvers/zod"
import { useForm } from "react-hook-form"
import { z } from "zod"

const formSchema = z.object({
  email: z.string().email("Please enter a valid email"),
  name: z.string().min(2, "Name must be at least 2 characters"),
})

function ContactForm() {
  const form = useForm<z.infer<typeof formSchema>>({
    resolver: zodResolver(formSchema),
    defaultValues: { email: "", name: "" },
  })

  return (
    <Form {...form}>
      <form onSubmit={form.handleSubmit(onSubmit)} className="space-y-6">
        <FormField
          control={form.control}
          name="name"
          render={({ field }) => (
            <FormItem>
              <FormLabel>Name</FormLabel>
              <FormControl>
                <Input placeholder="John Doe" {...field} />
              </FormControl>
              <FormMessage />
            </FormItem>
          )}
        />
        <Button type="submit" disabled={form.formState.isSubmitting}>
          {form.formState.isSubmitting ? "Submitting..." : "Submit"}
        </Button>
      </form>
    </Form>
  )
}

Toast Notifications (Sonner)

import { toast } from "sonner"

toast.success("Changes saved successfully")
toast.error("Failed to save changes")

// Promise toast
toast.promise(saveData(), {
  loading: "Saving...",
  success: "Data saved!",
  error: "Could not save data",
})

PhotoVault Configuration

Theme System

PhotoVault has 5 color themes in src/lib/themes.ts:

  • Warm Gallery - Cream background, terracotta primary (default)
  • Cool Professional - Cool slate, indigo primary
  • Gallery Dark - Warm charcoal, amber accents
  • Soft Sage - Sage green, emerald + pink accents
  • Original Teal - Original PhotoVault theme
PhotoVault UI Patterns

Gallery Card:

<Card className="overflow-hidden transition-shadow hover:shadow-lg">
  <div className="aspect-[4/3] relative">
    <Image src={coverImage} alt={galleryName} fill className="object-cover" />
  </div>
  <CardContent className="p-4">
    <h3 className="font-semibold truncate">{galleryName}</h3>
    <p className="text-sm text-muted-foreground">{photoCount} photos</p>
  </CardContent>
</Card>

Paywall UI:

<div className="text-center py-12 px-4">
  <Lock className="h-12 w-12 mx-auto text-muted-foreground mb-4" />
  <h2 className="text-2xl font-bold mb-2">Gallery Access Required</h2>
  <p className="text-muted-foreground mb-6">Pay to unlock all photos</p>
  <Button size="lg" onClick={handlePayment}>Pay Now - ${price}</Button>
</div>
Semantic Color Tokens
Token Purpose
bg-background Page background
bg-card Card backgrounds
bg-primary Primary buttons
bg-secondary Secondary elements
bg-destructive Delete/error actions
text-muted-foreground Subtle text
border-border Borders
Component Installation
npx shadcn@latest add button card dialog form input

Debugging Checklist

  1. Are you using semantic color tokens? Check for hardcoded colors
  2. Is cn() being used for class merging?
  3. Are refs being forwarded in custom components?
  4. Is the component accessible? Test with keyboard
  5. Are form fields properly connected to labels?

使用示例

查看来源仓库中的使用示例与配置说明。

查看来源 ↗

来源历史

收录时间
2026-01-20
索引更新时间
2026-01-26
Branch
main

收录与同步日期属于本站记录。源码修改历史请查看来源仓库。

GitHub ↗

chat Comments (0)

chat_bubble_outline

No comments yet. Be the first to share your thoughts!