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.
DraftGuides

Alternative UI Variants

Give the same component multiple visual styles — using style class names for CSS variation or ui/ Vue files for fully different templates.

The same component data can render in multiple visual ways. A NavigationLink might be a plain text link, a filled button, or a YouTube icon — all backed by the same label and url fields. An admin picks the variant from the manager panel.

There are two approaches. Use style classes for variations that are purely CSS differences. Use UI templates for variations that need fundamentally different markup.


Approach 1: Style Classes

Define named class sets in useCwaResource. An admin can select one of these styles from the manager panel.

<script setup lang="ts">
import { toRef, computed } from 'vue'
import type { IriProp } from '#cwa/composables/cwa-resource'
import { useCwaResource } from '#imports'

const props = defineProps<IriProp>()

const { getResource, exposeMeta, getCurrentStyleName } = useCwaResource(toRef(props, 'iri'), {
  styles: {
    multiple: false,
    classes: {
      'Default': [],
      'Filled Button': ['bg-blue-600 text-white px-6 py-2 rounded-md'],
      'Outlined Button': ['border border-blue-600 text-blue-600 px-6 py-2 rounded-md'],
    }
  }
})

const resource = getResource()
const currentStyleName = computed(() => {
  if (!resource.value?.data) return 'Default'
  return getCurrentStyleName(resource.value.data)
})

defineExpose(exposeMeta)
</script>

<template>
  <a :href="resource?.data?.url || '#'" :class="resource?.data?.uiClassNames">
    {{ resource?.data?.label }}
  </a>
</template>

How it works

styles.classes — a map of style names to arrays of Tailwind classes. The name (e.g. 'Filled Button') appears in the manager panel as an option. The classes are applied to uiClassNames on the component.

styles.multiplefalse means the admin picks one style. true allows multiple styles to be combined.

getCurrentStyleName(resource.value.data) — returns the currently applied style name (e.g. 'Filled Button'). Use this to drive conditional logic — different icons, different element types, different aria attributes.

resource?.data?.uiClassNames — the actual CSS classes that get applied. Use this on the element you want styled.

Conditional rendering based on style

For small variations — toggling an icon, changing an aria attribute, adjusting a minor class difference — using currentStyleName is fine:

<template>
  <!-- Show an arrow icon only on filled buttons -->
  <a :href="resource?.data?.url">
    {{ resource?.data?.label }}
    <ArrowIcon v-if="currentStyleName === 'Filled Button'" />
  </a>
</template>
Don't use currentStyleName to conditionally render fundamentally different HTML structure. If you find yourself showing completely different elements, different component hierarchies, or different semantic markup based on the style name, create a ui/ template instead. Style names can be renamed by the developer; ui/ filenames are stable identifiers. Conditional markup based on a name that could change is a maintenance hazard.

Approach 2: UI Templates (ui/ directory)

When a variant needs completely different HTML structure — not just different classes — create an alternative Vue file in a ui/ subdirectory.

The ui/ file

<!-- app/cwa/components/NavigationLink/ui/YouTube.vue -->
<template>
  <a :href="resource?.data?.url">
    <YouTubeIcon class="w-15 h-10" />
  </a>
</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'), {
  name: 'YouTube'   // must match the filename (without .vue)
})
const resource = getResource()
defineExpose(exposeMeta)
</script>

The name option in useCwaResource identifies this as the YouTube variant. When an admin selects YouTube in the manager panel, the module renders this file instead of NavigationLink.vue.

When to use ui/ vs styles.classes

Use a ui/ template when the variant needs different HTML structure — different elements, different child components, fundamentally different markup. The YouTube icon variant above is the classic case: it's not "NavigationLink but with different classes," it's a completely different thing that happens to share the same data.

Use styles.classes when it's the same HTML with different Tailwind classes, or minor conditional logic (toggling an icon, changing an aria label, a small class tweak).


The components-web-app playground shows both approaches in a single component:

NavigationLink.vue uses styles.classes with 'Filled', 'Outlined', and 'Tab Group' — each applying different classes and conditionally showing an icon.

NavigationLink/ui/YouTube.vue is an entirely different template that renders a YouTube logo instead of text.

An admin creating a nav region can choose: plain link, filled button, outlined button, tab, or YouTube icon — all backed by the same label + url data.