Disclosure defaultOpen transitions from closed to open causing wrong animation
What package within Headless UI are you using?
@headlessui/react v2.2.2 tailwindcss v4.1
What browser are you using?
Chrome, Safari, and Firefox
Reproduction URL
https://codesandbox.io/p/sandbox/github/jacogasp/headless-ui-disclosure-animation
Describe your issue
Hello,
I'm using the Disclosure component to implement a simple drawer with animation provided by tailwindcss v4.
The animation is handled by the data-[closed] attribute, e.g:
<Disclosure defaultOpen>
<DisclosureButton>Open/Close</DisclosureButton>
<DisclosurePanel
as="aside"
transition
className=easy-in-out fixed inset-0 z-20 mt-16 w-80 duration-100 data-[closed]:-translate-x-full"
>
...
</DisclosurePanel>
<Dialog>
The problem is that, nevertheless the disclosure should be already open, the animation plays when landing on the page, probably because the component starts with the attribute data-closed reaching the data-open attribute after the initial render.
The expected behavior is that the animation does not play at all until I click the disclosure button to close the drawer.
As dirty workaround I set a timeout to enable a boolean state which changes the className after some delay
e.g.
function Drawer() {
const isMounted = useRef(false);
const [animationEnabled, setAnimationEnabled] = useState(false);
useEffect(() => {
if (!isMounted.current) {
setTimeout(() => setAnimationEnabled(true), 500);
isMounted.current = true;
}
}, []);
return (
<Disclosure defaultOpen>
<DisclosureButton>Open/Close</DisclosureButton>
<DisclosurePanel
as="aside"
transition
className={`fixed inset-0 z-20 mt-16 w-80 ${animationEnabled ? "ease-in-out duration-100 data-[closed]:-translate-x-full" : ""}`}
>
...
</DisclosurePanel>
<Dialog>
)
}
I tried the same trick toggling the transition={animationEnabled} attribute instead of altering the className string, but I got weird glitches.
A much better approach is to remove the transition property and embed the <DisclosurePanel> inside a <Transition> component
<Disclosure defaultOpen>
<DisclosureButton>Open/Close</DisclosureButton>
<Transition>
<DisclosurePanel
as="aside"
className="fixed inset-0 z-20 mt-16 w-80 ease-in-out duration-100 data-[closed]:-translate-x-full"
>
...
</DisclosurePanel>
</Transition>
<Dialog>
Your last solution is completely fine. Under the hood they use the same transition helpers and expose the same data attributes so your last code snippet works totally fine.
Why it works:
The <Transition> component by default doesn't transition on initial load, if you want that you can use the appear={true} prop (but you don't want this behavior).
If you use the transition prop, then we assume that you always want to transition. If you don't you can use transition={false}. The problem is that you do want to transition after the initial page load, so you could mimic that with:
+ import { useEffect, useState } from 'react'
import { Disclosure, DisclosureButton, DisclosurePanel } from '@headlessui/react'
export default function Drawer() {
+ let [shouldTransition, setShouldTransition] = useState(false)
+ useEffect(() => setShouldTransition(true), [])
return (
<Disclosure defaultOpen>
<header className="fixed inset-0 h-16 bg-slate-200">
<DisclosureButton className="rounded bg-slate-400 p-2 hover:bg-slate-300">
Open/Close
</DisclosureButton>
<DisclosurePanel
- transition
+ transition={shouldTransition}
className="duration-2000 fixed inset-0 mt-16 w-80 bg-slate-200 ease-in-out data-[closed]:-translate-x-full data-[closed]:bg-red-300"
>
Hello
</DisclosurePanel>
</header>
</Disclosure>
)
}
But that's... a lot. So what you have is totally fine 👍
Going to close this issue since this is not really a bug, just different expectations around behavior.