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>
</>
);
}
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>
);
},
);
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>
);
},
);
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>
);
}
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>
</>
);
}