ArtisanPack UI
DocsVueLaravel and Inertia

Laravel and Inertia

Vue v2.0.0

connects the components to a Laravel app running Inertia.js: form fields that show Laravel's validation errors, pagination from a Laravel paginator, a...

@artisanpack-ui/vue-laravel connects the components to a Laravel app running Inertia.js: form fields that show Laravel's validation errors, pagination from a Laravel paginator, a menu that navigates with Inertia links, flash messages and the signed-in user.

Install

npm install @artisanpack-ui/vue-laravel @artisanpack-ui/css

It depends on @artisanpack-ui/vue, and needs vue 3.5+ and @inertiajs/vue3 2.0+ as peer dependencies. Load the CSS as in Getting Started.

Set up Inertia

Nothing has to be registered: import the components where you use them.

// resources/js/app.ts
import '@artisanpack-ui/css';
import { createApp, h } from 'vue';
import { createInertiaApp } from '@inertiajs/vue3';

createInertiaApp({
  resolve: (name) => {
    const pages = import.meta.glob('./Pages/**/*.vue', { eager: true });
    return pages[`./Pages/${name}.vue`];
  },
  setup({ el, App, props, plugin }) {
    createApp({ render: () => h(App, props) })
      .use(plugin)
      .mount(el);
  },
});

To register the adapter components globally, add app.use(createArtisanPackUILaravel()). It registers them with a prefix (Ap by default, so <ApInertiaInput>), set with the prefix option. It registers every component on this page. Pass flashColorMap to set the flash toast colors app-wide; see As toasts.

The <head> of an Inertia app is in the Blade root template, so apply the stored theme choice there: inline the output of themeScript() from @artisanpack-ui/behavior first in <head> of app.blade.php. If the app also has Livewire UI Components, its @apuiThemeScript directive renders the same script. See Theming.

Share the props

The helpers read auth.user, flash and errors from the shared page props. Share them in HandleInertiaRequests:

public function share(Request $request): array
{
    return [
        ...parent::share($request),
        'auth' => [
            'user' => $request->user(),
        ],
        'flash' => [
            'success' => fn () => $request->session()->get('success'),
            'error' => fn () => $request->session()->get('error'),
            'warning' => fn () => $request->session()->get('warning'),
            'info' => fn () => $request->session()->get('info'),
        ],
    ];
}

Inertia shares errors itself.

Forms

InertiaForm and the field wrappers

InertiaForm creates an Inertia form from data and submits it to action with method (post by default). The field wrappers inside it show the error for their name:

<script setup lang="ts">
import { Button } from '@artisanpack-ui/vue';
import { InertiaForm, InertiaInput, InertiaPassword } from '@artisanpack-ui/vue-laravel';
</script>

<template>
  <InertiaForm v-slot="{ form }" :data="{ email: '', password: '' }" action="/login">
    <InertiaInput v-model="form.email" name="email" label="Email" type="email" />
    <InertiaPassword v-model="form.password" name="password" label="Password" />
    <Button type="submit" color="primary" :loading="form.processing">Sign in</Button>
  </InertiaForm>
</template>
Prop Type Default Description
data Record<string, unknown> The form's initial data
action string The URL to submit to. Without it, submitting does nothing
method 'get' | 'post' | 'put' | 'patch' | 'delete' 'post' The HTTP method
options { resetOnSuccess?: boolean } resetOnSuccess defaults to true

It emits success and error after a submission. Its default slot receives form (the Inertia form), getError, clearErrors, processing, recentlySuccessful, isDirty and submit, and the same are exposed on a template ref. processing, recentlySuccessful and isDirty arrive as plain booleans, so :loading="processing" follows the submission.

Wrapper Wraps Binding
InertiaInput Input v-model
InertiaPassword Password v-model
InertiaTextarea Textarea v-model
InertiaSelect Select v-model
InertiaCheckbox Checkbox v-model (the checked state)
InertiaToggle Toggle v-model (the checked state)
InertiaRadioGroup RadioGroup v-model
InertiaRadio Radio (deprecated) v-model. Use InertiaRadioGroup

Each wrapper takes a required name and passes every other prop and attribute to the component it wraps. Its error is, in order: the error prop you pass, the enclosing InertiaForm's error for name, or errors[name] from the page props. So the wrappers also work without InertiaForm, with a form you submit yourself.

useInertiaForm

useInertiaForm(data, options) wraps Inertia's useForm() for use without InertiaForm:

<script setup lang="ts">
import { Button, Input } from '@artisanpack-ui/vue';
import { useInertiaForm } from '@artisanpack-ui/vue-laravel';

const { form, getError, post, processing } = useInertiaForm({ name: '', email: '' });
</script>

<template>
  <form @submit.prevent="post('/users')">
    <Input v-model="form.name" name="name" label="Name" :error="getError('name')" />
    <Input
      v-model="form.email"
      name="email"
      label="Email"
      type="email"
      :error="getError('email')"
    />
    <Button type="submit" color="primary" :loading="processing">Save</Button>
  </form>
</template>
Returns Description
form The Inertia form
errors, processing, recentlySuccessful, isDirty Computed refs of the form's state
getError(field) The field's error, for a component's error prop
submit(method, url, options?) Submits the form
post, put, patch, destroy (url, options?) shorthands for submit
reset(...fields), clearErrors(...fields) Reset values or clear errors: the given fields, or all of them

With resetOnSuccess (the default), the form resets after a successful submission, after any onSuccess you pass.

InertiaPagination

