Skip to content

Transition enterFrom classes not working as expected #1503

@bentefay

Description

@bentefay

What package within Headless UI are you using?

@headlessui/react

What version of that package are you using?

1.6.3

What browser are you using?

Chrome/Firefox

Reproduction URL

https://github.com/tailwindlabs/headlessui/blob/main/packages/playground-react/pages/transitions/component-examples/peek-a-boo.tsx

https://github.com/tailwindlabs/headlessui/blob/main/packages/playground-react/pages/transitions/component-examples/modal.tsx

https://headlessui.dev/react/transition

Describe your issue

Many of the examples for Transition in playground-react, the headlessui documentation and Tailwind UI apply transition in the className or enter attributes to achieve an enter effect. Some of these do not seem to be working as intended.

For example, take the React playground peek-a-boo example below:

SimpleColor

<Transition
            show={isOpen}
            enter="transition-colors ease-in duration-[5s]"
            enterFrom="transform bg-red-500"
            enterTo="transform bg-blue-500"
            leave="transition-colors ease-in duration-[5s]"
            leaveFrom="transform bg-blue-500"
            leaveTo="transform bg-red-500"
            entered="bg-blue-500"
            className="h-64 rounded-md p-4 shadow"
          >
            Contents to show and hide
</Transition>

Notice how the div starts off with a transparent background rather than a red background despite having enterFrom set to bg-red-500.

The same issue exists with the modal - it is supposed to be fading in but instead just pops in:

Modal

Setting unmount={false} fixes the problem.

Digger deeper, I think I can see why this is happening. Taking the example from the Transition documentation:

function MyComponent() {
  const [isShowing, setIsShowing] = useState(false)

  return (
    <>
      <button onClick={() => setIsShowing((isShowing) => !isShowing)}>
        Toggle
      </button>
      <Transition
        show={isShowing}
        enter="transition-opacity duration-75"
        enterFrom="opacity-0"
        enterTo="opacity-100"
        leave="transition-opacity duration-150"
        leaveFrom="opacity-100"
        leaveTo="opacity-0"
      >
        I will fade in and out
      </Transition>
    </>
  )
}

At least in Chrome, this appears to have the following behaviour:

  • The Transition component applies the enter and enterFrom classes for 1 frame. The classes duration-75 and opacity-0 were just applied, but the existing opacity is 100, so the browser starts animating from opacity: 100 to opacity: 0.
  • In the next frame the browser continues the opacity transition from 100 to 0 (setting opacity to something like 0.97). The Transition component then removes the enterFrom classes and applies the enterTo classes. This starts animating from the current opacity of opacity: 0.97 back to opacity: 1
  • In the next frame the opacity reaches 1 and transitionend fires.

Applying a className of opacity-0 fixes this issue, because the opacity is already 0 when the enterFrom classes are applied. Alternatively, applying transition-opacity and duration-75 to enterTo rather than enter fixes the problem because the browser does not try to animate the enterFrom.

This behaviour is a little confusing, particularly given many of the examples suffer from this problem. Is there a way to improve this? For example, could transition-property be set to none when applying enterFrom?

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions