headlessui: [Bug]: Transition applied on content instead of Disclosure panel

What package within Headless UI are you using?

@headlessui/react

What version of that package are you using?

v1.4.2

What browser are you using?

Chrome (Tested on Edge and Firefox)

Reproduction URL

https://codesandbox.io/s/headlessui-disclosure-transition-issue-6ogxj?file=/src/App.tsx

Describe your issue

When i use Transition component on Disclosure Panel, the transition is applied on content of panel instead of panel himself. I tried to use only documentation exemple, outside my project, but the exemple on documentation was already broken 🤔

(Note: I have volontarily increase the duration of transition to expect the beahavor) (Note 2: I tried to use unmount prop already, but seems to component was systematicaly unmounted regardless this prop)

About this issue

  • Original URL
  • State: closed
  • Created 3 years ago
  • Reactions: 7
  • Comments: 16

Most upvoted comments

Another workaround is to use the Disclosure component but manage its state manually using static, and then transition the height (or max-height to workaround the transition to auto):

<Disclosure>
  {({ open }) => (
    <>
      <Disclosure.Button>button</Disclosure.Button>
      <Transition
        show={open}
        className='overflow-hidden'
        enter='transition transition-[max-height] duration-200 ease-in'
        enterFrom='transform max-h-0'
        enterTo='transform max-h-screen'
        leave='transition transition-[max-height] duration-400 ease-out'
        leaveFrom='transform max-h-screen'
        leaveTo='transform max-h-0'
      >
        <Disclosure.Panel static>content</Disclosure.Panel>
      </Transition>
    </>
  )}
</Disclosure>

@RobinMalfait What about the behaviour I described in my comment? the sandbox example you provided also shows it. When you expand the Disclosure, the panel appears instantly, or at least everything shifts down instantly and the contents emerge from 0 opacity gradually (based on transition time function and duration). This produces ugly jump of a page content below the disclosure which the Transition meant to avoid at the first place.

Hey! Thank you for your bug report! Much appreciated! 🙏

The transition is applied onto the Disclosure.Panel, I applied a background color to the panel that showcases this: https://codesandbox.io/s/headlessui-disclosure-transition-issue-forked-hzww3?file=/src/App.tsx

The issue with your example is that the purple background color is not coming from the Disclosure.Panel, but it is coming from the Disclosure itself. This means that if you move the background color from the Disclosure to the Disclosure.Panel then your problem goes away: https://codesandbox.io/s/headlessui-disclosure-transition-issue-forked-kdtd9?file=/src/App.tsx

Hi @benderillo, I am having exactly the same inconvenience as you. Basically the behavior of the disclosure makes the content that is located at the bottom jump in a jerky way and I am looking for this to be nice, unfortunately it is not built in tailwindui either. Did you find a solution to the problem?

Yes, I found the solution! The solution is to DO NOT USE headlessui/disclosure component. I abandoned use of the component and just implemented my own super simplistic disclosure using the https://www.npmjs.com/package/react-animate-height package.

Here is my source code, it works for my needs, albeit is very simplistic:

import React, { useState } from 'react';
import AnimateHeight from 'react-animate-height';

type Props = {
  buttonContent: (open: boolean) => JSX.Element;
  children: JSX.Element | Array<JSX.Element>;
  initialOpen?: boolean;
};
export function Disclose({ buttonContent, children, initialOpen = false }: Props) {
  const [open, setOpen] = useState(initialOpen);

  return (
    <div className='relative'>
      <button className='accordion_button w-full' onClick={() => setOpen(!open)}>
        {buttonContent(open)}
      </button>
      <AnimateHeight id={'sliding_wrapper'} duration={300} height={open ? 'auto' : 0}>
        {children}
      </AnimateHeight>
    </div>
  );
}

Is there a correct way to do this with headlessui?

Is anyone else still getting this issue?

Hey! Thank you for your bug report! Much appreciated! pray

The transition is applied onto the Disclosure.Panel, I applied a background color to the panel that showcases this: https://codesandbox.io/s/headlessui-disclosure-transition-issue-forked-hzww3?file=/src/App.tsx

The issue with your example is that the purple background color is not coming from the Disclosure.Panel, but it is coming from the Disclosure itself. This means that if you move the background color from the Disclosure to the Disclosure.Panel then your problem goes away: https://codesandbox.io/s/headlessui-disclosure-transition-issue-forked-kdtd9?file=/src/App.tsx

