本文へ移動
cccskills
無料GitHub で公開

shadcn-vue

Skill for scaffolding, installation, and composition of shadcn-vue components (based on Reka UI v2). Use when you need to install components, understand Vue variants, compose patterns with vee-validate/Zod, or adapt existing components. Stack: Vue 3 + Reka UI v2 + Tailwind CSS.

インストール方法を見る

含まれるファイル(1)

  • SKILL.md9.5 KB

SKILL.md(原文)

インストールする前に、エージェントに与えられる指示の中身を確認できます。

Skill: shadcn-vue + Reka UI v2

shadcn-vue@latest uses Reka UI v2 by default (since Feb/2025). For legacy Radix Vue: shadcn-vue@radix

CLI — Essential commands

# Initialize (new project)
npx shadcn-vue@latest init

# Add component
npx shadcn-vue@latest add button
npx shadcn-vue@latest add form dialog card

# Add from remote URL (custom registry)
npx shadcn-vue add https://acme.com/registry/navbar.json

# Check installed components
ls components/ui/

Vite + Vue 3 Installation

npm create vue@latest my-app
cd my-app
npm install
npx shadcn-vue@latest init

Nuxt 3 Installation

npx nuxi@latest init my-app
cd my-app
npx shadcn-vue@latest init

Generated components.json:

{
  "$schema": "https://shadcn-vue.com/schema.json",
  "style": "new-york",
  "tailwind": {
    "config": "tailwind.config.ts",
    "css": "src/assets/index.css",
    "baseColor": "zinc",
    "cssVariables": true
  },
  "aliases": {
    "components": "@/components",
    "utils": "@/lib/utils",
    "ui": "@/components/ui",
    "lib": "@/lib",
    "composables": "@/composables"
  }
}

Vue 3 Component Patterns

Button

<script setup lang="ts">
import { Button } from '@/components/ui/button'
import { Loader2, Trash2 } from 'lucide-vue-next'
</script>

<template>
  <!-- Variants: default | destructive | outline | secondary | ghost | link -->
  <!-- Sizes: default | sm | lg | icon -->
  <Button variant="destructive" size="sm" :disabled="isLoading">
    <Loader2 v-if="isLoading" class="mr-2 h-4 w-4 animate-spin" aria-hidden="true" />
    Delete
  </Button>

  <!-- Icon-only button — always use aria-label -->
  <Button variant="ghost" size="icon" aria-label="Close panel">
    <X class="h-4 w-4" aria-hidden="true" />
  </Button>
</template>

Card with actions

<script setup lang="ts">
import {
  Card, CardContent, CardDescription,
  CardFooter, CardHeader, CardTitle
} from '@/components/ui/card'
import { Badge } from '@/components/ui/badge'
import { Button } from '@/components/ui/button'
</script>

<template>
  <Card>
    <CardHeader>
      <div class="flex items-center justify-between">
        <CardTitle>Card Title</CardTitle>
        <Badge variant="outline">Status</Badge>
      </div>
      <CardDescription>Clear and concise description.</CardDescription>
    </CardHeader>
    <CardContent>
      <slot />
    </CardContent>
    <CardFooter class="flex justify-end gap-2">
      <Button variant="outline" @click="emit('cancel')">Cancel</Button>
      <Button @click="emit('confirm')">Confirm</Button>
    </CardFooter>
  </Card>
</template>

Dialog (Modal)

<script setup lang="ts">
import { ref } from 'vue'
import {
  Dialog, DialogContent, DialogDescription,
  DialogHeader, DialogTitle, DialogTrigger
} from '@/components/ui/dialog'
import { Button } from '@/components/ui/button'

const open = ref(false)
</script>

<template>
  <Dialog v-model:open="open">
    <DialogTrigger as-child>
      <Button>Open Dialog</Button>
    </DialogTrigger>
    <DialogContent>
      <DialogHeader>
        <DialogTitle>Accessible Title</DialogTitle>
        <DialogDescription>
          Description of what happens in this dialog.
        </DialogDescription>
      </DialogHeader>
      <!-- content -->
    </DialogContent>
  </Dialog>
</template>

Select

<script setup lang="ts">
import { ref } from 'vue'
import {
  Select, SelectContent, SelectItem,
  SelectTrigger, SelectValue
} from '@/components/ui/select'

const value = ref('')
</script>

