headlessui: Transition not working in NextJs

It appears like Transition classes are not there initially on any div(<Transition>) Btw I’m using ( tailwindcss(^1.8.4) , styled-components & twin-macro)

Sample code I’m trying to test on my NextJs WebApp image

About this issue

  • Original URL
  • State: closed
  • Created 4 years ago
  • Reactions: 5
  • Comments: 28

Most upvoted comments

Here’s a working example for styled components and twin.macro: https://codesandbox.io/s/next-styled-components-tailwind-twin-starter-headless-ui-transition-forked-vow27?file=/pages/index.js

Isn’t perfect but works…

Hi, not sure if its the same issue. I just faced this a minute ago, and the solution was to hoist the classes in the inner div to the <Transition>

initial:

<Transition
   show={showMenu}
   enter="transition duration-75 ease-in-out"
   enterFrom="transform translate-x-full"
   enterTo="transform translate-x-0"
   leave="transition duration-75 ease-in-out"
   leaveFrom="transform translate-x-0"
   leaveTo="transform translate-x-full"
      >
        <div className="bg-primary absolute right-0 h-screen w-full md:w-1/4 md:rounded-tl-lg shadow-xl">
              .......
        </div>

After

<Transition
   show={showMenu}
   enter="transition duration-500 ease-in-out"
   enterFrom="transform translate-x-full"
   enterTo="transform translate-x-0"
   leave="transition duration-500 ease-in-out"
   leaveFrom="transform translate-x-0"
   leaveTo="transform translate-x-full"
   className="bg-primary absolute right-0 h-screen w-full md:w-1/4 md:rounded-tl-lg shadow-xl"
>
        <div className="p-8">
              ..........
        </div>

I think this is because the <Transition> component renders the child in another div. so having your classes there might solve it.

Since twin.macro is a macro (hence the name), many things happens in build time so you can’t use <Transition enter="transition-opacity delay-500 duration-1000" ... just like that.

Here is how I fixed it using the default twin.macro setup (emotion behind the scenes):

import { Transition } from "@headlessui/react";
import { css, cx } from "@emotion/css"; // <- this

// get classnames from style objects
const twClassNames = {
  enter: cx(css(tw`transition ease-out duration-100 transform`)),
  enterFrom: cx(css(tw`opacity-0 scale-95`)),
  enterTo: cx(css(tw`opacity-100 scale-100`)),
  leave: cx(css(tw`transition ease-in duration-75 transform`)),
  leaveFrom: cx(css(tw`opacity-100 scale-100`)),
  leaveTo: cx(css(tw`opacity-0 scale-95`)),
};

function App() {
  const [show, setShow] = useState(false);
  return (
    <Transition show={show} {...twClassNames}>
      {(ref) => ( ... )}
    </Transition>
  )
}

I hope headlessui implements function props while keeping support for strings. Eg:

// This is just an idea, do not use this code
<Transition enter={() =>tw`transition ease-out duration-100 transform`} />

I am using twin.macro (tw``) with custom Transition wrapper component.

import tw from 'twin.macro'
import { Transition } from './transition'

<Transition
  show={isShowing}
  enter={tw`transition-opacity duration-1000`}
  enterFrom={tw`opacity-0`}
  enterTo={tw`opacity-100`}
  leave={tw`transition-opacity duration-150`}
  leaveFrom={tw`opacity-100`}
  leaveTo={tw`opacity-0`}
>
  I will fade in and out
</Transition>

transition.tsx

import React, { ElementType, MutableRefObject } from 'react'

import { Transition as TransitionBase } from '@headlessui/react'
import { TransitionEvents } from '@headlessui/react/dist/components/transitions/transition'
import { Props } from '@headlessui/react/dist/types'
import { PropsForFeatures } from '@headlessui/react/dist/utils/render'

import { css } from '@emotion/css'
import { TwStyle } from 'twin.macro'

const DEFAULT_TRANSITION_CHILD_TAG = 'div' as const
type NewType = MutableRefObject<HTMLDivElement>

type TransitionChildRenderPropArg = NewType
const Features = { None: 0, RenderStrategy: 1, Static: 2 }
const TransitionChildRenderFeatures = Features.RenderStrategy

interface TransitionClasses {
  enter?: TwStyle | string
  enterFrom?: TwStyle | string
  enterTo?: TwStyle | string
  entered?: TwStyle | string
  leave?: TwStyle | string
  leaveFrom?: TwStyle | string
  leaveTo?: TwStyle | string
}

type TransitionChildProps<TTag> = Props<TTag, TransitionChildRenderPropArg> &
  PropsForFeatures<typeof TransitionChildRenderFeatures> &
  TransitionClasses &
  TransitionEvents & { appear?: boolean }

function getPropsWithTw(props: TransitionClasses) {
  const newProps = { ...props }
  newProps.enter =
    typeof props.enter == 'string' ? props.enter : css(props.enter)
  newProps.enterFrom =
    typeof props.enterFrom == 'string' ? props.enterFrom : css(props.enterFrom)
  newProps.enterTo =
    typeof props.enterTo == 'string' ? props.enterTo : css(props.enterTo)
  newProps.leave =
    typeof props.leave == 'string' ? props.leave : css(props.leave)
  newProps.leaveFrom =
    typeof props.leaveFrom == 'string' ? props.leaveFrom : css(props.leaveFrom)
  newProps.leaveTo =
    typeof props.leaveTo == 'string' ? props.leaveTo : css(props.leaveTo)

  return newProps
}

export function Transition<
  TTag extends ElementType = typeof DEFAULT_TRANSITION_CHILD_TAG,
>(props: TransitionChildProps<TTag> & { show?: boolean; appear?: boolean }) {
  // @ts-ignore
  return <TransitionBase {...getPropsWithTw(props)} />
}

Transition.Child = function Child<
  TTag extends ElementType = typeof DEFAULT_TRANSITION_CHILD_TAG,
>(props: TransitionChildProps<TTag>) {
  // @ts-ignore
  return <TransitionBase.Child {...getPropsWithTw(props)} />
}
Transition.Root = Transition

@Barbariansyah see #681 for more information

stack: tailwind + headless UI + Next.js

I’ve also faced the same issue running with next dev, using the first example from https://headlessui.dev/react/transition

but when I do next build then next start it works just fine

is this an expected behavior? can anyone help explain what’s happening? thanks a lot 🙏

Having the same issue with styled components and twin.macro. Would be great if anyone can provide an example.

@puneetverma05 Did you solve this?

@puneetverma05 It would be very weird that we should support insert tools here. That said, if it is not the responsibility of twin.macro but rather styled-components / emotion, then it is up to those tools to generate classNames.

For example, when you use emotion (I don’t know if styled-components exposes such an api) than you can do this: https://codesandbox.io/s/next-styled-components-tailwind-twin-starter-headless-ui-transition-forked-p7hbe?file=/pages/index.js:1633-2041

Based on a quick test it looks like keeping the appear property on true solves this issue with NextJS

@puneetverma05

The transition currently works by reading the duration from the DOM. So this duration is defined by the duration-100 class for example. However, when you use twin.macro the duration-100 class doesn’t exists, thus the duration will be 0 and immediately mounted/unmounted.

Essentially if you know how to generate a className form the macro then it should work

function magic(twinput) {} // This function should return a className

<Transition enter={magic(tw`...`)} leave={magic(tw`...`))} />

@andrewgbliss What do you mean with The div doesn't have any classes on it. which div are you referring to? The one Transition generates (when it is mounted) or another one?