Components
What Is a Component?
A component is the smallest unit of managed content on a page. Every piece of visible content — a heading, a block of rich text, an image, a call-to-action button, a conference card — is a component.
Components are:
- Stored in the API as entities in your database (each type is a PHP class)
- Rendered by the Nuxt module using matching Vue files
- Edited inline in the browser by admins without leaving the page
A Component Has Two Sides
1. The PHP entity (the data)
#[Silverback\Publishable]
#[ApiResource(mercure: true)]
#[ORM\Entity]
class Title extends AbstractComponent
{
use PublishableTrait;
#[ORM\Column(nullable: true)]
public ?string $title = null;
}
AbstractComponent gives it identity (IRI, UUID) and the uiComponent field. You add your own data fields.
2. The Vue component (the display)
<!-- app/cwa/components/Title/Title.vue -->
<template>
<h1>{{ resource?.data?.title || 'No Title' }}</h1>
</template>
<script setup lang="ts">
import { toRef } from 'vue'
import type { IriProp } from '#cwa/composables/cwa-resource'
import { useCwaResource } from '#imports'
const props = defineProps<IriProp>()
const { getResource, exposeMeta } = useCwaResource(toRef(props, 'iri'))
const resource = getResource()
defineExpose(exposeMeta)
</script>
The module passes the component's IRI as a prop. useCwaResource fetches and subscribes to the resource. defineExpose(exposeMeta) wires the component into the admin manager.
The Admin Tab
An admin tab is a separate Vue file that renders inside the manager panel when this component is selected in edit mode.
<!-- app/cwa/components/Title/admin/Title.vue -->
<template>
<CwaUiFormLabelWrapper label="Title">
<CwaUiFormInput v-model="titleModel.model.value" />
</CwaUiFormLabelWrapper>
</template>
<script setup lang="ts">
import { useCwaResourceManagerTab, useCwaResourceModel } from '#imports'
const { exposeMeta, iri } = useCwaResourceManagerTab({ name: 'Title' })
const titleModel = useCwaResourceModel<string>(iri, 'title')
defineExpose(exposeMeta)
</script>
The v-model binding sends a debounced PATCH to the API every time the admin types. No save button needed.
Component Patterns
Looking at real-world usage, a few patterns come up constantly:
| Pattern | Example | What it needs |
|---|---|---|
| Text field | Title, TextBlock | useCwaResource + useCwaResourceModel |
| Image upload | Image, HeroSection | useCwaImageResource + useCwaResourceUpload |
| Rich text | HtmlContent | useCwaResource + TipTap editor in admin |
| Link | NavigationLink | Text + Route/URL fields |
| Parent + children | Accordion, Gallery | CwaComponentGroup inside the parent component |
| Collection listing | Blog list | Built-in Collection component |
Each of these is covered in detail in the Guides section with step-by-step instructions.
Alternative UI Variants
A component can have multiple visual representations. The same NavigationLink data might render as a plain text link, a filled button, or a YouTube icon depending on what an admin selects.
There are two ways to provide variants:
- Style classes — apply different Tailwind classes based on the selected
uiClassNames - UI directory — create
app/cwa/components/Title/ui/AlternativeName.vueas a full alternative template
Both approaches are covered in the Alternative UI Variants guide.
Publishing
Most components use the #[Silverback\Publishable] annotation. This means:
- Admins edit a draft version — the live site is unaffected
- Changes become live when the admin clicks Publish
- Mercure broadcasts the update so visitors see the new content in real-time
See Draft & Publish for details.