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.
.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
Pass your own name to use your own classes.
<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.
.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.
<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.
<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.
@media (prefers-reduced-motion: reduce) {
.mirror-select-popup-enter-active,
.mirror-select-popup-leave-active {
transition: none;
}
}
Further reading
- Styling: the state attributes an animation selects on.
- Accessibility: what else stays your job.