TypeScript
UnoCSS Variants is written in TypeScript. Variant keys and values infer from the object you pass to uv — no codegen.
VariantProps
Extract the props a recipe accepts. class and className are omitted so you can add your own host-prop type.
import type { VariantProps } from 'unocss-variants';
import { uv } from 'unocss-variants';
export const button = uv({
base: 'px-4 py-1.5 rounded-full hover:opacity-80',
variants: {
color: {
primary: 'bg-blue-500 color-white',
neutral: 'bg-zinc-500 color-black dark:color-white'
},
flat: {
true: 'bg-transparent'
}
},
defaultVariants: {
color: 'primary'
},
compoundVariants: [
{
color: 'primary',
flat: true,
class: 'bg-blue-500/40'
},
{
color: 'neutral',
flat: true,
class: 'bg-zinc-500/20'
}
]
});
type ButtonVariants = VariantProps<typeof button>;
// {
// color?: 'primary' | 'neutral'
// flat?: boolean
// }
Keys present in defaultVariants stay optional. Boolean axes (true / false keys) infer as boolean.
In Vue, extend the type for your component props:
interface ButtonProps extends ButtonVariants {
class?: string;
}
Required variants
There is no required: true flag. Use TypeScript utilities:
import type { VariantProps } from 'unocss-variants';
import { uv } from 'unocss-variants';
const buttonStyles = uv({
base: 'px-4 py-1.5 rounded-full hover:opacity-80',
variants: {
color: {
primary: 'bg-blue-500 color-white',
neutral: 'bg-zinc-500 color-black dark:color-white'
},
requiredFlat: {
true: 'bg-transparent',
false: 'bg-white'
}
},
defaultVariants: {
color: 'primary'
}
});
type ButtonVariants = VariantProps<typeof buttonStyles>;
export interface ButtonProps
extends Omit<ButtonVariants, 'requiredFlat'>,
Required<Pick<ButtonVariants, 'requiredFlat'>> {}
export function button(props: ButtonProps) {
return buttonStyles(props);
}
// @ts-expect-error missing requiredFlat
button({});
button({ requiredFlat: true });
Leaving an axis out of defaultVariants also keeps it required in practice at the type level if you model it that way — VariantProps still marks every variant key optional because the runtime accepts a missing key (it just skips that axis). Use Required<Pick<...>> when the UI must not render without a value.
Slotted return types
A recipe with slots returns an object of functions. ReturnType<typeof recipe> is that object:
const alert = uv({
slots: {
base: 'flex gap-3 rounded-lg p-4',
title: 'font-semibold',
description: 'text-sm'
},
variants: {
color: {
default: {
base: 'bg-zinc-100',
title: 'color-zinc-900'
},
danger: {
base: 'bg-red-50',
title: 'color-red-900'
}
}
}
});
type AlertSlots = ReturnType<typeof alert>;
// { base: (props?) => string; title: (props?) => string; description: (props?) => string }
function classesFor(color: VariantProps<typeof alert>['color']) {
const { base, title, description } = alert({ color });
return { base: base(), title: title(), description: description() };
}
The exported return type name is UvReturnType (not UVReturnType). It is a callable plus metadata: base, slots, variants, variantKeys, defaultVariants, compoundVariants, compoundSlots, and extend.
as const for external maps
When variant maps live outside uv, use as const so keys stay literal:
const colors = {
primary: 'bg-zinc-900 color-white',
secondary: 'bg-zinc-100 color-zinc-900'
} as const;
const button = uv({
variants: {
color: colors
}
});