useCwaComponent
The recommended composable for all CWA display components. Wraps useCwaResource and adds a typed plugin system so collection, file, and custom behaviours can be composed together without nesting.
const props = defineProps<IriProp>()
const { resource, exposeMeta } = useCwaComponent(props)
defineExpose(exposeMeta)
Signature
useCwaComponent(
props: IriProp,
plugins?: CwaResourcePlugin[],
ops?: {
name?: string
styles?: {
multiple?: boolean
// each style's value is a class string (string[] accepted but deprecated)
classes: Record<string, string | string[]>
}
manager?: {
disabled?: boolean
}
autoClass?: boolean // default true — auto-applies uiClassNames to the root element
}
)
Parameters
| Parameter | Type | Description |
|---|---|---|
props | IriProp | The raw defineProps<IriProp>() result. No toRef needed |
plugins | CwaResourcePlugin[] | Optional plugin factory results, such as withCollection() and withFile(). See Plugins |
ops | object | The same options as useCwaResource: styles (see Style Variants), name (the UI variant's label), manager.disabled (opt out of the admin manager) and autoClass |
Return Values
const { resource, exposeMeta, $cwa, getCurrentStyleName, ...pluginReturns } = useCwaComponent(props)
| Return | Type | Purpose |
|---|---|---|
resource | ComputedRef<CwaCurrentResourceInterface | undefined> | Reactive resource ref — access data on .value?.data |
exposeMeta | object | Pass directly to defineExpose |
$cwa | CwaComposable | Auth, resources, UI state |
getCurrentStyleName | (data) => string | undefined | Read the currently selected style name |
uiClassNames | ComputedRef<string[] | undefined> | The active style classes — auto-applied to the root element by default |
publishedIri | ComputedRef<string> | The published IRI of this component, or its own IRI if it was never published. Never empty. See below |
...pluginReturns | merged object | All return values from provided plugins |
defineExpose(exposeMeta) is required — without it the admin manager cannot select this component.
publishedIri falls back to the component's own IRI because an unpublished draft is still a valid group location. You rarely need it: a nested <CwaComponentGroup> resolves its location to the published IRI itself, so pass it iri.
Example
<template>
<div v-if="resource?.data">
<h2>{{ resource.data.title }}</h2>
</div>
</template>
<script setup lang="ts">
import type { IriProp } from '#cwa/composables/cwa-resource'
import { useCwaComponent } from '#imports'
const props = defineProps<IriProp>()
const { resource, exposeMeta } = useCwaComponent(props)
defineExpose(exposeMeta)
</script>
resource.value states:
undefined— the store has no entry for this IRI yet- object without
data— the fetch is in progress or failed (apiState.statussays which) - object with
data— data available
Plugins
Plugins extend useCwaComponent with additional state and methods. Pass an array of plugin factory results as the second argument. Return values from all plugins are merged and destructured alongside the base returns.
withCollection()
For components backed by the built-in Collection API resource. Returns pagination state and navigation helpers.
import { useCwaComponent, withCollection } from '#imports'
const props = defineProps<IriProp>()
const {
resource,
exposeMeta,
collectionItems,
isLoadingCollection,
totalPages,
pageModel,
goToNextPage,
goToPreviousPage,
changePage,
resolveResourceLink,
} = useCwaComponent(props, [withCollection()])
defineExpose(exposeMeta)
| Return | Type | Purpose |
|---|---|---|
collectionItems | ComputedRef<CwaResource[] | undefined> | The response's collection.member array. Each item is a raw API resource, so read its fields directly (item.title, not item.data.title). undefined until the first response arrives |
isLoadingCollection | Ref<boolean> | true while a re-fetch after a query change is in progress. The first page comes with the component's own data, so this stays false for it |
totalPages | Ref<number> | Parsed from the page parameter of the collection's view.last link; 1 when there is no last-page link |
pageModel | Ref<number> | Current page number, bound to the URL's ?page= parameter. Set it to change page |
goToNextPage | () => void | Moves to the next page. Does nothing on the last page |
goToPreviousPage | () => void | Moves to the previous page. Does nothing on page 1 |
changePage | (page: number) => void | Jumps to a page. It doesn't check the page exists |
resolveResourceLink | (resource, property) => string | RouteLocation | Link target for a collection item. See Linking to a Resource's Page |
The three page-changing functions also scroll the window to the top. The collection re-fetches whenever the URL query changes, and returns to page 1 when anything other than page or perPage changes. Collections & Pagination shows a full component, numbered pagination and user-controlled sorting.
Replaces the deprecated useCwaCollectionResource.
withFile()
withFile(fileOps?) is for components backed by a PHP entity with #[Silverback\Uploadable]. It handles any uploadable file, not just images. Rather than flat return values, it exposes the field under a files map keyed by its fileProp (default 'file') — so files.file.contentUrl, files.file.displayMedia, etc. Map entries are reactive, so in templates you access them without .value.
import { useCwaComponent, withFile } from '#imports'
const props = defineProps<IriProp>()
const { resource, exposeMeta, files } = useCwaComponent(props, [withFile()])
defineExpose(exposeMeta)
// files.file.contentUrl · files.file.displayMedia · files.file.srcset · files.file.handleLoad · files.file.loaded
Options (fileOps):
| Option | Description |
|---|---|
fileProp | Name of the PHP #[UploadableField] property holding the file, and the files map key (default: file) |
imagineFilterName | Imagine filter variant to surface as displayMedia/contentUrl (image-specific) |
imageRef | Optional template ref for the <img>, to detect an image already loaded from cache. Never registered for you — see useCwaFileField |
Each entry in files exposes contentUrl, displayMedia (a MediaFile with width/height/mimeType), srcset (every sized variant, for a responsive <img>; see srcset), handleLoad, and loaded. See Images & Media for the full display pattern, and useCwaFileField for the standalone per-field variant.
If you rely on loaded, pass an imageRef typed useTemplateRef<unknown>(…); see Why imageRef exists.
Multiple File Fields
Use withFile() once per field, each with its own fileProp. useCwaComponent's plugin merge accumulates the files key rather than overwriting it, so the fields never collide:
const { files } = useCwaComponent(props, [
withFile({ fileProp: 'heroImage' }),
withFile({ fileProp: 'thumbnail', imagineFilterName: 'small' }),
])
// files.heroImage.contentUrl · files.thumbnail.contentUrl
Combining Plugins
Multiple plugins can be composed in a single call:
const { resource, exposeMeta, collectionItems, files } = useCwaComponent(
props,
[withCollection(), withFile()]
)
defineExpose(exposeMeta)
Style Variants
const { resource, exposeMeta, getCurrentStyleName } = useCwaComponent(props, [], {
styles: {
multiple: false,
classes: {
'Filled': 'bg-blue-600 text-white px-6 py-2 rounded-md',
'Outlined': 'border border-blue-600 text-blue-600 px-6 py-2',
}
}
})
const currentStyleName = computed(() => {
if (!resource.value?.data) return undefined
return getCurrentStyleName(resource.value.data)
})
Each style's value is a class string. (A string[] is still accepted and joined, but is deprecated — prefer a plain string.) The style names appear in the admin "Style" selector. The selected classes are automatically applied to the component's root element — no :class binding needed.
Allowing Several Styles at Once
Set multiple: true to let the author tick more than one style in the admin Style dropdown (it becomes a multi-select; single mode stays a normal dropdown). The chosen classes are combined onto the element:
const { resource, exposeMeta } = useCwaComponent(props, [], {
styles: {
multiple: true,
classes: {
Bordered: 'border border-gray-200',
Rounded: 'rounded',
},
},
})
The resource's flat uiClassNames: string[] stores one entry per selected style — that style's class string — for both single and multiple select. A single selection is a one-element array (e.g. ['border border-gray-200']); a multiple selection is an array of N (picking both above saves ['border border-gray-200', 'rounded']).
Applying Classes to an Inner Element
If you want the style classes on an inner element rather than the root, opt out of auto-apply and bind uiClassNames manually:
const { resource, exposeMeta, uiClassNames } = useCwaComponent(props, [], {
autoClass: false,
styles: { ... }
})
<template>
<div>
<a :class="uiClassNames">{{ resource?.data?.label }}</a>
</div>
</template>
Writing a Custom Plugin
A plugin is a function that receives { iri, resource, $cwa } and returns any object:
import type { CwaResourcePlugin } from '#cwa/composables/cwa-component'
const withWordCount = (): CwaResourcePlugin<{ wordCount: ComputedRef<number> }> => {
return ({ resource }) => {
const wordCount = computed(() => {
const body = resource.value?.data?.body ?? ''
return body.split(/\s+/).filter(Boolean).length
})
return { wordCount }
}
}
// Usage
const { resource, exposeMeta, wordCount } = useCwaComponent(props, [withWordCount()])