---
title: "Slots"
description: "Split a component into named parts, style them with variants, and share classes through compound slots."
canonical_url: "https://vinicunca.dev/unocss-variants/concepts/slots"
---
# Slots

> Split a component into named parts, style them with variants, and share classes through compound slots.

## Basic usage

Set `slots` to an object of part names. The recipe then returns **functions**, not a string. Always include the parts you render — on the published 1.5.0 build, `slots: {}` does **not** switch into slot mode (it still returns a string).

An implicit `base` slot is added when you pass a non-empty `slots` object, and `base` on the options object becomes that slot's classes.

```vue
<template>
  <uv-slot-card />
</template>
```

```vue
<script 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 color-sky-500 dark:color-sky-400',
    role: 'text-sm color-slate-700 dark:color-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 lets you reuse recipes instead of copying class strings, and keeps every variant typed.
        </p>
      </blockquote>
      <div :class="infoWrapper()">
        <div :class="name()">
          Ketut Gabler
        </div>
        <div :class="role()">
          Full-stack developer
        </div>
      </div>
    </div>
  </figure>
</template>
```

## Slots with variants

A variant value can be a string (applied to the `base` slot) or a map of slot names.

```vue
<template>
  <uv-slot-product />
</template>
```

> [!NOTE]
> 
> Each slot function already includes the variant classes for that part. You do not pick slots yourself.

```ts
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',
    buyButton: 'text-xs sm:text-sm px-4 h-10 rounded-lg shadow-lg uppercase font-semibold tracking-wider color-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',
        addToBagButton: 'color-blue-500 border-blue-500'
      },
      secondary: {
        buyButton: 'bg-purple-500 shadow-purple-500/50',
        addToBagButton: 'color-purple-500 border-purple-500'
      }
    }
  }
});

const classes = item({ color: 'primary' });
classes.buyButton();
```

Call the recipe again when the selected variant changes (in Vue, wrap that call in `computed`).

## Slots with compound variants

Pass a **slot map** as `class` / `className` on a compound variant to target individual parts.

```vue
<template>
  <uv-slot-alert />
</template>
```

```ts
const alert = 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: 'filled',
      severity: 'success',
      class: {
        root: 'bg-green-100 dark:bg-green-800',
        title: 'color-green-900 dark:color-green-50',
        message: 'color-green-700 dark:color-green-200'
      }
    }
    // other combinations...
  ],
  defaultVariants: {
    variant: 'filled',
    severity: 'success'
  }
});
```

## Compound slots

`compoundSlots` applies the same classes to several slots. Omit extra variant keys to always apply the classes; add keys to apply them only when those variants match.

```vue
<template>
  <uv-pagination />
</template>
```

```ts
const pagination = uv({
  slots: {
    base: 'relative flex max-w-fit flex-wrap gap-1',
    item: 'data-[active="true"]:bg-blue-500 data-[active="true"]:color-white',
    prev: '',
    next: ''
  },
  variants: {
    size: {
      xs: {},
      sm: {},
      md: {}
    }
  },
  defaultVariants: {
    size: 'md'
  },
  compoundSlots: [
    {
      slots: ['item', 'prev', 'next'],
      class: [
        'flex',
        'items-center',
        'justify-center',
        'rounded-lg',
        'bg-neutral-200',
        'color-neutral-800'
      ]
    },
    {
      slots: ['item', 'prev', 'next'],
      size: 'md',
      class: 'h-9 w-9 text-base'
    }
  ]
});
```

You can do the same with per-slot variant maps. Compound slots just avoid repeating the shared list:

```ts
const shared = [
  'flex',
  'items-center',
  'justify-center',
  'rounded-lg',
  'bg-neutral-200'
];

const pagination = uv({
  slots: {
    base: 'relative flex max-w-fit flex-wrap gap-1',
    item: [...shared, 'data-[active="true"]:bg-blue-500 data-[active="true"]:color-white'],
    prev: shared,
    next: shared
  },
  variants: {
    size: {
      md: {
        item: 'h-9 w-9 text-base',
        prev: 'h-9 w-9 text-base',
        next: 'h-9 w-9 text-base'
      }
    }
  }
});
```

## Slot-level variant overrides

Each slot function accepts the same props as the root call. Use that to override a variant for one part only (for example a selected tab):

```vue
<script setup>
import { uv } from 'unocss-variants';

const card = uv({
  slots: {
    base: 'flex gap-2',
    tab: 'rounded'
  },
  variants: {
    color: {
      primary: {
        tab: 'color-blue-500 dark:color-blue-400'
      },
      secondary: {
        tab: 'color-purple-500 dark:color-purple-400'
      }
    },
    isSelected: {
      true: {
        tab: 'font-bold'
      }
    }
  }
});

const { base, tab } = card({ color: 'primary' });
</script>

<template>
  <div :class="base()">
    <button
      v-for="item in items"
      :key="item.id"
      :class="tab({ isSelected: item.isSelected })"
    >
      {{ item.label }}
    </button>
  </div>
</template>
```

```ts
tab();
// rounded color-blue-500 dark:color-blue-400

tab({ isSelected: true });
// rounded color-blue-500 dark:color-blue-400 font-bold
```

Pass `class` / `className` to the **slot function**, not to the root call. A root-level `class` on a slotted recipe is not applied to `base()` on the published 1.5.0 build:

```ts
const slots = card({ class: 'max-w-md' });
slots.base(); // still only "flex gap-2"
slots.base({ class: 'max-w-md' }); // "flex gap-2 max-w-md"
```


## Sitemap

See the full [sitemap](https://vinicunca.dev/sitemap.md) for all pages.
