Skip to main content
react-md

useCSSTransition

function useCSSTransition<E extends HTMLElement>(
  options: CSSTransitionHookOptions<E>
): CSSTransitionHookReturnValue<E>;

The useCSSTransition can be used to create CSS transitions for React components while also dynamically rendering them. This ensures that the exit transition will complete before unmounting the component.

Example Usage

import { Button } from "@react-md/core/button/Button";
import { useCSSTransition } from "@react-md/core/transition/useCSSTransition";
import { Typography } from "@react-md/core/typography/Typography";
import { type ReactElement, useState } from "react";

// Pretend styles
// .enter {
//   opacity: 0.5;
//   transition: opacity .15s;
// }
//
// .enter--active {
//   opacity: 1;
// }
//
// .exit {
//   opacity: 1;
//   transition: opacity .15s;
// }
//
// .exit--active {
//   opacity: 0.5;
// }

function Example(): ReactElement {
  const [transitionIn, setTransitionIn] = useState(false);
  const { elementProps } = useCSSTransition({
    timeout: 150,
    classNames: {
      enter: "enter",
      enterActive: "enter--active",
      exit: "exit",
      exitActive: "exit--active",
    },
    transitionIn,
  });

  return (
    <>
      <Button onClick={() => setTransitionIn(!transitionIn)}>Toggle</Button>
      <Typography {...elementProps}>Some Opacity Changing Text</Typography>
    </>
  );
}

Visibility Transition

import { Button } from "@react-md/core/button/Button";
import { useCSSTransition } from "@react-md/core/transition/useCSSTransition";
import { Typography } from "@react-md/core/typography/Typography";
import { type ReactElement, useState } from "react";

// Pretend styles
// .enter {
//   opacity: 0;
//   transition: opacity .2s;
// }
//
// .enter--active {
//   opacity: 1;
// }
//
// .exit {
//   opacity: 1;
//   transition: opacity .15s;
// }
//
// .exit--active {
//   opacity: 0;
// }

function Example(): ReactElement {
  const [transitionIn, setTransitionIn] = useState(false);
  const { elementProps, rendered } = useCSSTransition({
    timeout: {
      enter: 200,
      exit: 150,
    },
    classNames: {
      enter: "enter",
      enterActive: "enter--active",
      exit: "exit",
      exitActive: "exit--active",
    },
    transitionIn,
    temporary: true,
  });

  return (
    <>
      <Button onClick={() => setTransitionIn(!transitionIn)}>Toggle</Button>
      {rendered && (
        <Typography {...elementProps}>Some Opacity Changing Text</Typography>
      )}
    </>
  );
}

Mount Transition

import { useCSSTransition } from "@react-md/core/transition/useCSSTransition";
import { type ReactElement } from "react";

// Pretend styles
// .opacity {
//   opacity: 0;
//   transition: opacity .3s;
// }
//
// .opacity--active {
//   opacity: 1;
// }
//

function Example(): ReactElement {
  const { elementProps } = useCSSTransition({
    appear: true,
    transitionIn: true,
    timeout: 300,
    classNames: "opacity",
  });

  return <div {...elementProps}>Some Content!</div>;
}

Parameters

export interface PreconfiguredCSSTransitionOptions<E extends HTMLElement> {
  /**
   * This boolean controls the transition by activating flowing through the
   * {@link TransitionStage}.
   */
  transitionIn: boolean;

  /**
   * Either a single timeout duration in milliseconds to use for each of the
   * {@link TransitionActions} stages, or an object of transition durations.
   */
  timeout: TransitionTimeout;
  classNames: CSSTransitionClassNames;

  /**
   * An optional ref that will be merged with the
   * {@link TransitionHookReturnValue.ref}
   */
  nodeRef?: Ref<E>;

  /**
   * Boolean if the element should mount and unmount based on the
   * {@link PreconfiguredTransitionInDefaultedOptions.transitionIn} value.
   *
   * @defaultValue `false`
   */
  temporary?: boolean;

