Speed
Slow motion multiplier
1×
Reduced motion
Show

Motion library compatibility

CSS-first
What a team that installs motion.dev runs into today, and the smallest set of changes that makes Astryx compose with it. Three blockers, in the order they bite.Decide here: The three blockers, and whether Button keeps its press transition.
Stay CSS-first and become explicitly compatible
Astryx should not ship Motion: ~34KB and a runtime on every consumer, to fix nothing the CSS path is failing at. But a consumer who installs it themselves should find that Astryx composes cleanly, and today three things stop that. Two of the three are already on the roadmap under other names.

1. Overlays cannot exit-animate

blocker
A consumer wraps a Dialog or a Popover in AnimatePresence, writes an exit variant, and sees nothing. The enter animation works, so the failure looks like their mistake.
CauseDialog is built on the native <dialog> element and every layer rides the Popover API. Both hard-remove the element from the top layer the moment it closes, so the exit animation runs against a box that is no longer painted. This is the same root cause as the CSS exit gap — one mechanism, failing two ways.
FixA keepMounted escape hatch: the consumer owns unmount, Astryx stops removing the element the frame the state flips. This is the pattern Motion documents for Base UI, so it is a shape consumers already recognise.
CostShares its fix with the presence work. Doing them separately means solving top-layer retention twice.
Same mechanism as the CSS exit gap: see it fail natively. One fix closes both, and doing them apart means solving top-layer retention twice.

2. CSS transitions on transform

blocker
Springs never settle and drags rubber-band. The consumer is writing a value every frame and Astryx is re-easing every one of those writes.
transition: transform — today
Press it, then run the drag. The CSS transition re-eases every transform the library writes, so the drag lags the pointer and a spring never settles.
transition: scale — the middle path
Press feedback on the independent scale property, so it no longer collides with the library’s transform writes. Both work — this is the middle path worth prototyping.
no transition — what the brief proposes
No press feedback at all. The library is happy, and every consumer who never installs one loses the affordance.
CauseMotion performs all layout animation by writing transform on each frame. A CSS transition on transform turns every one of those writes into the start of a new 175ms ease, so the element chases the value it was already given.
FixDrop transform from the transition list where it only carries press or chevron feedback, or move that feedback onto the independent scale property, which no longer collides with a library writing transform.
ComponentSiteDeclaration
Bannercore/Banner/Banner.tsx:340transitionProperty: 'transform'
BottomSheetcore/BottomSheet/BottomSheetPanel.tsx:144transitionProperty: 'transform, opacity'
Buttoncore/Button/Button.tsx:91transitionProperty: 'background-image, background-color, color, opacity, transform'
Chatcore/Chat/ChatDictationButton.tsx:70transitionProperty: 'transform, background-color'
Chatcore/Chat/ChatLayoutScrollButton.tsx:71transitionProperty: 'opacity, transform, max-width'
CodeBlockcore/CodeBlock/CodeBlock.tsx:207transitionProperty: 'transform'
Collapsiblecore/Collapsible/Collapsible.tsx:107transitionProperty: 'transform'
ComplexSelectorcore/ComplexSelector/ComplexSelector.tsx:128transitionProperty: 'background-image, background-color, color, opacity, transform'
ComplexSelectorcore/ComplexSelector/ComplexSelector.tsx:154transitionProperty: 'transform'
DateInputcore/DateInput/TouchDateField.tsx:371transitionProperty: 'transform'
Lightboxcore/Lightbox/Lightbox.tsx:185transitionProperty: 'transform'
MobileNavcore/MobileNav/MobileNav.tsx:157transitionProperty: 'transform'
MultiSelectorcore/MultiSelector/MultiSelector.tsx:187transitionProperty: 'transform'
MultiSelectorcore/MultiSelector/MultiSelector.tsx:208transitionProperty: 'background-image, background-color, color, opacity, transform'
Overlaycore/Overlay/OverlayScrim.tsx:58transitionProperty: 'opacity, visibility, transform'
Resizablecore/Resizable/ResizeHandle.tsx:194transitionProperty: 'opacity, background-color, transform'
Selectorcore/Selector/Selector.tsx:209transitionProperty: 'transform'
Selectorcore/Selector/Selector.tsx:230transitionProperty: 'background-image, background-color, color, opacity, transform'
SideNavcore/SideNav/SideNavCollapseButton.tsx:53transitionProperty: 'transform'
SideNavcore/SideNav/SideNavItem.tsx:140transitionProperty: 'transform'
Steppercore/Stepper/Step.tsx:367transitionProperty: 'transform'
Switchcore/Switch/Switch.tsx:288transitionProperty: 'transform, width, height'
TabListcore/TabList/TabMenu.tsx:165transitionProperty: 'transform'
Tablecore/Table/plugins/groupedRows/useTableGroupedRows.tsx:178transitionProperty: 'transform'
Tablecore/Table/plugins/rowExpansion/useTableRowExpansion.tsx:86transitionProperty: 'transform, color'
Tablecore/Table/plugins/tree/useTableTreeData.tsx:266transitionProperty: 'transform'
Toastcore/Toast/Toast.tsx:65transitionProperty: 'opacity, transform'
TopNavcore/TopNav/TopNavMegaMenu.tsx:114transitionProperty: 'transform'
TopNavcore/TopNav/TopNavMegaMenu.tsx:131transitionProperty: 'opacity, transform, overlay, display'
TopNavcore/TopNav/TopNavMegaMenu.tsx:222transitionProperty: 'transform'
TopNavcore/TopNav/TopNavMenu.tsx:101transitionProperty: 'transform'
TopNavcore/TopNav/TopNavMenu.tsx:185transitionProperty: 'transform'
TreeListcore/TreeList/TreeListItem.tsx:219transitionProperty: 'transform'
Drawerlab/Drawer/Drawer.tsx:157transitionProperty: 'transform'
23 components, and the shape of the list is chevrons and menus rather than roots: Banner, BottomSheet, Button, Chat, CodeBlock, Collapsible, ComplexSelector, DateInput, Lightbox, MobileNav, MultiSelector, Overlay, Resizable, Selector, SideNav, Stepper, Switch, TabList, Table, Toast, TopNav, TreeList, Drawer. A consumer animating a chevron is rare; a consumer animating a Button is not, which is why the brief's framing mattered and why the correction changes the priority.
CostSmall and mechanical, and materially smaller than the brief budgets for.

