The CWA is in heavy development
The CWA is still in alpha and not ready for production - some code and implementations are likely to change. If you would like to try out the CWA, please enjoy what we have provided and feel free to provide feedback, or get involved on GitHub.
DraftBuilding Your Ui

Layouts

How to create CWA layout components that form the outer shell of your site — header, footer, navigation, and component group regions.

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/cwa/layouts/Primary.vue  →  registered as CwaLayoutPrimary

The uiComponent value stored on the Layout API entity is the full registered component nameCwaLayoutPrimary for app/cwa/layouts/Primary.vue. Nuxt PascalCases the file name, so primary.vue also registers as CwaLayoutPrimary.

Unlike pages and components, layouts are rendered straight from uiComponent with no prefix handling. A Layout entity storing Primary instead of CwaLayoutPrimary will not resolve. The admin panel always stores the full name for you.

Minimal Layout

<!-- 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 content regions that admins can populate 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

Auth state is only available after client hydration. Always wrap auth-dependent UI in <ClientOnly> to avoid SSR hydration mismatches:

<ClientOnly>
    <template #default>
        <UserMenu v-if="$cwa.auth.signedIn.value" />
        <NuxtLink v-else to="/login">Sign in</NuxtLink>
    </template>
    <template #fallback>
        <!-- server-side: render nothing or a skeleton -->
        <div class="w-20 h-8 bg-gray-200 rounded animate-pulse" />
    </template>
</ClientOnly>

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/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>