This issue should be reopen and fixed. All previous comments are valid: the component has a wrong and wired behavior when animating the Disclosure.Panel height.

Having the same behaviour. Pure example from the documentation doesn’t work as explained. Expectation is that the entire panel will transition but the result is very weird transition: the panel appears instantly and the content transitions within the duration time. Can somebody from headlessui team comment on this?

Hey! Thank you for your bug report! Much appreciated! 🙏

The transition is applied onto the Disclosure.Panel, I applied a background color to the panel that showcases this: https://codesandbox.io/s/headlessui-disclosure-transition-issue-forked-hzww3?file=/src/App.tsx

The issue with your example is that the purple background color is not coming from the Disclosure.Panel, but it is coming from the Disclosure itself. This means that if you move the background color from the Disclosure to the Disclosure.Panel then your problem goes away: https://codesandbox.io/s/headlessui-disclosure-transition-issue-forked-kdtd9?file=/src/App.tsx

Hey! Thank you for your bug report! Much appreciated! 🙏

The transition is applied onto the Disclosure.Panel, I applied a background color to the panel that showcases this: https://codesandbox.io/s/headlessui-disclosure-transition-issue-forked-hzww3?file=/src/App.tsx

The issue with your example is that the purple background color is not coming from the Disclosure.Panel, but it is coming from the Disclosure itself. This means that if you move the background color from the Disclosure to the Disclosure.Panel then your problem goes away: https://codesandbox.io/s/headlessui-disclosure-transition-issue-forked-kdtd9?file=/src/App.tsx

How are you marking this as completed man common don’t be lazy

export function PortalAccordionPanel({ title, text }: { title: string; text: string }) {
  const ref = useRef<HTMLDivElement>(null);
  return (
    <Disclosure>
      {({ open }) => (
        <>
          <Disclosure.Button
            className={`border-t border-portal-black/15 first-of-type:border-t-0 flex w-full justify-between items-center bg-white pt-6 pb-6 font-semibold text-base text-portal-black`}
          >
            <span>{title}</span>
            <ExpandMore
              className={`${
                open ? 'rotate-180 transform transition duration-300' : ''
              } text-3xl text-portal-black opacity-15`}
            />
          </Disclosure.Button>
          <Transition
            ref={ref}
            show={open}
            beforeEnter={() => {
              ref.current &&
                ref.current.style.setProperty(`max-height`, `${ref.current.scrollHeight}px`);
            }}
            beforeLeave={() => {
              ref.current && ref.current.style.setProperty(`max-height`, `0px`);
            }}
            className="transition-all duration-300"
            enterFrom="transform opacity-0 max-h-[0px]"
            enterTo="transform opacity-100"
            leaveFrom="transform opacity-50"
            leaveTo="transform opacity-0"
          >
            <Disclosure.Panel className="font-base text-portal-black -mt-4 pb-6">
              {text}
            </Disclosure.Panel>
          </Transition>
        </>
      )}
    </Disclosure>
  );
}

Another workaround is to use the Disclosure component but manage its state manually using static, and then transition the height (or max-height to workaround the transition to auto):

<Disclosure>
  {({ open }) => (
    <>
      <Disclosure.Button>button</Disclosure.Button>
      <Transition
        show={open}
        className='overflow-hidden'
        enter='transition transition-[max-height] duration-200 ease-in'
        enterFrom='transform max-h-0'
        enterTo='transform max-h-screen'
        leave='transition transition-[max-height] duration-400 ease-out'
        leaveFrom='transform max-h-screen'
        leaveTo='transform max-h-0'
      >
        <Disclosure.Panel static>content</Disclosure.Panel>
      </Transition>
    </>
  )}
</Disclosure>

In this example, if the height of the panel is greater than the screen, the panel will be truncated, I don’t think that’s a safe use case.

@hodbn this way you can’t use transitions other than max-height (opacity, scale etc), transition-[max-height] overwrites transition-property set by transition. Can be fixed globally https://tailwindcss.com/docs/transition-property#customizing-your-theme

Actually I couldn’t just add height and max-height to default transition so ended up using transition-all instead of transition-[max-height] Another option is using an arbitrary value like [transition-property: max-height opacity]

Having the same problem myself on a custom drawer component