CWA 2 is a prerelease (2.0.0-alpha), heading for a beta. It runs production sites; configuration can still change between releases, always with upgrade notes. View versions and support
Building Your Ui

Layouts

How to create CWA layout components that form the outer shell of your site — header, footer, navigation, and component groups.
New to layouts? Start with the Your First Layout guide. This page is the reference.

A CWA layout is the outer shell of your site — the header, footer, navigation, and any component groups that persist across pages. The PHP API has a Layout entity whose uiComponent value maps to your Vue file.

File Convention

app/app/cwa/layouts/primary.vue  →  registered as CwaLayoutPrimary

The Layout resource's uiComponent holds the full registered name, CwaLayoutPrimary. The admin panel stores that for you. The naming rule for layouts, page templates and components is below.

How uiComponent Names Resolve

The module registers the files in three directories as global components, each with its own prefix. Nuxt builds the name from the prefix and the PascalCased path below the directory:

DirectoryPrefixExample fileRegistered nameWhat uiComponent stores
app/app/cwa/layouts/CwaLayoutprimary.vueCwaLayoutPrimaryThe full name, CwaLayoutPrimary. It is rendered as stored, so Primary doesn't resolve
app/app/cwa/pages/CwaPageBlogDetail.vueCwaPageBlogDetailThe full name, CwaPageBlogDetail. The API adds the CwaPage prefix when a page is written with the short name, so the admin, the fixture builder and your own API calls all store the same value
app/app/cwa/components/CwaComponentTitle/Title.vueCwaComponentTitleNothing by default: a component renders by its type (@type). Picking a ui/ variant in the admin stores the variant's full name, such as CwaComponentNavigationLinkUiYouTube for NavigationLink/ui/YouTube.vue. The module renders it with or without the CwaComponent prefix

Three consequences:

  • The prefix is added to whatever the file is called. PrimaryLayout.vue registers as CwaLayoutPrimaryLayout, not CwaLayoutPrimary, so that is the name uiComponent must hold. A short file name such as primary.vue keeps the names readable.
  • Nuxt PascalCases the file name, so primary.vue and Primary.vue both register as CwaLayoutPrimary, and site-header.vue as CwaLayoutSiteHeader.
  • The nuxt.config keys under cwa.layouts and cwa.pages use the name without the prefix: Primary, BlogDetail.
The API stores a page's full name, and the admin sends it. The module also renders a page stored in the short form, such as BlogDetail, and the admin selects the right Page UI for it. prefix-page-ui-components rewrites such rows to the full name. Layouts are stored as sent, with no prefix added.

Minimal Layout

<!-- app/app/cwa/layouts/primary.vue -->
<template>
    <div>
        <header>
            <NuxtLink to="/">Home</NuxtLink>
        </header>

        <main>
            <slot />
        </main>

        <footer>
            <p>© 2024 My Site</p>
        </footer>
    </div>
</template>

<script setup lang="ts">
useCwaLayout()
</script>

No special props needed — the page content renders into the default <slot />.

Accessing Layout Data

useCwaLayout() gives you the layout resource and the style classes the admin selected:

const cwa = useCwa()
const { layout, uiClassNames } = useCwaLayout()

const isLoading = computed(() => cwa.resources.isLoading.value)

uiClassNames is a string[] of the CSS classes the admin selected from the options you register in nuxt.config. You don't need to bind it — the composable applies those classes to the layout's root element for you. Pass useCwaLayout({ autoClass: false }) if you'd rather bind uiClassNames yourself.

Component Groups

Add named component groups that admins can fill with components. The reference is a stable name unique within this layout's IRI:

<template>
    <div>
        <header>
            <CwaComponentGroup
                reference="navigation"
                :location="$cwa.resources.layoutIri.value"
            />
        </header>

        <slot />

        <footer>
            <CwaComponentGroup
                reference="footer"
                :location="$cwa.resources.layoutIri.value"
            />
        </footer>
    </div>
</template>

location must be the IRI of the owning entity — for layout groups, that's $cwa.resources.layoutIri.value.

Auth-Conditional UI

$cwa.auth.signedIn and $cwa.auth.status come from a cookie, so you can render them on the server. isAdmin, user and roles need the user loaded, so wrap UI that reads them in <ClientOnly>:

<UserMenu v-if="$cwa.auth.signedIn.value" />
<NuxtLink v-else to="/login">Sign in</NuxtLink>

See Reading Auth State for the details.

Registering Layout Options

Register your layout in nuxt.config to expose it in the admin panel and define which CSS classes an admin can apply:

// nuxt.config.ts
cwa: {
    layouts: {
        Primary: {
            name: 'Primary Layout',
            classes: {
                Light: 'theme-light',
                Dark: 'theme-dark'
            }
        }
    }
}

classes is a map of label → class string, keyed by the label the admin sees. A "Default" option (no classes) is added automatically — don't declare an empty entry for it.

A layout that isn't registered in nuxt.config still renders correctly — it just has no name in the admin and no class options.

Multiple Layouts

Create one file per layout variant. A common setup:

app/app/cwa/layouts/
    primary.vue     # full layout with header + footer
    Minimal.vue     # just a centered container (login pages, errors)
    Landing.vue     # marketing layout with large hero

Each is independently configurable in nuxt.config.layouts.

Loading State

Show a skeleton while the layout resource is loading. The layout loads first in the CWA fetch sequence, so this is typically only visible on the initial server-side render:

<div v-if="$cwa.resources.isLoading.value" class="animate-pulse ...">
    <!-- skeleton -->
</div>
<div v-else>
    <!-- real content -->
</div>

Renaming or Deleting a Layout

A Layout resource stores the component name in uiComponent. Rename or delete the Vue file and that stored value dangles — it now points at a component the app no longer registers.

CWA falls back to the default layout and warns rather than failing, so one dangling value can't take the site down:

The layout component 'CwaLayoutPrimary' for resource '/_/layouts/018e-…' cannot be resolved

The same message appears as a warning on the layout's admin page at /_cwa/layouts/[iri], and the select keeps the unresolvable value as a clearly-marked option so you can see what is actually stored. That display is presentational only — nothing rewrites the resource. Fix it by picking a registered component in the admin.