# Animation

Mirror parts hand you a transition name and leave the timing to your CSS. Read this for the names and for what stays in the DOM.

Parts that appear and disappear hand you a Vue transition *name*, and all the
timing lives in your CSS under that name, so a duration is never a prop.

```css
.mirror-select-popup-enter-active,
.mirror-select-popup-leave-active {
  transition: opacity 120ms ease-out;
}

.mirror-select-popup-enter-from,
.mirror-select-popup-leave-to {
  opacity: 0;
}
```

## The transition names

::docs-table
---
columns:
  - label: Part
  - label: Where it is set
  - label: Default
rows:
  - items:
      - label: Checkbox.Indicator
      - label: '`options.transition` on `Checkbox.Root`'
        plaintext: true
      - label: mirror-checkbox-indicator
  - items:
      - label: Radio.Indicator
      - label: 'The `transition` prop on `Radio.Indicator`'
        plaintext: true
      - label: mirror-radio-indicator
  - items:
      - label: Select.Content
      - label: '`options.transition.popup` on `Select.Root`'
        plaintext: true
      - label: mirror-select-popup
  - items:
      - label: Select.Content
      - label: '`options.transition.backdrop` on `Select.Root`'
        plaintext: true
      - label: mirror-select-backdrop
  - items:
      - label: Combobox.Content
      - label: '`options.transition.popup` on `Combobox.Root`'
        plaintext: true
      - label: mirror-combobox-popup
  - items:
      - label: Combobox.Content
      - label: '`options.transition.backdrop` on `Combobox.Root`'
        plaintext: true
      - label: mirror-combobox-backdrop
  - items:
      - label: Autocomplete.Content
      - label: '`options.transition.popup` on `Autocomplete.Root`'
        plaintext: true
      - label: mirror-autocomplete-popup
  - items:
      - label: Autocomplete.Content
      - label: '`options.transition.backdrop` on `Autocomplete.Root`'
        plaintext: true
      - label: mirror-autocomplete-backdrop
  - items:
      - label: Popover.Content
      - label: '`options.transition.popup` on `Popover.Root`'
        plaintext: true
      - label: none
        plaintext: true
  - items:
      - label: Popover.Content
      - label: '`options.transition.backdrop` on `Popover.Root`'
        plaintext: true
      - label: none
        plaintext: true
  - items:
      - label: Tooltip.Content
      - label: '`options.transition.popup` on `Tooltip.Root`'
        plaintext: true
      - label: mirror-tooltip-popup
  - items:
      - label: Tooltip.Content
      - label: '`options.transition.backdrop` on `Tooltip.Root`'
        plaintext: true
      - label: mirror-tooltip-backdrop
  - items:
      - label: PreviewCard.Content
      - label: '`options.transition.popup` on `PreviewCard.Root`'
        plaintext: true
      - label: none
        plaintext: true
  - items:
      - label: PreviewCard.Content
      - label: '`options.transition.backdrop` on `PreviewCard.Root`'
        plaintext: true
      - label: none
        plaintext: true
  - items:
      - label: Tabs.Content
      - label: 'The `transition` prop on `Tabs.Content`'
        plaintext: true
      - label: none
        plaintext: true
  - items:
      - label: ScrollArea.Scrollbar
      - label: 'The `transition` prop on `ScrollArea.Scrollbar`'
        plaintext: true
      - label: none
        plaintext: true
---
::

Pass your own name to use your own classes.

```vue
<template>
  <Select.Root :options="{ transition: { popup: 'app-popup' } }">
    <!-- … -->
  </Select.Root>
</template>
```

`Popover`, `PreviewCard`, `Tabs` and `ScrollArea` ship no default name, so those
popups appear and vanish, panels swap and scrollbars leave instantly until you
pass one.

## What unmounts

A part that is not present is removed from the DOM, so a checkbox indicator
exists only while the box is checked or indeterminate, a select popup only
while it is open, and `Field.Error` only while the field is invalid.

While they are mounted, popups and panels also write `data-state` as `open` or
`closed`, so a CSS animation can read the state directly instead of going
through a transition class.

```css
.app-popup[data-state='closed'] {
  animation: fade-out 120ms ease-in;
}
```

## Keeping a part mounted

`forceMount` keeps a part in the DOM permanently. Reach for it when something
outside Mirror has to measure the node, or when your animation library owns the
enter and leave itself.

```vue
<template>
  <Select.Content force-mount class="data-[state=closed]:pointer-events-none" />
</template>
```

Set `options.forceMount` on the root and it answers for every part under that
root at once. On `Select`, `Combobox` and `Autocomplete` the option is keyed by
part instead, so you write `options.forceMount.popup` rather than a plain
boolean.

Where a part also takes a `forceMount` prop, the prop wins whenever you set it,
`false` included, so one instance can stay unmounted under a root that keeps
everything else. Leave the prop off and the option answers. A part that appears
more than once under one root takes the prop alone, with no option behind it,
and defaults to `false`: `Field.Error`, `Radio.Indicator`, `Tabs.Content` and
`ScrollArea.Scrollbar`.

The parts that take it: `Radio.Indicator`, `Field.Error`, `Select.Content`,
`Combobox.Content`, `Autocomplete.Content`, `Popover.Content`,
`Tooltip.Content`, `PreviewCard.Content`, `Dialog.Popup`, `Dialog.Backdrop`,
`AlertDialog.Popup`, `AlertDialog.Backdrop`, `Collapsible.Content`,
`Tabs.Content`, `ScrollArea.Scrollbar`, `Select.ItemIndicator` and
`Combobox.ItemIndicator`. `Checkbox.Indicator` is not among them: one root has
one indicator, so `options.forceMount` on `Checkbox.Root` answers alone.

A kept panel also leaves the accessibility tree: an inactive kept panel is
rendered with the `hidden` attribute, while a panel that is currently leaving
is not, so its transition can finish.

```vue
<template>
  <Tabs.Content force-mount value="tokens">…</Tabs.Content>
</template>
```

## Respect reduced motion

Timing lives in your CSS, so the media query does too. Write it next to the
transition it switches off.

```css
@media (prefers-reduced-motion: reduce) {
  .mirror-select-popup-enter-active,
  .mirror-select-popup-leave-active {
    transition: none;
  }
}
```

## Further reading

- [Styling](/components/styling): the state attributes an animation selects on.
- [Accessibility](/overview/accessibility): what else stays your job.
