Skip to main content
react-md

Cross Fade

The cross fade transition is generally used for displaying new items with an enter only transition that fades in while dropping down into place. I don't remember why I named it cross fade.

The transition can be triggered by using the CrossFade component or useCrossFadeTransition hook.

New Items Example

This example will showcase how the CrossFade component can be used to animate new items that appear within a grid. The CrossFade child must forward refs and accept a className to animate correctly.

"use client";

import { Box } from "@react-md/core/box/Box";
import { Button } from "@react-md/core/button/Button";
import { Card } from "@react-md/core/card/Card";
import { CardContent } from "@react-md/core/card/CardContent";
import { CrossFade } from "@react-md/core/transition/CrossFade";
import { type ReactElement, useState } from "react";

export default function NewItemsExample(): ReactElement {
  const [items, setItems] = useState(0);

  return (
    <>
      <Box fullWidth disablePadding>
        <Button
          theme="primary"
          themeType="contained"
          disabled={items >= 16}
          onClick={() => {
            setItems((prev) => prev + 1);
          }}
        >
          Add Item
        </Button>
        <Button
          onClick={() => {
            setItems(0);
          }}
          theme="warning"
          themeType="contained"
        >
          Reset
        </Button>
      </Box>
      <Box grid fullWidth gridColumns="fill">
        {Array.from({ length: items }, (_, i) => (
          <CrossFade key={i}>
            <Card>
              <CardContent>{`Item ${i + 1}`}</CardContent>
            </Card>
          </CrossFade>
        ))}
      </Box>
    </>
  );
}

Press Enter to start editing.

Incorrect Example

This example will show how to fix common issues with the CrossFade component's child transition.

"use client";

import { Box } from "@react-md/core/box/Box";
import { Button } from "@react-md/core/button/Button";
import { Card, type CardProps } from "@react-md/core/card/Card";
import { CardContent } from "@react-md/core/card/CardContent";
import { Switch } from "@react-md/core/form/Switch";
import { CrossFade } from "@react-md/core/transition/CrossFade";
import { type ReactElement, forwardRef, useState } from "react";

export default function IncorrectExample(): ReactElement {
  const [items, setItems] = useState(0);
  const [fixed, setFixed] = useState(false);

  const CustomCard = fixed ? CustomCardFixed : CustomCardBad;

  return (
    <>
      <Box fullWidth disablePadding>
        <Button
          theme="primary"
          themeType="contained"
          disabled={items >= 16}
          onClick={() => {
            setItems((prev) => prev + 1);
          }}
        >
          Add Item
        </Button>
        <Button
          onClick={() => {
            setItems(0);
          }}
          theme="warning"
          themeType="contained"
        >
          Reset
        </Button>
        <Switch
          label="Fixed?"
          checked={fixed}
          onChange={(event) => {
            setFixed(event.currentTarget.checked);
          }}
        />
      </Box>
      <Box grid fullWidth gridColumns="fill">
        {Array.from({ length: items }, (_, i) => (
          <CrossFade key={i}>
            <CustomCard index={i} />
          </CrossFade>
        ))}
      </Box>
    </>
  );
}

function CustomCardBad(props: { index: number }): ReactElement {
  const { index } = props;

  return (
    <Card>
      <CardContent>{`Item ${index + 1}`}</CardContent>
    </Card>
  );
}

interface CustomCardFixedProps extends CardProps {
  index: number;
}

const CustomCardFixed = forwardRef<HTMLDivElement, CustomCardFixedProps>(
  function CustomCardFixed(props, ref) {
    const { index, ...remaining } = props;
    return (
      <Card {...remaining} ref={ref}>
        <CardContent>{`Item ${index + 1}`}</CardContent>
      </Card>
    );
  },
);

Press Enter to start editing.

Using Keys

The cross fade transition can be triggered by changing the key on the CrossFade component.

Page 1

Nunc dapibus nec neque vitae aliquam. Phasellus eu luctus tortor. Morbi et massa lectus. Nam nec posuere urna, nec tincidunt ligula. Vestibulum in urna dapibus, rutrum nisi eu, convallis leo. Morbi maximus ultricies metus at venenatis. Nulla tincidunt in enim ac semper. Maecenas at felis eget dui malesuada placerat eu a dui. Vestibulum vel quam egestas turpis commodo euismod ac quis purus.

"use client";

import { SegmentedButton } from "@react-md/core/segmented-button/SegmentedButton";
import { SegmentedButtonContainer } from "@react-md/core/segmented-button/SegmentedButtonContainer";
import { CrossFade } from "@react-md/core/transition/CrossFade";
import { TextContainer } from "@react-md/core/typography/TextContainer";
import { Typography } from "@react-md/core/typography/Typography";
import {
  type HTMLAttributes,
  type ReactElement,
  forwardRef,
  useEffect,
  useRef,
  useState,
} from "react";

