shadcn-vue-laravel
Skill0 starsChanged 7 months agoMITView on GitHub
Use when adapting shadcn-vue components or examples to a Laravel + Inertia + Vue application, especially when examples assume Nuxt, Rails, src-based paths, or vee-validate-first forms.
In this repository2 other files it gives its agents.
- laravel-inertiaSkill
- youtrackSkill
The file
Show its rules and text
We split this skill into the rules it gives an agent
16 rules. About 0 are common, 3 are distinctive, and 13 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈3
Distinctive · estimate
This author's own choices. Worth a look.
13
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Very specific: line 8
Use this skill as the translation layer from generic shadcn-vue examples to a Laravel + Inertia + Vue 3 codebase.
- Distinctive: line 23
Start from a working Laravel + Inertia + Vue application.
- Very specific: line 25
Keep generated components under resources/js/components/ui unless the project already uses a different component root.
- Very specific: line 26
Ensure @/* resolves to ./resources/js/* in tsconfig.json.
- Very specific: line 27
If the project uses @ imports and vite.config.ts does not already define the alias, add one that points to resources/js.
- Very specific: line 29
Keep Inertia boot files ESM-only.
- Distinctive: line 31
If you need the Vite alias, use:
- Very specific: line 46
If the app adopts @inertiajs/vite in v3, let that plugin own page resolution and dev SSR plumbing instead of copying older manual bootstrap …
- Very specific: line 50
Use Inertia <Form> for straightforward server-driven forms where inputs can submit through name attributes.
- Very specific: line 51
Use useForm when the component owns reactive state, conditional fields, client-side transforms, file uploads, or progressive validation.
- Very specific: line 54
Do not use shadcn-vue FormField, FormItem, FormLabel, or FormMessage inside an Inertia <Form> unless you intentionally wire vee-validate …
- Distinctive: line 59
Use plain shadcn-vue inputs plus manual error rendering:
- Very specific: line 177
Use <Link> instead of raw <a> for internal navigation that should preserve Inertia behavior.
- Very specific: line 182
If a component pattern depends on global router listeners or visit callbacks, verify the exact event names in $laravel-inertia before …
- Very specific: line 188
Read references/flash-toast.md when implementing centralized Sonner toasts or a reusable flash composable.
- Very specific: line 204
Use a small useAppearance composable and toggle the .dark class on <html>.
≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
shadcn-vue for Laravel Inertia
Use this skill as the translation layer from generic shadcn-vue examples to a Laravel + Inertia + Vue 3 codebase. Optimize for Laravel starter-kit conventions: Blade root view, @inertiajs/vue3, TypeScript, Tailwind, and frontend code under resources/js.
For broader Inertia application behavior such as useHttp, layout props, optimistic updates, deferred props, prefetch policy, router events, and Laravel adapter testing, use the companion skill $laravel-inertia.
Quick Translation Rules
- Nuxt data APIs such as
useFetchanduseAsyncData: replace with controller props, deferred props, partial reloads, or explicit Inertia visits. useRouter()from Nuxt or Vue Router: replace withrouterfrom@inertiajs/vue3.<NuxtLink>or<RouterLink>: replace with<Link>from@inertiajs/vue3.useHead(): replace with<Head>from@inertiajs/vue3.src/...,app/frontend/..., or Rails frontend paths: translate to the existing Laravel structure, usuallyresources/js/....- shadcn-vue form examples built around
vee-validate: do not copy them blindly into Inertia<Form>flows.
Setup
- Start from a working Laravel + Inertia + Vue application.
- Run
npx shadcn-vue@latest init. - Keep generated components under
resources/js/components/uiunless the project already uses a different component root. - Ensure
@/*resolves to./resources/js/*intsconfig.json. - If the project uses
@imports andvite.config.tsdoes not already define the alias, add one that points toresources/js. - Preserve the page and layout casing already used by the project. Do not rename
Pagestopages, or the reverse, just to match an example. - Keep Inertia boot files ESM-only. Do not introduce
require()intoapp.ts, SSR entrypoints, or Vite config.
If you need the Vite alias, use:
import path from 'node:path'
import { defineConfig } from 'vite'
export default defineConfig({
resolve: {
alias: {
'@': path.resolve(__dirname, 'resources/js'),
},
},
})If the app adopts @inertiajs/vite in v3, let that plugin own page resolution and dev SSR plumbing instead of copying older manual bootstrap patterns back into the project.
Choose <Form> vs useForm
- Use Inertia
<Form>for straightforward server-driven forms where inputs can submit throughnameattributes. - Use
useFormwhen the component owns reactive state, conditional fields, client-side transforms, file uploads, or progressive validation. v-modelis correct withuseForm.v-modelalone is not the source of truth for an Inertia<Form>; the submitted payload comes from inputnameattributes.- Do not use shadcn-vue
FormField,FormItem,FormLabel, orFormMessageinside an Inertia<Form>unless you intentionally wirevee-validatefor that page. They are not part of the default Laravel + Inertia path. - In v3,
useForm().processingremainstrueuntilonFinish, so disable buttons and pending UI fromprocessinginstead of resetting eagerly.
Default <Form> Pattern
Use plain shadcn-vue inputs plus manual error rendering:
<script setup lang="ts">
import { Form } from '@inertiajs/vue3'
import { Input } from '@/components/ui/input'
import { Label } from '@/components/ui/label'
import { Button } from '@/components/ui/button'
</script>
<template>
<Form method="post" action="/users">
<template #default="{ errors, processing }">
<div class="space-y-4">
<div>
<Label for="name">Name</Label>
<Input id="name" name="name" />
<p v-if="errors.name" class="text-sm text-destructive">{{ errors.name }}</p>
</div>
<div>
<Label for="email">Email</Label>
<Input id="email" name="email" type="email" />
<p v-if="errors.email" class="text-sm text-destructive">{{ errors.email }}</p>
</div>
<Button type="submit" :disabled="processing">
{{ processing ? 'Creating...' : 'Create User' }}
</Button>
</div>
</template>
</Form>
</template>Inside <Form>, Select must carry a name prop:
<template>
<Select name="role" default-value="member">
<SelectTrigger><SelectValue placeholder="Select role" /></SelectTrigger>
<SelectContent>
<SelectItem value="admin">Admin</SelectItem>
<SelectItem value="member">Member</SelectItem>
</SelectContent>
</Select>
</template>Default useForm Pattern
Switch to useForm when local reactive state is the point:
<script setup lang="ts">
import { useForm } from '@inertiajs/vue3'
import { Input } from '@/components/ui/input'
import { Label } from '@/components/ui/label'
import { Button } from '@/components/ui/button'
const form = useForm({
name: '',
email: '',
role: 'member',
})
const submit = () => form.post('/users')
</script>
<template>
<form class="space-y-4" @submit.prevent="submit">
<div>
<Label for="name">Name</Label>
<Input id="name" v-model="form.name" />
<p v-if="form.errors.name" class="text-sm text-destructive">{{ form.errors.name }}</p>
</div>
<div>
<Label for="email">Email</Label>
<Input id="email" v-model="form.email" type="email" />
<p v-if="form.errors.email" class="text-sm text-destructive">{{ form.errors.email }}</p>
</div>
<Button type="submit" :disabled="form.processing">
{{ form.processing ? 'Creating...' : 'Create User' }}
</Button>
</form>
</template>Shadcn-Relevant v3 Helper
useFormContext() lets deep child components read the parent <Form> state. Use it for reusable shadcn wrappers, sticky action bars, field summaries, or nested submit controls without prop drilling.
For broader Inertia primitives such as Precognition, cache invalidation, router error events, deferred props, and prefetch strategy, load $laravel-inertia instead of expanding this skill.
Example child component using useFormContext():
<script setup lang="ts">
import { useFormContext } from '@inertiajs/vue3'
import { Button } from '@/components/ui/button'
const form = useFormContext()
</script>
<template>
<div v-if="form" class="flex items-center gap-2">
<span v-if="form.isDirty" class="text-sm text-muted-foreground">Unsaved changes</span>
<Button type="button" :disabled="form.processing" @click="form.submit()">
Save
</Button>
</div>
</template>Component Gotchas
- Use
<Link>instead of raw<a>for internal navigation that should preserve Inertia behavior. - For table sorting, filtering, tabs, and dialogs driven by URL state, use
router.get()orrouter.reload()withpreserveStateandpreserveScrollwhen appropriate. usePage()is reactive; wrap derived values incomputed(() => page.props...)instead of destructuring once.- shadcn-vue
Dialogemitsupdate:open, notclose. - Translate Rails layout snippets to
resources/views/app.blade.php. - If a component pattern depends on global router listeners or visit callbacks, verify the exact event names in
$laravel-inertiabefore copying older snippets.
Flash Toasts
Prefer the Inertia v3 flash-data flow: server-side Inertia::flash(...), then page.flash, per-visit onFlash, or router.on('flash', ...) on the client. Use router.flash(...) for client-only ephemeral UI state when no server round trip is needed. Treat props.flash through HandleInertiaRequests as a compatibility path for older code, not the default for new work.
Read references/flash-toast.md when implementing centralized Sonner toasts or a reusable flash composable. Do not load it for pages that only render one flash value inline.
Dark Mode
npx shadcn-vue@latest init gives you the CSS variables. The Laravel-specific part is preventing FOUC in resources/views/app.blade.php before @vite(...) runs:
<script>
document.documentElement.classList.toggle(
'dark',
localStorage.appearance === 'dark' ||
(!('appearance' in localStorage) && window.matchMedia('(prefers-color-scheme: dark)').matches),
);
</script>Use a small useAppearance composable and toggle the .dark class on <html>. Do not pull in Nuxt color-mode or Rails-specific theme helpers.
Troubleshooting
| Symptom | Cause | Fix |
FormField or FormMessage crash | shadcn-vue form components were copied without vee-validate context | Use plain Input / Label plus Inertia error rendering, or intentionally wire vee-validate |
Select value is missing on submit | <Select> inside <Form> has no name prop | Add name="field" |
v-model value never reaches the server | <Form> is reading DOM field names, not your local refs | Use name attributes or switch to useForm |
| Shared props look stale after navigation | A usePage() value was destructured once | Wrap derived values in computed() |
| Dialog close handler never fires | @close was used instead of @update:open | Handle @update:open="(open) => { if (!open) close() }" |
| Global error listener never runs | v2 event names were copied into a v3 app | Replace invalid with httpException and exception with networkError |
| Dark mode flashes on first paint | Theme script runs after Vite assets | Put the script in resources/views/app.blade.php before @vite(...) |
References
- Load
references/components.mdwhen you need extended patterns such as AlertDialog, Sheet, Tabs, DropdownMenu, Pagination, Search, Date Picker, or Breadcrumbs. - Load
references/flash-toast.mdwhen you need centralized flash-toast wiring with Sonner.
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