<template>
  <Select v-model="value">
    <SelectTrigger class="w-full">
      <SelectValue placeholder="Select an option" />
    </SelectTrigger>
    <SelectContent>
      <SelectItem value="opt1">Option 1</SelectItem>
      <SelectItem value="opt2">Option 2</SelectItem>
    </SelectContent>
  </Select>
</template>

Complete form with vee-validate + Zod

<script setup lang="ts">
import { useForm } from 'vee-validate'
import { toTypedSchema } from '@vee-validate/zod'
import * as z from 'zod'
import { toast } from 'vue-sonner'
import { Button } from '@/components/ui/button'
import {
  FormControl, FormDescription, FormField,
  FormItem, FormLabel, FormMessage
} from '@/components/ui/form'
import { Input } from '@/components/ui/input'
import { Textarea } from '@/components/ui/textarea'

const schema = toTypedSchema(
  z.object({
    name: z.string().min(2, 'Name must have at least 2 characters'),
    email: z.string().email('Invalid email'),
    message: z.string().min(10, 'Message too short').max(500),
  })
)

const form = useForm({ validationSchema: schema })

const onSubmit = form.handleSubmit(async (values) => {
  try {
    await api.submit(values)
    toast.success('Message sent successfully!')
    form.resetForm()
  } catch {
    toast.error('Error sending. Please try again.')
  }
})
</script>

<template>
  <form @submit="onSubmit" class="space-y-6" novalidate>
    <FormField v-slot="{ componentField }" name="name">
      <FormItem>
        <FormLabel>Name</FormLabel>
        <FormControl>
          <Input placeholder="Your name" v-bind="componentField" />
        </FormControl>
        <FormMessage />
      </FormItem>
    </FormField>

    <FormField v-slot="{ componentField }" name="email">
      <FormItem>
        <FormLabel>Email</FormLabel>
        <FormControl>
          <Input type="email" placeholder="name@company.com" v-bind="componentField" />
        </FormControl>
        <FormDescription>We never share your email.</FormDescription>
        <FormMessage />
      </FormItem>
    </FormField>

    <FormField v-slot="{ componentField }" name="message">
      <FormItem>
        <FormLabel>Message</FormLabel>
        <FormControl>
          <Textarea placeholder="Your message..." v-bind="componentField" />
        </FormControl>
        <FormMessage />
      </FormItem>
    </FormField>

    <Button type="submit" :disabled="form.isSubmitting.value" class="w-full">
      <Loader2
        v-if="form.isSubmitting.value"
        class="mr-2 h-4 w-4 animate-spin"
        aria-hidden="true"
      />
      {{ form.isSubmitting.value ? 'Sending...' : 'Send' }}
    </Button>
  </form>
</template>

Field component (alternative without vee-validate)

<script setup lang="ts">
import {
  Field, FieldGroup, FieldLabel,
  FieldDescription, FieldError, FieldSet, FieldLegend
} from '@/components/ui/field'
import { Input } from '@/components/ui/input'
</script>

<template>
  <FieldGroup>
    <FieldSet>
      <FieldLegend>Personal Information</FieldLegend>
      <FieldGroup>
        <Field>
          <FieldLabel html-for="username">Username</FieldLabel>
          <Input id="username" v-model="username" />
          <FieldDescription>Your public username.</FieldDescription>
          <FieldError v-if="usernameError">{{ usernameError }}</FieldError>
        </Field>
      </FieldGroup>
    </FieldSet>
  </FieldGroup>
</template>

Empty state (native shadcn-vue component)

<script setup lang="ts">
import { Empty, EmptyContent, EmptyDescription, EmptyHeader, EmptyTitle } from '@/components/ui/empty'
import { Button } from '@/components/ui/button'
import { SearchIcon } from 'lucide-vue-next'
</script>

<template>
  <Empty>
    <EmptyHeader>
      <EmptyTitle>No results found</EmptyTitle>
      <EmptyDescription>Try adjusting your search or filters.</EmptyDescription>
    </EmptyHeader>
    <EmptyContent>
      <Button variant="outline" @click="reset">Clear filters</Button>
    </EmptyContent>
  </Empty>
</template>

Toast with vue-sonner

<script setup lang="ts">
import { toast } from 'vue-sonner'

