Alternative UI Variants
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.multiple — false 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>
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).
Real Example: NavigationLink from the Playground
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.