export default function UsingKeysExample(): ReactElement {
  const [page, setPage] = useState<1 | 2>(1);

  // make it so the appear transition only happens after the first render
  const renderedOnce = useRef(false);
  useEffect(() => {
    renderedOnce.current = true;
  }, []);

  return (
    <>
      <SegmentedButtonContainer>
        <SegmentedButton
          selected={page === 1}
          onClick={() => {
            setPage(1);
          }}
        >
          Page 1
        </SegmentedButton>
        <SegmentedButton
          selected={page === 2}
          onClick={() => {
            setPage(2);
          }}
        >
          Page 2
        </SegmentedButton>
      </SegmentedButtonContainer>
      <CrossFade key={page} appear={renderedOnce.current}>
        {page === 1 ? <Page1 /> : <Page2 />}
      </CrossFade>
    </>
  );
}

const Page1 = forwardRef<HTMLDivElement, HTMLAttributes<HTMLDivElement>>(
  function Page1(props, ref) {
    return (
      <TextContainer {...props} ref={ref}>
        <Typography type="headline-4">Page 1</Typography>
        <Typography>
          Nunc dapibus nec neque vitae aliquam. Phasellus eu luctus tortor.
          Morbi et massa lectus. Nam nec posuere urna, nec tincidunt ligula.
          Vestibulum in urna dapibus, rutrum nisi eu, convallis leo. Morbi
          maximus ultricies metus at venenatis. Nulla tincidunt in enim ac
          semper. Maecenas at felis eget dui malesuada placerat eu a dui.
          Vestibulum vel quam egestas turpis commodo euismod ac quis purus.
        </Typography>
      </TextContainer>
    );
  },
);

const Page2 = forwardRef<HTMLDivElement, HTMLAttributes<HTMLDivElement>>(
  function Page2(props, ref) {
    return (
      <TextContainer {...props} ref={ref}>
        <Typography type="headline-4">Page 2</Typography>
        <Typography>
          Nullam consectetur rhoncus rhoncus. Nullam cursus porttitor lacus non
          facilisis. Donec tincidunt arcu sollicitudin neque iaculis
          sollicitudin. Vivamus in accumsan turpis. Praesent elementum elit
          vitae risus sollicitudin pretium. Aliquam vitae diam non libero
          efficitur consequat. Ut a porttitor nibh. Pellentesque habitant morbi
          tristique senectus et netus et malesuada fames ac turpis egestas.
        </Typography>
      </TextContainer>
    );
  },
);

Press Enter to start editing.

Cross Fade Transition Hook

The useCrossFadeTransition can be used instead of the CrossFade component to enable the cross fade transition. The hook will provide elementProps containing a ref and className which must be passed to a component for the transition to work.

Unlike the CrossFade component, the useCrossFadeTransition does not default to enabling appear transitions.

Page 1

Nunc dapibus nec neque vitae aliquam. Phasellus eu luctus tortor. Morbi et massa lectus. Nam nec posuere urna, nec tincidunt ligula. Vestibulum in urna dapibus, rutrum nisi eu, convallis leo. Morbi maximus ultricies metus at venenatis. Nulla tincidunt in enim ac semper. Maecenas at felis eget dui malesuada placerat eu a dui. Vestibulum vel quam egestas turpis commodo euismod ac quis purus.

"use client";

import { SegmentedButton } from "@react-md/core/segmented-button/SegmentedButton";
import { SegmentedButtonContainer } from "@react-md/core/segmented-button/SegmentedButtonContainer";
import { useCrossFadeTransition } from "@react-md/core/transition/useCrossFadeTransition";
import { TextContainer } from "@react-md/core/typography/TextContainer";
import { Typography } from "@react-md/core/typography/Typography";
import { type ReactElement, useEffect, useRef, useState } from "react";

export default function CrossFadeTransitionHookExample(): ReactElement {
  const [page, setPage] = useState<1 | 2>(1);

  // make it so the appear transition only happens after the first render
  const renderedOnce = useRef(false);
  useEffect(() => {
    renderedOnce.current = true;
  }, []);

  return (
    <>
      <SegmentedButtonContainer>
        <SegmentedButton
          selected={page === 1}
          onClick={() => {
            setPage(1);
          }}
        >
          Page 1
        </SegmentedButton>
        <SegmentedButton
          selected={page === 2}
          onClick={() => {
            setPage(2);
          }}
        >
          Page 2
        </SegmentedButton>
      </SegmentedButtonContainer>
      {page === 1 ? (
        <Page1 appear={renderedOnce.current} />
      ) : (
        <Page2 appear={renderedOnce.current} />
      )}
    </>
  );
}

interface PageProps {
  appear: boolean;
}