3. No composition escape hatch

blocker
motion.create() needs an element to attach to. Without one, the consumer wraps the component root in a motion.div and animates a box around the component rather than the component.
CauseAstryx has deliberately refused polymorphism, so there is no asChild-style prop. Wrapping works for a root; it is unavailable for every compound part whose element Astryx owns internally — a DropdownMenu item, a Tab, a TableRow.
FixA composition prop, or a render-prop shape that fits the house conventions better. This is a design review rather than a fix, and it is the one blocker that cannot be closed inside the motion project.
CostUnsized. It unblocks router links and drag-and-drop as well, so it should not be argued on motion alone.

Cheap wins

What moves Astryx from “wrappable with caveats” to Motion-friendly for everything except overlay exits. None of these needs the composition decision.
Add ref to the overlay components that lack itA component a library cannot get a DOM node out of cannot be animated by it at all. Everything else on this page is moot for those components.
Drop the transform transitionsCloses blocker 2 for every consumer, and removes a transition nobody outside a press interaction is asking for.34 sites across 23 components
Ship the JS token mirrorMotion takes seconds as a number and easing as a four-number array. It cannot resolve var(--duration-fast), so today a consumer either reads computed style or invents its own timing.Already a brief deliverable; the lab has it at /motion/js-mirror
Export a reduced-motion hookAn app with its own reduce-motion setting can flip Motion but cannot flip a CSS media query, so the two halves of one interface disagree. A shared hook is the only way the CSS half and the JS half can be told the same thing.Blocked on degrade-or-delete: the hook has to return a policy, not a boolean
The ref gap is the one number on this page the lab cannot check: the audit reads the built package’s styles, not its prop types, so the brief’s “eight overlay components” is unverified here.

What already works

Astryx is structurally better placed for this than most CSS-first systems, and the part that usually needs a rewrite is the part that is already right.
okRefs are React-19 native
No forwardRef ceremony for the components that pass one through.
okclassName and style pass through, consumer last
The consumer wins the merge, which is what a library needs to write to an element.
okNo runtime CSS-in-JS
Nothing is recomputing styles underneath a library that is writing them.
okStyleX loses the specificity contest by construction
Atomic classes versus inline styles: Motion wins every time, without a single !important. This is the compatibility that matters most and it already works.