function handleAction() {
  try {
    // action
    toast.success('Saved successfully!')
  } catch {
    toast.error('Error saving. Please try again.')
  }
}

// With undo action
toast('Email archived', {
  action: {
    label: 'Undo',
    onClick: () => undo(),
  },
})
</script>

Add <Toaster /> in the root layout (app.vue or layouts/default.vue):

<script setup>
import { Toaster } from 'vue-sonner'
</script>
<template>
  <div>
    <slot />
    <Toaster richColors position="bottom-right" />
  </div>
</template>

cn() — class composition

// lib/utils.ts (generated by init)
import { type ClassValue, clsx } from 'clsx'
import { twMerge } from 'tailwind-merge'

export function cn(...inputs: ClassValue[]) {
  return twMerge(clsx(inputs))
}

Usage in template:

<div :class="cn('base', isActive && 'active', props.class)" />

Critical gotchas Vue + shadcn-vue

ProblemCauseSolution
componentField undefinedAuto-import conflict Form/vee-validateImport explicitly from '@/components/ui/form'
asChild does not propagate propsExtra element wrapping the triggerUse as-child directly on the component
Dialog does not close with EscapeDialogContent without @keydown.escReka UI manages it — do not override
v-model on Select not reactiveUsing modelValue without update:modelValueUse v-model directly on the Select root
Skeleton wrong sizeDimensions do not mirror the actual contentReplicate h-* and w-* of the actual element
Toaster in child componentReinitializes and loses statePlace only in root layout

レビュー

まだレビューはありません。使ってみた感想をお寄せください。

同じリポジトリのスキル

概要と使いどころ

Accessibility auditing and remediation skill for Vue 3 + Reka UI. Use when reviewing components, verifying WCAG 2.1 AA, fixing ARIA issues, or validating keyboard navigation in Reka UI components.

日本語の概要は準備中です。原文の説明を表示しています。

pwdev-solucoes/pwdev-claude-marketplace32026年9月28日 更新

ai-infra

無料

Infraestrutura para IA — GPU, servidores MCP, gateway de LLM, pipeline de inferência, custo de token, cache semântico e observabilidade de modelo. Use quando o usuário disser "GPU", "MCP server", "LLM", "inferência", "modelo", "gateway de IA", "custo de token", "rate limit da API de IA", "self-host de modelo".

日本語の概要は準備中です。原文の説明を表示しています。

pwdev-solucoes/pwdev-claude-marketplace32026年9月28日 更新

alt-text

無料

Escreve texto alternativo e descrição de imagem para peças de rede social. Use quando o usuário disser "alt text", "texto alternativo", "descrição da imagem", "acessibilidade da peça", "descrever para leitor de tela", ou automaticamente antes de qualquer publicação. Obrigatório em todas as peças — em carrossel, um por slide.

日本語の概要は準備中です。原文の説明を表示しています。

pwdev-solucoes/pwdev-claude-marketplace32026年9月28日 更新

Organiza, compara e seleciona entre as variações geradas, e publica a grade de comparação como Artifact para aprovação do time. Use quando o usuário disser "qual dessas", "comparar as variações", "escolher a melhor", "mostrar as opções", "montar grade de aprovação", ou após qualquer lote de geração. Selecionar é trabalho, não detalhe.

日本語の概要は準備中です。原文の説明を表示しています。

pwdev-solucoes/pwdev-claude-marketplace32026年9月28日 更新

Aumenta resolução e qualidade de imagem via Freepik/Magnific, ou orienta o processo manual quando não há chave. Use quando o usuário disser "upscale", "melhorar a resolução", "a imagem está pixelada", "Magnific", "Freepik", "aumentar a imagem", "imagem pequena demais". Gasta crédito — exige confirmação.

日本語の概要は準備中です。原文の説明を表示しています。

pwdev-solucoes/pwdev-claude-marketplace32026年9月28日 更新

CI/CD e automação — GitHub Actions, Terraform, OpenTofu, Ansible, n8n. Use quando o usuário disser "pipeline", "CI/CD", "GitHub Actions", "workflow", "terraform", "ansible", "n8n", "automatizar", "deploy automático", "IaC".

日本語の概要は準備中です。原文の説明を表示しています。

pwdev-solucoes/pwdev-claude-marketplace32026年9月28日 更新

pwdev-solucoes のスキルをすべて見る

このスキルの問題を報告する