Renders Pagination from a Laravel LengthAwarePaginator and visits the chosen page with Inertia:

<script setup lang="ts">
import type { LaravelPaginator } from '@artisanpack-ui/vue-laravel';
import { InertiaPagination } from '@artisanpack-ui/vue-laravel';

defineProps<{ users: LaravelPaginator<{ id: number; name: string }> }>();
</script>

<template>
  <InertiaPagination :paginator="users" :query-params="{ search: 'ada' }" preserve-scroll />
</template>
Prop Type Default Description
paginator LaravelPaginator The paginator from the controller
siblingCount number Pages shown on each side of the current one
queryParams Record<string, string | number> Query parameters to keep on every page link
preserveScroll boolean false Inertia's preserveScroll
preserveState boolean false Inertia's preserveState
className string A class for the Pagination root

InertiaMenu

Renders Menu with its links as Inertia <Link>s:

<InertiaMenu
  label="Main"
  :items="[
    { name: 'dashboard', label: 'Dashboard', href: '/dashboard', active: true },
    { name: 'users', label: 'Users', href: '/users', badge: 3 },
    {
      name: 'settings',
      label: 'Settings',
      children: [{ name: 'profile', label: 'Profile', href: '/settings/profile' }],
    },
  ]"
  @select="onSelect"
/>

Each item has a name, a label and optionally href, active, disabled, badge and children (a sub-menu, open while it holds the active item). The menu takes label and collapsible (default true), and emits select with the item's name.

InertiaBreadcrumbs

Renders the design-system Breadcrumbs with its links as Inertia <Link>s:

<InertiaBreadcrumbs
  :items="[
    { label: 'Users', href: '/users' },
    { label: 'Ada Lovelace', href: '/users/1' },
    { label: 'Edit' },
  ]"
/>

Each item is { label, href? }. The last item is the current page and is never a link. The scoped item slot ({ item, index, isLast }) replaces an item's text. className and every other attribute, including Breadcrumbs' own props (label, maxItems, overflowLabel, classes), go to the Breadcrumbs. Like Breadcrumbs, it collapses the middle of a trail longer than maxItems (default 4) behind an overflow button; set :max-items="0" to never collapse.

Flash messages

FlashAlerts

Renders each flash message in the page props as an Alert: success, error, warning and info keep their color, and any other key shows as info.

<FlashAlerts />

It takes dismissible (default true) and className for the wrapper.

As toasts

FlashToasts shows each flash message as a design-system Toast and renders the Toaster that displays them, after its default slot:

<script setup lang="ts">
import { FlashToasts } from '@artisanpack-ui/vue-laravel';
</script>

<template>
  <FlashToasts placement="top-end">
    <slot />
  </FlashToasts>
</template>
Prop Type Default Description
colorMap Record<string, FlashToastColor> Flash key to toast color, merged over the plugin's flashColorMap and the defaults
duration number 5000 Milliseconds before each toast dismisses itself. 0 keeps it
toaster ToasterInstance A toaster from createToaster(). Defaults to the page's toast
region boolean true Renders the Toaster. Set :region="false" when the page already has one

Every other attribute, including the Toaster's own props (placement, label, closeLabel), goes to the Toaster.

useFlashMessages() is the composable underneath. It takes the same colorMap, duration and toaster options and returns { toast }. Use it when you render the Toaster yourself:

<script setup lang="ts">
import { Toaster } from '@artisanpack-ui/vue';
import { useFlashMessages } from '@artisanpack-ui/vue-laravel';

useFlashMessages({ colorMap: { status: 'success' } });
</script>

<template>
  <slot />
  <Toaster />
</template>

Each non-empty flash value becomes one toast when the page loads and after each visit. The colors come from colorMap, then the plugin's flashColorMap (app.use(createArtisanPackUILaravel({ flashColorMap: { status: 'success' } }))), then the defaults: success, error, warning and info keep their color, message shows as info, and any other key shows as info. A color is a FlashToastColor, any toast color: primary, secondary, accent, neutral, success, warning, error or info.

The signed-in user

useAuth() reads auth.user from the page props:

<script setup lang="ts">
import { useAuth } from '@artisanpack-ui/vue-laravel';

const { user, isAuthenticated } = useAuth<{
  id: number;
  name: string;
  email: string;
  role: string;
}>();
</script>

<template>
  <p v-if="isAuthenticated">Hello, {{ user?.name }}</p>
</template>

It returns user, isAuthenticated and isGuest as computed refs.

Layouts

AppLayout, AuthLayout and GuestLayout are page shells that set the document title with Inertia's <Head> from a title prop, and take className.

Layout Slots
AppLayout header, default (in <main>), footer
AuthLayout header, sidebar, default (in <main>), footer
GuestLayout logo, default, in a centered column

Each is a design-system Stack that fills the viewport, with <main> taking the free space, so they need only @artisanpack-ui/css, not Tailwind. For an application frame with a sidebar, use AppShell.

Types

@artisanpack-ui/vue-laravel exports types for Laravel's responses and the shared props: LaravelPaginator, LaravelSimplePaginator, PaginationLink, FlashMessages, FlashToastColor, AuthUser, AuthProps, SharedPageProps, InertiaFormOptions, InertiaFormContext, UseFlashMessagesOptions, ArtisanPackUILaravelOptions, AppLayoutProps, AuthLayoutProps and GuestLayoutProps.

import type { SharedPageProps } from '@artisanpack-ui/vue-laravel';

interface PageProps extends SharedPageProps {
  appName: string;
}