Your First Layout
The layout is already part of the CWA data model — your job is to create the Vue file it renders through. You write one file; editors create instances with different content configurations (different nav links, different class names) and assign pages to whichever instance they want.
The key difference from a regular Nuxt layout: the navigation inside a CWA layout isn't hard-coded HTML. It's a component group — a named region that an editor fills with components at runtime. Change the nav without touching code.
The File
Create app/cwa/layouts/primary.vue:
<template>
<div>
<header>
<nav>
<CwaComponentGroup
reference="navigation"
:location="$cwa.resources.layoutIri.value"
:allowed-components="['/component/navigation_links']"
/>
</nav>
</header>
<main>
<slot />
</main>
<footer>
<p>My Site © {{ new Date().getFullYear() }}</p>
</footer>
</div>
</template>
<script setup lang="ts">
import { useCwa } from '#imports'
const $cwa = useCwa()
</script>
What's happening here
<slot /> — this is where the current page template renders. The module injects the page content here automatically.
useCwa() — gives you access to the current layout's resource data: layoutIri (the IRI of the active layout record) and layout.value?.data (its fields).
CwaComponentGroup — marks a region in the layout where components can be placed by editors.
reference— a stable name for this region (e.g."navigation","header-cta"). Must be unique within this layout.location— the IRI of the resource that owns this group. For layout regions, always use$cwa.resources.layoutIri.value.
location must be the IRI of a layout, page, or component — not a ComponentGroup or any other resource type. For layout regions: $cwa.resources.layoutIri.value. For page regions: props.iri (the IRI passed into the page template as a prop).allowed-components— restricts which component types editors can add./component/navigation_linkslimits this region toNavigationLinkcomponents only. Omit it to allow any component type.
Register in nuxt.config
// nuxt.config.ts
cwa: {
layouts: {
primary: {
name: 'Primary Layout',
classes: [
{ value: '', label: 'Default' },
{ value: 'bg-gray-50', label: 'Light Background' },
]
}
}
}
The classes array defines the style options an editor can choose from for this layout. The selected value is stored on the layout record as uiClassNames and you apply it to the root element of your Vue file:
<div :class="$cwa.resources.layout.value?.data?.uiClassNames">
Create the Layout in the API
The Vue file defines how a layout looks, but it doesn't create a Layout record in the database. You need a record for the module to associate pages with it and for component groups (like the nav) to have an owner.
Naming convention: the uiComponent value is derived from the filename — convert to PascalCase and prefix with Cwa + resource type. primary.vue → CwaLayoutPrimary. site-header.vue → CwaLayoutSiteHeader. The same convention applies to page templates and components.
Via the admin
Go to /_cwa/layouts → Create → set uiComponent to CwaLayoutPrimary. The record is created immediately.
Via fixtures
Add the layout to your fixture scaffold and it will be created automatically on every doctrine:fixtures:load:
// api/src/DataFixtures/AppScaffold.php
public function build(CwaFixtureBuilder $cwa): void
{
$cwa->layout('main', 'CwaLayoutPrimary');
$cwa->flush();
}
Fixtures are the recommended approach for development — they give you a consistent, repeatable starting state, which is invaluable when switching feature branches or onboarding a new environment for a client. See Data Fixtures for the full scaffold API.
Next: Create a Page Template
With a layout in place, create a page template to define the content regions for individual pages.