You can add slots by using the slots key. There's no limit to how many slots you can add.
<script lang="ts" setup>
import { uv } from 'unocss-variants';
const card = uv({
slots: {
base: 'md:flex bg-background-muted rounded-xl p-8 md:p-0',
avatar: 'w-24 h-24 md:w-48 md:h-auto md:rounded-none rounded-full mx-auto drop-shadow-lg',
wrapper: 'flex-1 pt-6 md:p-8 text-center md:text-left space-y-4',
description: 'text-md font-medium',
infoWrapper: 'font-medium',
name: 'text-sm text-sky-500 dark:text-sky-400',
role: 'text-sm text-slate-700 dark:text-slate-500',
},
});
const { base, avatar, wrapper, description, infoWrapper, name, role } = card();
</script>
<template>
<figure :class="base()">
<img
src="/images/intro-avatar.png"
alt=""
:class="avatar()"
height="512"
width="385"
>
<div :class="wrapper()">
<blockquote>
<p :class="description()">
UnoCSS variants allows you to reduce repeated code in your
project and make it more readable. They fixed the headache of
building a design system with UnoCSS.
</p>
</blockquote>
<figcaption :class="infoWrapper()">
<div :class="name()">
Ketut Gabler
</div>
<div :class="role()">
Full-stack developer, Ngibur
</div>
</figcaption>
</div>
</figure>
</template>
You can also change the entire component and its slots by using the variants. See more about variants here.

