active

SkillSlap Component Builder

Safe
System VerifiedSafe

Generate Next.js 16 App Router components following SkillSlap patterns: Tailwind + Shadcn, server/client split, typed Supabase queries, no SELECT *. Describe the UI you need and get a production-ready component.

@system/skillslap-component-builder

nextjs
react
typescript
tailwind
supabase
skillslap

SkillSlap Component Builder

Purpose: Generate a production-ready Next.js 16 App Router component following SkillSlap's exact codebase conventions. The output should be paste-ready into skillslap/components/ with no modification.


Invocation

code
/component <description>

Examples:

  • /component A skill report modal — user selects a reason from a dropdown and submits
  • /component A collections sidebar showing the user's saved skill collections with add/remove
  • /component A skill version history timeline showing diffs between versions

SkillSlap Component Conventions

Server vs Client

Server component (default — no 'use client'):

  • Fetches data directly from Supabase via createClient() from @/lib/supabase/server
  • Cannot use hooks, event listeners, or browser APIs
  • Use for: pages, data-fetching wrappers, layout sections

Client component (add 'use client' at top):

  • Required for: useState, useEffect, event handlers, useRouter, useSearchParams
  • Keep client components small — push data fetching up to a server parent
tsx
// Server parent passes data down
async function SkillCollectionsList({ userId }: { userId: string }) {
  const supabase = await createClient()
  const { data } = await (supabase.from('skill_collections') as any)
    .select('id, title, created_at')
    .eq('user_id', userId)
  return <SkillCollectionsClient collections={data ?? []} />
}

Supabase queries — NEVER SELECT *

Always use explicit column lists. For skills table use SKILLS_COLUMNS from @/lib/supabase/skills-columns:

tsx
import { SKILLS_COLUMNS } from '@/lib/supabase/skills-columns'

const { data } = await supabase
  .from('skills')
  .select(`${SKILLS_COLUMNS}, author:users!skills_author_id_fkey(id, username, avatar_url)`)
  .eq('status', 'active')

For joins or partial selects, add as any to satisfy the typed client:

tsx
const { data } = await (supabase.from('skill_collections') as any)
  .select('id, title, skill_count')

Styling — Tailwind + cn() utility

tsx
import { cn } from '@/lib/utils/cn'

// Always use cn() for conditional classes
<div className={cn(
  'base-classes here',
  isActive && 'active-classes',
  className  // always accept className prop
)} />

Color palette:

  • Background: bg-gray-950, bg-gray-900, bg-gray-800
  • Borders: border-gray-800, border-gray-700
  • Text: text-white, text-gray-300, text-gray-400, text-gray-500
  • Accent: text-purple-400, border-purple-600/50, bg-purple-600
  • Success: text-green-400
  • Warning: text-yellow-400
  • Danger: text-red-400

Shadcn UI components (already installed)

tsx
import { Button } from '@/components/ui/button'
import { Input } from '@/components/ui/input'
import { Badge } from '@/components/ui/badge'
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'
import * as Dialog from '@radix-ui/react-dialog'
import * as DropdownMenu from '@radix-ui/react-dropdown-menu'

Icons — lucide-react only

tsx
import { Plus, X, Search, ChevronDown, Loader2 } from 'lucide-react'

Error + loading states

tsx
// Loading skeleton
<div className="h-8 bg-gray-800 rounded animate-pulse" />

// Error display
<p className="text-sm text-red-400">{error}</p>

// Empty state
<div className="text-center py-12 text-gray-500">
  <p className="text-sm">No items yet</p>
</div>

Forms + mutations (client component)

tsx
'use client'
import { useState } from 'react'
import { createClient } from '@/lib/supabase/client'  // note: client, not server

async function handleSubmit(e: React.FormEvent) {
  e.preventDefault()
  setLoading(true)
  setError(null)
  try {
    const supabase = createClient()
    const { error } = await supabase.from('table').insert({ ... })
    if (error) throw error
    onSuccess?.()
  } catch (err: any) {
    setError(err.message ?? 'Something went wrong')
  } finally {
    setLoading(false)
  }
}

Props interface pattern

tsx
interface MyComponentProps {
  // required props first
  skillId: string
  // optional props with defaults
  className?: string
  onSuccess?: () => void
}

export function MyComponent({ skillId, className, onSuccess }: MyComponentProps) {

TypeScript types

tsx
import type { SkillWithAuthor } from '@/lib/supabase/types'

Output Format

  1. Main component file — full TypeScript, no placeholders, no TODOs
  2. File path — where it belongs in skillslap/components/
  3. Imports — only what's used
  4. If server + client split needed — output both files

Checklist Before Outputting

  • No SELECT * — explicit columns everywhere
  • as any on Supabase joins
  • cn() for all conditional classNames
  • className?: string prop on every component
  • Loading and error states present
  • 'use client' only where actually needed
  • No hardcoded colors — use SkillSlap palette
Dormant$0/mo

$20 more to next tier

Info

Created February 24, 2026
Version 1.0.0
Agent-invoked
Terminal output

Embed

Add this skill card to any webpage.

<iframe src="https://skillslap.com/skill/a4d142cd-d11b-47ac-98e4-6d9432bc3700/embed"
        width="400" height="200"
        style="border:none;border-radius:12px;"
        title="SkillSlap Skill: SkillSlap Component Builder">
</iframe>