Skip to content
cccskills
FreePublished on 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.

The English summary is being prepared. Showing the original description.

See how to install

Files included (1)

  • SKILL.md9.5 KB

SKILL.md (original)

Review the instructions the agent will receive before you install.

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

Reviews

No reviews yet. Tell others what you thought.

Comments

No login needed. Share how it worked for you, ask a question, or note where it did not run.

No comments yet. Be the first to write one.

Comments are published right away. Do not include personal information such as email addresses or phone numbers. Up to 2 URLs.

0 / 1,000 characters

This form is protected by reCAPTCHA, and the Google Privacy Policy and Terms of Service apply.

More skills from this repository

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.

The English summary is being prepared. Showing the original description.

pwdev-solucoes/pwdev-claude-marketplace3Updated Oct 15, 2026

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".

The English summary is being prepared. Showing the original description.

pwdev-solucoes/pwdev-claude-marketplace3Updated Oct 15, 2026

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.

The English summary is being prepared. Showing the original description.

pwdev-solucoes/pwdev-claude-marketplace3Updated Oct 15, 2026

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.

The English summary is being prepared. Showing the original description.

pwdev-solucoes/pwdev-claude-marketplace3Updated Oct 15, 2026

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.

The English summary is being prepared. Showing the original description.

pwdev-solucoes/pwdev-claude-marketplace3Updated Oct 15, 2026

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".

The English summary is being prepared. Showing the original description.

pwdev-solucoes/pwdev-claude-marketplace3Updated Oct 15, 2026

See all skills by pwdev-solucoes

Report a problem with this skill