Writing blocks
Keys
Each key names a part of the component, and its value holds the classes
for that part. The template reads them from classes:
<template>
<button :class="classes.root">
<slot />
</button>
</template>
<tailwind lang="yaml">
root: inline-flex items-center gap-2 rounded-md px-4 py-2 text-sm
</tailwind>
lang can be yaml, yml or left out. Prefer yaml, because VS Code
highlights the block as YAML only then. A component holds one block.Lists
A list is joined into one class string, with each entry trimmed. Use one when the classes no longer fit on a line:
root:
- inline-flex items-center gap-2 rounded-md px-4 py-2 text-sm
- disabled:pointer-events-none disabled:opacity-50
- focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-sky-500
classes.root
// 'inline-flex items-center gap-2 rounded-md px-4 py-2 text-sm disabled:pointer-events-none disabled:opacity-50 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-sky-500'
Comments
A # after a space starts a comment, on its own line or after an entry:
root:
- inline-flex items-center gap-2 rounded-md px-4 py-2 text-sm
# without pointer-events-none, a disabled button still shows hover styles
- disabled:pointer-events-none disabled:opacity-50
A # written without a preceding space is treated as part of the class name.
This means arbitrary hex values like bg-[#1da1f2] or text-[#333] work
unquoted without being parsed as comments.
Groups
A key can hold other keys instead of classes, to any depth. The template picks an entry from each group with its props:
<script setup lang="ts">
defineProps<{
variant: 'solid' | 'outline'
size: 'sm' | 'lg'
disabled?: boolean
}>()
</script>
<template>
<button
:class="[
classes.root,
classes.variants[variant],
classes.sizes[size],
disabled && classes.states.disabled,
]"
>
<slot />
</button>
</template>
<tailwind lang="yaml">
root: inline-flex items-center gap-2 rounded-md text-sm
variants:
solid: bg-sky-600 text-white hover:bg-sky-500
outline: border border-zinc-300 hover:bg-zinc-50
sizes:
sm: h-8 px-3
lg: h-10 px-6
states:
disabled: pointer-events-none opacity-50
</tailwind>
root, variants, sizes, and states are arbitrary names you choose.
Name the groups after the visual parts or states of your component.
Reading classes in <script setup>
classes is available in <script setup> just like in the template. You
can pass it to child components or use it in computed properties:
<script setup lang="ts">
import { computed } from 'vue'
const props = defineProps<{ fullWidth?: boolean }>()
const buttonClasses = computed(() => [
classes.root,
props.fullWidth && classes.fullWidth,
])
</script>
Quoting
Classes need no quotes, even with characters YAML treats as syntax, such
as !, @, *, [ and &:
root:
- "!bg-red-500 *:rounded-md"
- @container/card:p-4
- [&>svg]:size-4 w-[calc(100%-1rem)]
- bg-[color:var(--brand)]/10
Quoted entries work too.
# ( #), such as
content-['hello # world']. Otherwise, YAML treats the # as the start
of a comment.Stick to standard key-value mappings and bulleted lists. Advanced YAML
features like flow lists ([a, b]) or anchors and aliases are treated as
plain text strings.
Errors
Every value must be a string, a list of strings or a group of keys. Anything else fails the build with an error that names the key:
variants:
solid:
outline: border border-zinc-300 hover:bg-zinc-50
Expected a class string, list or group at variants.solid, got null
YAML syntax errors and a lang other than yaml or yml fail the build
too.