The same component, four ways
Delaying an unmount
Presence needs the element to stay in the DOM until its closed transition finishes. Keeping it mounted is the simplest answer; when you genuinely must remove it, delay past the closed duration.The closed duration is the open duration × 0.7 by default. For
tm-presence-slide-block that is 240 × 0.7 = 168ms, so a 240ms timeout is
safely past it. Set --tm-exit-duration if you would rather pin an exact number
on both sides.Headless UI libraries
Radix UI, Base UI, Ark and Melt already putdata-state="open" | "closed" on
their content parts and keep them mounted for the duration of the exit. The
class is the entire integration — no timeout, no wrapper, no forceMount.
tm-menu and tm-tooltip also read data-side, which these libraries emit, and
map it to a transform origin so the panel grows out of its trigger rather than
its own middle. Passing the library’s resolved origin through --tm-origin
handles alignment as well.
For Headless UI, apply the presence class and drive
data-state yourself from
its render-prop state, or use its <Transition> classes directly.
TypeScript
TailMotionVars types every custom property TailMotion reads, so an inline
style object autocompletes and a typo fails the build:
TailMotionProfile and TailMotionState are useful for props:
Server rendering
Nothing here needs hydration. An element rendered on the server withdata-state="open" animates in on first paint via @starting-style, with no
flash and no useEffect.
The one thing to avoid is rendering a presence element with no state
attribute and adding the real state after hydration — the element is treated as
open and visible in the server HTML, then jumps closed. Render the real state on
the server.