function Page1({ appear }: PageProps): ReactElement {
  const { elementProps } = useCrossFadeTransition({ appear });

  return (
    <TextContainer {...elementProps}>
      <Typography type="headline-4">Page 1</Typography>
      <Typography>
        Nunc dapibus nec neque vitae aliquam. Phasellus eu luctus tortor. Morbi
        et massa lectus. Nam nec posuere urna, nec tincidunt ligula. Vestibulum
        in urna dapibus, rutrum nisi eu, convallis leo. Morbi maximus ultricies
        metus at venenatis. Nulla tincidunt in enim ac semper. Maecenas at felis
        eget dui malesuada placerat eu a dui. Vestibulum vel quam egestas turpis
        commodo euismod ac quis purus.
      </Typography>
    </TextContainer>
  );
}

function Page2({ appear }: PageProps): ReactElement {
  const { elementProps } = useCrossFadeTransition({ appear });

  return (
    <TextContainer {...elementProps}>
      <Typography type="headline-4">Page 2</Typography>
      <Typography>
        Nullam consectetur rhoncus rhoncus. Nullam cursus porttitor lacus non
        facilisis. Donec tincidunt arcu sollicitudin neque iaculis sollicitudin.
        Vivamus in accumsan turpis. Praesent elementum elit vitae risus
        sollicitudin pretium. Aliquam vitae diam non libero efficitur consequat.
        Ut a porttitor nibh. Pellentesque habitant morbi tristique senectus et
        netus et malesuada fames ac turpis egestas.
      </Typography>
    </TextContainer>
  );
}

Press Enter to start editing.

Transition To

The main benefit to the useCrossFadeTransition hook is that the transition can be triggered without re-mounting child components which is ideal for full page layout transitions. To trigger a new transition, call transitionTo("enter").

See the useCSSTransition hook for more options.

Page 1

Nunc dapibus nec neque vitae aliquam. Phasellus eu luctus tortor. Morbi et massa lectus. Nam nec posuere urna, nec tincidunt ligula. Vestibulum in urna dapibus, rutrum nisi eu, convallis leo. Morbi maximus ultricies metus at venenatis. Nulla tincidunt in enim ac semper. Maecenas at felis eget dui malesuada placerat eu a dui. Vestibulum vel quam egestas turpis commodo euismod ac quis purus.

"use client";

import { SegmentedButton } from "@react-md/core/segmented-button/SegmentedButton";
import { SegmentedButtonContainer } from "@react-md/core/segmented-button/SegmentedButtonContainer";
import { useCrossFadeTransition } from "@react-md/core/transition/useCrossFadeTransition";
import { TextContainer } from "@react-md/core/typography/TextContainer";
import { Typography } from "@react-md/core/typography/Typography";
import { type ReactElement, useState } from "react";

export default function TransitionToExample(): ReactElement {
  const [page, setPage] = useState<1 | 2>(1);
  const { elementProps, transitionTo } = useCrossFadeTransition();

  const transitionToPage = (page: 1 | 2): void => {
    setPage(page);
    transitionTo("enter");
  };

  return (
    <>
      <SegmentedButtonContainer>
        <SegmentedButton
          selected={page === 1}
          onClick={() => {
            transitionToPage(1);
          }}
        >
          Page 1
        </SegmentedButton>
        <SegmentedButton
          selected={page === 2}
          onClick={() => {
            transitionToPage(2);
          }}
        >
          Page 2
        </SegmentedButton>
      </SegmentedButtonContainer>
      <TextContainer {...elementProps}>
        {page === 1 ? <Page1 /> : <Page2 />}
      </TextContainer>
    </>
  );
}

function Page1(): ReactElement {
  return (
    <>
      <Typography type="headline-4">Page 1</Typography>
      <Typography>
        Nunc dapibus nec neque vitae aliquam. Phasellus eu luctus tortor. Morbi
        et massa lectus. Nam nec posuere urna, nec tincidunt ligula. Vestibulum
        in urna dapibus, rutrum nisi eu, convallis leo. Morbi maximus ultricies
        metus at venenatis. Nulla tincidunt in enim ac semper. Maecenas at felis
        eget dui malesuada placerat eu a dui. Vestibulum vel quam egestas turpis
        commodo euismod ac quis purus.
      </Typography>
    </>
  );
}

function Page2(): ReactElement {
  return (
    <>
      <Typography type="headline-4">Page 2</Typography>
      <Typography>
        Nullam consectetur rhoncus rhoncus. Nullam cursus porttitor lacus non
        facilisis. Donec tincidunt arcu sollicitudin neque iaculis sollicitudin.
        Vivamus in accumsan turpis. Praesent elementum elit vitae risus
        sollicitudin pretium. Aliquam vitae diam non libero efficitur consequat.
        Ut a porttitor nibh. Pellentesque habitant morbi tristique senectus et
        netus et malesuada fames ac turpis egestas.
      </Typography>
    </>
  );
}

Press Enter to start editing.