<script lang="ts" setup>
import { PRadioGroup } from '#components';
import { uv } from 'unocss-variants';
import { computed, ref } from 'vue';
const color = ref('primary' as const);
const size = ref('xs');
const item = uv({
slots: {
base: 'flex flex-col mb-4 sm:flex-row p-6 bg-white dark:bg-stone-900 drop-shadow-xl rounded-xl',
imageWrapper: 'mb-6 flex-none h-48 w-full relative z-10 sm:(mb-0 h-auto w-48) before:(rounded-xl bg-[#18000E] h-full w-full content-empty left-0 top-0 absolute from-[#010187] bg-gradient-to-r)',
img: 'sm:scale-125 absolute z-10 top-2 sm:left-2 inset-0 w-full h-full object-cover rounded-lg',
title: 'relative w-full flex-none mb-2 text-2xl font-semibold text-stone-900 dark:text-white',
price: 'relative font-semibold text-xl dark:text-white',
previousPrice: 'relative line-through font-bold text-neutral-500 ml-3',
percentOff: 'relative font-bold text-green-500 ml-3',
sizeButton: 'cursor-pointer select-none relative font-semibold rounded-full w-10 h-10 flex items-center justify-center active:opacity-80 dark:text-white peer-checked:text-white',
buyButton: 'text-xs sm:text-sm px-4 h-10 rounded-lg shadow-lg uppercase font-semibold tracking-wider text-white active:opacity-80',
addToBagButton: 'text-xs sm:text-sm px-4 h-10 rounded-lg uppercase font-semibold tracking-wider border-2 active:opacity-80',
},
variants: {
color: {
primary: {
buyButton: 'bg-blue-500 shadow-blue-500/50',
sizeButton: 'peer-checked:bg-blue',
addToBagButton: 'text-blue-500 border-blue-500',
},
secondary: {
buyButton: 'bg-purple-500 shadow-purple-500/50',
sizeButton: 'peer-checked:bg-purple',
addToBagButton: 'text-purple-500 border-purple-500',
},
success: {
buyButton: 'bg-green-500 shadow-green-500/50',
sizeButton: 'peer-checked:bg-green',
addToBagButton: 'text-green-500 border-green-500',
},
},
},
});
const variant = computed(() =>
uv({
extend: uv(item),
})({
color: color.value,
}),
);
</script>
<template>
<div>
<div :class="variant.base()">
<div :class="variant.imageWrapper()">
<img
alt="Shoes for example"
:class="variant.img()"
loading="lazy"
src="/images/shoes-1.png"
>
</div>
<div class="pl-4 flex-auto sm:pl-8">
<div class="flex flex-wrap items-baseline relative">
<h1 :class="variant.title()">
Nike Adapt BB 2.0
</h1>
<div :class="variant.price()">
$279.97
</div>
<div :class="variant.previousPrice()">
$350
</div>
<div :class="variant.percentOff()">
20% off
</div>
</div>
<div class="my-4 flex items-baseline">
<div class="text-sm font-medium flex space-x-3">
<label
v-for="itemSize in ['xs', 's', 'm', 'l', 'xl']"
:key="itemSize"
>
<input
v-model="size"
:checked="size === itemSize"
class="peer sr-only"
name="size"
:value="itemSize"
type="radio"
>
<div :class="variant.sizeButton()">{{ itemSize.toUpperCase() }}</div>
</label>
</div>
</div>
<div class="flex space-x-4">
<button :class="variant.buyButton()">
Buy now
</button>
<button :class="variant.addToBagButton()">
Add to bag
</button>
</div>
</div>
</div>
<div class="flex flex-col gap-2">
<span class="text-slate-800 font-semibold dark:text-slate-200">
Select color:
</span>
<PRadioGroup
v-model="color"
:items="['primary', 'secondary', 'success']"
:pohon="{
label: 'capitalize',
}"
/>
</div>
</div>
</template>
Like variants, you can also define the styles for each slot when using compound
variants. The slots can be passed to class or className as an object.
<script lang="ts" setup>
import { PRadioGroup } from '#components';
import { uv } from 'unocss-variants';
import { computed, ref } from 'vue';
const severity = ref('success' as const);
const variant = ref('filled' as const);
const item = uv({
slots: {
root: 'rounded py-3 px-5 mb-4',
title: 'font-bold mb-1',
message: '',
},
variants: {
variant: {
outlined: {
root: 'border',
},
filled: {
root: '',
},
},
severity: {
error: '',
success: '',
},
},
compoundVariants: [
{
variant: 'outlined',
severity: 'error',
class: {
root: 'border-red-700 dark:border-red-500',
title: 'text-red-700 dark:text-red-500',
message: 'text-red-600 dark:text-red-500',
},
},
{
variant: 'outlined',
severity: 'success',
class: {
root: 'border-green-700 dark:border-green-500',
title: 'text-green-700 dark:text-green-500',
message: 'text-green-600 dark:text-green-500',
},
},
{
variant: 'filled',
severity: 'error',
class: {
root: 'bg-red-100 dark:bg-red-800',
title: 'text-red-900 dark:text-red-50',
message: 'text-red-700 dark:text-red-200',
},
},
{
variant: 'filled',
severity: 'success',
class: {
root: 'bg-green-100 dark:bg-green-800',
title: 'text-green-900 dark:text-green-50',
message: 'text-green-700 dark:text-green-200',
},
},
],
defaultVariants: {
variant: 'filled',
severity: 'success',
},
});
const alertUv = computed(() =>
uv({
extend: uv(item),
})({
severity: severity.value,
variant: variant.value,
}),
);
</script>
<template>
<div :class="alertUv.root()">
<div :class="alertUv.title()">
Oops, something went wrong
</div>
<div :class="alertUv.message()">
Something went wrong saving your changes. Try again later.
</div>
</div>
<div class="flex flex-col gap-2">
<span class="text-slate-800 font-semibold dark:text-slate-200">
Select severity:
</span>
<PRadioGroup
v-model="severity"
:items="['success', 'error']"
:pohon="{
label: 'capitalize',
}"
/>
<span class="text-slate-800 font-semibold dark:text-slate-200">
Select variant:
</span>
<PRadioGroup
v-model="variant"
:items="['filled', 'outlined']"
:pohon="{
label: 'capitalize',
}"
/>
</div>
</template>
Compound slots allow apply classes to multiple slots at once. This avoids having to repeat the same classes in multiple slots.
<script lang="ts" setup>
import { uv } from 'unocss-variants';
const pagination = uv({
slots: {
base: 'relative flex max-w-fit flex-wrap gap-1',
item: 'data-[active="true"]:bg-blue-500 data-[active="true"]:text-white',
prev: '',
next: '',
},
variants: {
size: {
xs: {},
sm: {},
md: {},
},
},
defaultVariants: {
size: 'md',
},
compoundSlots: [
// if you don't specify any variant, it will always be applied
{
slots: ['item', 'prev', 'next'],
class: [
'flex',
'flex-wrap',
'truncate',
'box-border',
'outline-none',
'items-center',
'justify-center',
'bg-neutral-200',
'rounded-lg',
'hover:bg-neutral-300',
'active:bg-neutral-400',
'text-neutral-800',
], // --> these classes will be applied to all slots
},
// if you specify a variant, it will only be applied if the variant is active
{
slots: ['item', 'prev', 'next'],
size: 'xs',
class: 'h-7 w-7 text-xs', // --> these classes will be applied to all slots if size is xs
},
{
slots: ['item', 'prev', 'next'],
size: 'sm',
class: 'h-8 w-8 text-sm', // --> these classes will be applied to all slots if size is sm
},
{
slots: ['item', 'prev', 'next'],
size: 'md',
class: 'h-9 w-9 text-base', // --> these classes will be applied to all slots if size is md
},
],
});
const { base, item, prev, next } = pagination();
</script>
<template>
<ul
aria-label="pagination navigation"
:class="base()"
>
<li>
<button
aria-label="Go to previous page"
:class="prev()"
data-disabled="true"
>
<svg
fill="none"
height="24"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
width="24"
>
<path d="m15 18-6-6 6-6" />
</svg>
</button>
</li>
<li>
<button
aria-label="page 1"
:class="item()"
>
1
</button>
</li>
<li>
<button
aria-label="page 2"
:class="item()"
>
2
</button>
</li>
<li>
<button
aria-label="page 3"
:class="item()"
data-active="true"
>
3
</button>
</li>
<li>
<button
aria-label="page 4"
:class="item()"
>
4
</button>
</li>
<li>
<button
aria-label="page 5"
:class="item()"
>
5
</button>
</li>
<li
aria-hidden="true"
:class="item()"
>
...
</li>
<li>
<button
aria-label="page 10"
:class="item()"
>
10
</button>
</li>
<li>
<button
aria-label="Go to next page"
:class="next()"
>
<svg
fill="none"
height="24"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
width="24"
xmlns="http://www.w3.org/2000/svg"
>
<path d="m9 18 6-6-6-6" />
</svg>
</button>
</li>
</ul>
</template>
import { uv } from "unocss-variants";
cosnt baseItem = [
"flex",
"flex-wrap",
"truncate",
"box-border",
"outline-none",
"items-center",
"justify-center",
"bg-neutral-100",
"hover:bg-neutral-200",
"active:bg-neutral-300",
"text-neutral-500",
]
const pagination = uv({
slots: {
base: "flex flex-wrap relative gap-1 max-w-fit",
item: [...baseItem, 'data-[active="true"]:bg-blue-500 data-[active="true"]:text-white'],
prev: baseItem,
next: baseItem,
},
variants: {
size: {
xs: {
item: "w-7 h-7 text-xs",
prev: "w-7 h-7 text-xs",
next: "w-7 h-7 text-xs",
},
sm: {
item: "w-8 h-8 text-sm",
prev: "w-8 h-8 text-sm",
next: "w-8 h-8 text-sm",
},
md: {
item: "w-9 h-9 text-base",
prev: "w-9 h-9 text-base",
next: "w-9 h-9 text-base",
},
},
},
defaultVariants: {
size: "md",
},
});
Some component libraries use class render props to allow customizing the class name of a component based on internal state. UnoCSS Variants supports slot level variant overrides to make it simple to override the selected variant(s) per slot as necessary.
<script setup>
import { uv } from 'unocss-variants';
const card = uv({
slots: {
base: 'flex gap-2',
tab: 'rounded'
},
variants: {
color: {
primary: {
tab: 'text-blue-500 dark:text-blue-400'
},
secondary: {
tab: 'text-purple-500 dark:text-purple-400'
}
},
isSelected: {
true: {
tab: 'font-bold'
}
}
}
});
const { base, tab } = card({ color: 'primary' });
</setup>
<template>
<Tabs :class="base()">
<Tab
v-for="item in items"
:key="item.id"
:class="tab({ isSelected: item.isSelected })"
:id="item.id"
>
{{ item.label }}
</Tab>
</Tabs>
</template>