Inspired by Stitches they are a great way to create a consistent design system, so we created UnoCSS Variants to bring them to UnoCSS.
<script setup>
import { uv } from 'unocss-variants';
const button = uv({
base: 'font-medium bg-blue-500 text-white rounded-full active:opacity-80',
variants: {
color: {
primary: 'bg-blue-500 text-white',
secondary: 'bg-purple-500 text-white'
},
size: {
sm: 'text-sm',
md: 'text-base',
lg: 'px-4 py-3 text-lg'
}
},
compoundVariants: [
{
size: ['sm', 'md'],
class: 'px-3 py-1'
}
],
defaultVariants: {
size: 'md',
color: 'primary'
}
});
</script>
<template>
<button :class="button({ size: 'sm', color: 'secondary' })">
Click me
</button>
</template>
To learn more about variants, check the variants page.
You can style multiple components at once using the slots property.
<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>
To learn more about slots and how to use them, check out the Slots page.
UnoCSS Variants provides a class / className prop for overriding classes on any component.
import { uv } from 'unocss-variants';
const button = uv({
base: 'font-semibold text-white py-1 px-3 rounded-full active:opacity-80',
variants: {
color: {
primary: 'bg-blue-500 hover:bg-blue-700',
secondary: 'bg-purple-500 hover:bg-purple-700',
success: 'bg-green-500 hover:bg-green-700',
error: 'bg-red-500 hover:bg-red-700'
}
}
});
button({
color: 'secondary',
class: 'bg-pink-500 hover:bg-pink-500' // overrides the color variant
});
/**
* Result:
* font-semibold text-white py-1 px-3 rounded-full active:opacity-80 bg-pink-500 hover:bg-pink-500
*/
To learn more the overrides, check out this page.
UnoCSS Variants allows you to compose components using the extend parameter. It automatically merges the classes, slots, variants, defaultVariants and compoundVariants of the extended component.
<script lang="ts" setup>
import { uv } from 'unocss-variants';
const baseButton = uv({
base: [
'font-semibold',
'dark:text-white',
'py-1',
'px-3',
'rounded-full',
'active:opacity-80',
'bg-zinc-100',
'hover:bg-zinc-200',
'dark:bg-zinc-800',
'dark:hover:bg-zinc-800',
],
});
const buyButton = uv({
extend: baseButton,
base: [
'text-sm',
'text-white',
'rounded-lg',
'shadow-lg',
'uppercase',
'tracking-wider',
'uno-layer-uv:bg-blue-500',
'uno-layer-uv:hover:bg-blue-600',
'shadow-blue-500/50',
'uno-layer-uv:dark:bg-blue-500',
'uno-layer-uv:dark:hover:bg-blue-600',
],
});
</script>
<template>
<div class="flex gap-3">
<button :class="baseButton()">
Button
</button>
<button :class="buyButton()">
Buy button
</button>
</div>
</template>
/**
* buyButton();
*
* Result:
* font-semibold dark:text-white py-1 px-3 rounded-full active:opacity-80 bg-zinc-100 hover:bg-zinc-200 dark:bg-zinc-800 dark:hover:bg-zinc-800 text-sm text-white rounded-lg shadow-lg uppercase tracking-wider uno-layer-uv:bg-blue-500 uno-layer-uv:hover:bg-blue-600 shadow-blue-500/50 uno-layer-uv:dark:bg-blue-500 uno-layer-uv:dark:hover:bg-blue-600
*/
To learn more about Components composition, check out this page.
UnoCSS Variants is built with developer experience in mind, it provides a great autocomplete experience thanks to the fully-typed API, so when using TypeScript, slots, values, and breakpoints will be auto-completed for you.
UnoCSS Variants does not include built-in conflict resolution, so you must manage class conflicts manually. While tools like tailwind-merge provide automatic resolution, relying on them can obscure which classes are actually applied, potentially leading to unexpected results. Since we want to keep the philosophy of CSS which is "cascading" and "specificity", we believe that managing conflicts manually is a better approach.
UnoCSS Variants is a fork of tailwind variants, all credits go to the original authors.