Guide

Writing blocks

Lists, groups, comments, and the YAML a block accepts.

Keys

Each key names a part of the component, and its value holds the classes for that part. The template reads them from classes:

Button.vue
<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

Comments

A # after a space starts a comment, on its own line or after an entry:

Button.vue
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:

Button.vue
<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:

Button.vue
<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.

Quote an entry only if it contains a space before # ( #), 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

YAML syntax errors and a lang other than yaml or yml fail the build too.

Copyright © 2026