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 file name (without .vue) must match the uiComponent value stored in the Layout API entity. Capitalisation matters.

Minimal Layout

<!-- app/cwa/layouts/Primary.vue -->
<template>
    <div :class="uiClassNames">
        <header>
            <NuxtLink to="/">Home</NuxtLink>
        </header>

        <main>
            <slot />
        </main>

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

<script setup lang="ts">
const cwa = useCwa()

const layout = computed(() => cwa.resources.layout.value)
const uiClassNames = computed(() => layout.value?.data?.uiClassNames ?? '')
</script>

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

Accessing Layout Data

The layout resource is available through cwa.resources.layout:

const cwa = useCwa()

const layout = computed(() => cwa.resources.layout.value)
const uiClassNames = computed(() => layout.value?.data?.uiClassNames ?? '')
const isLoading = computed(() => cwa.resources.isLoading.value)

uiClassNames contains the CSS class name(s) the admin selected from the options you register in nuxt.config. Apply them to the layout's root element:

<div :class="uiClassNames">...</div>

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: [
                { value: '', label: 'Default' },
                { value: 'theme-light', label: 'Light' },
                { value: 'theme-dark', label: 'Dark' }
            ]
        }
    }
}

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 :class="uiClassNames">
    <!-- real content -->
</div>