CWA 2 is a prerelease (2.0.0-alpha), heading for a beta. It runs production sites; configuration can still change between releases, always with upgrade notes. View versions and support
Component

useCwaComponent

The recommended entry point for all CWA display components — fetch resource data, expose to the admin manager, and compose plugins for collections, uploaded files, or custom behaviour.

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

ParameterTypeDescription
propsIriPropThe raw defineProps<IriProp>() result. No toRef needed
pluginsCwaResourcePlugin[]Optional plugin factory results, such as withCollection() and withFile(). See Plugins
opsobjectThe 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)
ReturnTypePurpose
resourceComputedRef<CwaCurrentResourceInterface | undefined>Reactive resource ref — access data on .value?.data
exposeMetaobjectPass directly to defineExpose
$cwaCwaComposableAuth, resources, UI state
getCurrentStyleName(data) => string | undefinedRead the currently selected style name
uiClassNamesComputedRef<string[] | undefined>The active style classes — auto-applied to the root element by default
publishedIriComputedRef<string>The published IRI of this component, or its own IRI if it was never published. Never empty. See below
...pluginReturnsmerged objectAll 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.status says 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)
ReturnTypePurpose
collectionItemsComputedRef<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
isLoadingCollectionRef<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
totalPagesRef<number>Parsed from the page parameter of the collection's view.last link; 1 when there is no last-page link
pageModelRef<number>Current page number, bound to the URL's ?page= parameter. Set it to change page
goToNextPage() => voidMoves to the next page. Does nothing on the last page
goToPreviousPage() => voidMoves to the previous page. Does nothing on page 1
changePage(page: number) => voidJumps to a page. It doesn't check the page exists
resolveResourceLink(resource, property) => string | RouteLocationLink 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):

OptionDescription
filePropName of the PHP #[UploadableField] property holding the file, and the files map key (default: file)
imagineFilterNameImagine filter variant to surface as displayMedia/contentUrl (image-specific)
imageRefOptional 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()])