Skip to main content
Most applications share common UI elements across pages, such as a primary navigation bar, sidebar, or footer. Layout components let you define this shared UI once and wrap your pages with it automatically.

Creating Layouts

A layout is a standard component that accepts child content. There is nothing Inertia-specific about it.
<script setup>
import { Link } from "@inertiajs/vue3"
</script>

<template>
    <main>
        <header>
            <Link href="/">Home</Link>
            <Link href="/about">About</Link>
            <Link href="/contact">Contact</Link>
        </header>
        <article>
            <slot />
        </article>
    </main>
</template>
You may use a layout by wrapping your page content with it directly. However, this approach forces the layout instance to be destroyed and recreated between visits.
<script setup>
import Layout from './Layout'

defineProps({ user: Object })
</script>

<template>
    <Layout>
        <h1>Welcome</h1>
        <p>Hello {{ user.name }}, welcome to your first Inertia app!</p>
    </Layout>
</template>

Persistent Layouts

Wrapping a page with a layout as a child component works, but it means the layout is destroyed and recreated on every visit. This prevents maintaining layout state across navigations, such as an audio player that should keep playing or a sidebar that should retain its scroll position. Persistent layouts solve this by telling Inertia which layout to use for a page. Inertia then manages the layout instance separately, keeping it alive between visits.
<script>
import Layout from './Layout'

export default {
    layout: Layout,
}
</script>

<script setup>
defineProps({ user: Object })
</script>

<template>
    <h1>Welcome</h1>
    <p>Hello {{ user.name }}, welcome to your first Inertia app!</p>
</template>

Nested Layouts

You may create more complex layout arrangements using nested layouts. Pass an array of layout components to wrap the page in multiple layers.
<script>
import SiteLayout from './SiteLayout'
import NestedLayout from './NestedLayout'

export default {
    layout: [SiteLayout, NestedLayout],
}
</script>

<script setup>
defineProps({ user: Object })
</script>

<template>
    <h1>Welcome</h1>
    <p>Hello {{ user.name }}, welcome to your first Inertia app!</p>
</template>

Default Layouts

The layout option in createInertiaApp lets you define a default layout for all pages, saving you from defining it on every page individually. Per-page layouts always take precedence over the default.
import Layout from './Layout'

createInertiaApp({
    layout: () => Layout,
    // ...
})
You may also conditionally return a layout based on the page name. For example, you may wish to exclude public pages from the default layout.
import Layout from './Layout'

createInertiaApp({
    layout: (name) => {
        if (name.startsWith('Public/')) {
            return null
        }

        return Layout
    },
    // ...
})
The full page object is also available as the second argument, giving you access to the page’s URL, props, and other metadata. The layout callback supports all layout formats, including arrays for nested layouts, named objects for named layouts, and tuples for static props.

Using the Resolve Callback

You may also set a default layout inside the resolve callback by mutating the resolved page component. The callback receives the component name and the full page object, which is useful when you need to conditionally apply layouts based on page data.
import Layout from './Layout'

createInertiaApp({
    resolve: (name) => {
        const pages = import.meta.glob('./Pages/**/*.vue', { eager: true })
        let page = pages[`./Pages/${name}.vue`]
        page.default.layout = page.default.layout || Layout
        return page
    },
    // ...
})

Layout Props

Persistent layouts often need dynamic data from the current page, such as a page title, the active navigation item, or a sidebar toggle. Layout props provide a way to define defaults in your layout and override them from any page.

Defining Defaults

Layout props are defined as regular component props with default values.
<script setup>
const props = withDefaults(defineProps<{
    title?: string
    showSidebar?: boolean
}>(), {
    title: 'My App',
    showSidebar: true,
})
</script>

<template>
    <header>{{ title }}</header>
    <aside v-if="showSidebar">Sidebar</aside>
    <main>
        <slot />
    </main>
</template>

Static Props

You may pass static props directly in your persistent layout definition using a tuple. These props are set once when the layout is defined and don’t change between page navigations.
<script setup>
import Layout from './Layout'

defineProps({ user: Object })
defineOptions({
    layout: [Layout, { title: 'Dashboard' }],
})
</script>

<template>
    <h1>Dashboard</h1>
</template>

Callback Props

Sometimes layout props need to be derived from the current page’s props. A callback function receives the page props and returns a layout definition with computed static props.
<script setup>
import Layout from './Layout'

defineOptions({
    layout: (props) => [Layout, { title: 'Profile: ' + props.auth.user.name }],
})
</script>

<template>
    <h1>Profile</h1>
</template>
The callback receives the page’s props and may return any valid layout format: a single component, a tuple with static props, an array for nested layouts, or a named layout object. TypeScript users may use the LayoutCallback type for type safety.

Returning Props Only

When a default layout is configured in createInertiaApp, callbacks may return a plain props object instead of a full layout definition. Inertia will automatically use the default layout and merge the returned props onto it.
<script setup>
defineOptions({
    layout: (props) => ({ title: 'Profile: ' + props.auth.user.name, showSidebar: false }),
})
</script>

<template>
    <h1>Profile</h1>
</template>
A static object may also be used when the props don’t depend on page data.
Dashboard.layout = { title: 'Dashboard', showSidebar: true }

Dynamic Props

You may also update layout props dynamically from any page component using the setLayoutProps function. TypeScript users may type these props globally.
<script setup>
import { setLayoutProps } from '@inertiajs/vue3'

setLayoutProps({
    title: 'Dashboard',
    showSidebar: false,
})
</script>

<template>
    <h1>Dashboard</h1>
</template>

Targeting Named Layouts

Nested layouts may also be defined as a named object instead of an array, allowing you to target specific layouts with props.
<script>
import AppLayout from './AppLayout'
import ContentLayout from './ContentLayout'

export default {
    layout: {
        app: AppLayout,
        content: ContentLayout,
    },
}
</script>
You may target a specific named layout by passing the layout name as the first argument to setLayoutProps.
import { setLayoutProps } from '@inertiajs/vue3'

setLayoutProps('sidebar', {
    collapsed: true,
})
Nested layouts and named layouts may also include static props using the tuple syntax.
// Nested layouts with static props
Dashboard.layout = [
    [AppLayout, { title: 'Dashboard' }],
    [ContentLayout, { padding: 'sm' }],
]

// Named layouts with static props
Dashboard.layout = {
    app: [AppLayout, { theme: 'dark' }],
    content: [ContentLayout, { padding: 'sm' }],
}

Merge Priority

Layout props are resolved from multiple sources with the following priority (highest to lowest):
  1. Dynamic props - set via setLayoutProps()
  2. Static props - defined in the persistent layout definition (including callback props)
  3. Defaults - declared as default values on the layout component’s props

Auto-Reset on Navigation

Dynamic layout props are automatically reset when navigating to a new page (unless preserveState is enabled). This ensures each page starts with a clean slate and only the layout props explicitly set by that page are applied.

Resetting Props

You may also manually reset all dynamic layout props using resetLayoutProps.
import { resetLayoutProps } from '@inertiajs/vue3'

resetLayoutProps()