  /**
   * An optional className to be merged with the transition classes.
   */
  className?: string;

  /**
   * When this is `true` and the {@link temporary} option is `false`, the
   * element will gain a class name to hide it with `display: none` instead of
   * conditionally rendering the element.
   *
   * @defaultValue `false`
   * @since 6.0.0
   */
  exitedHidden?: boolean;
}

export interface CSSTransitionClassNamesObject {
  /**
   * The class name to apply starting at the `"enter"` {@link TransitionStage}
   * while {@link TransitionState.appearing}.
   *
   * @defaultValue `""`
   */
  appear?: string;

  /**
   * The class name to apply starting at the `"entering"` {@link TransitionStage}
   * while {@link TransitionState.appearing}.
   *
   * @defaultValue `""`
   */
  appearActive?: string;

  /**
   * The class name to apply starting at the `"entered"` {@link TransitionStage}
   * while {@link TransitionState.appearing}.
   *
   * @defaultValue `""`
   */
  appearDone?: string;

  /**
   * The class name to apply starting at the `"enter"` {@link TransitionStage}
   *
   * @defaultValue `""`
   */
  enter?: string;

  /**
   * The class name to apply starting at the `"entering"` {@link TransitionStage}
   *
   * @defaultValue `""`
   */
  enterActive?: string;

  /**
   * The class name to apply starting at the `"entered"` {@link TransitionStage}
   *
   * @defaultValue `""`
   */
  enterDone?: string;

  /**
   * The class name to apply starting at the `"exit"` {@link TransitionStage}
   *
   * @defaultValue `""`
   */
  exit?: string;

  /**
   * The class name to apply starting at the `"exiting"` {@link TransitionStage}
   *
   * @defaultValue `""`
   */
  exitActive?: string;

  /**
   * The class name to apply starting at the `"exited"` {@link TransitionStage}
   *
   * @defaultValue `""`
   */
  exitDone?: string;
}

/**
 * @since 4.0.0
 */
export type CSSTransitionClassNames =
  | string
  | Readonly<CSSTransitionClassNamesObject>;

Returns

An object with the following definition:

export interface CSSTransitionHookReturnValue<E extends HTMLElement>
  extends TransitionHookReturnValue<E>,
    CSSTransitionElementProps<E> {
  /**
   * This can be used so that you don't need to destructure multiple props from
   * the hook return value to pass to the transitioning component.
   *
   * @example Simple Example
   * ```tsx
   * import type { ReactElement } from "react";
   * import { useCSSTransition } from "@react-md/transition";
   *
   * interface ExampleProps {
   *   transitionIn: boolean;
   *   children: ReactNode;
   * }
   *
   * function Example({ transitionIn, children }: ExampleProps): ReactElement | null {
   *   const { elementProps, rendered } = useCSSTransition({
   *     timeout: 150,
   *     classNames: "example",
   *     transitionIn,
   *   });
   *
   *   if (!rendered) {
   *     return null;
   *   }
   *
   *   return <div {...elementProps}>{children}</div>
   * }
   * ```
   *
   * @example Verbose Version
   * ```tsx
   * import type { ReactElement } from "react";
   * import { useCSSTransition } from "@react-md/transition";
   *
   * interface ExampleProps {
   *   transitionIn: boolean;
   *   children: ReactNode;
   * }
   *
   * function Example({ transitionIn, children }: ExampleProps): ReactElement | null {
   *   const { ref, className, rendered } = useCSSTransition({
   *     timeout: 150,
   *     classNames: "example",
   *     transitionIn,
   *   });
   *
   *   if (!rendered) {
   *     return null;
   *   }
   *
   *   return <div ref={ref} className={className}>{children}</div>
   * }
   * ```
   */
  elementProps: CSSTransitionElementProps<E>;
}

export interface CSSTransitionElementProps<E extends HTMLElement> {
  /** @see {@link TransitionHookReturnValue.ref} */
  ref: RefCallback<E>;

  /**
   * The current transition class name or `undefined`.
   */
  className: string | undefined;
}

See Also