CSS transition support hooks for smooth animations when floating elements enter and exit the DOM, with status tracking and style management.
Provides status string for CSS transitions to enable smooth animations during floating element lifecycle.
/**
* Provides status string for CSS transitions
* @param context - Floating UI context
* @param props - Transition status configuration
* @returns Transition status and mounting state
*/
function useTransitionStatus(
context: FloatingContext,
props?: UseTransitionStatusProps
): {
isMounted: boolean;
status: 'unmounted' | 'initial' | 'open' | 'close';
};
interface UseTransitionStatusProps {
duration?: number | { open?: number; close?: number };
}Usage Examples:
import {
useTransitionStatus,
useFloating,
useClick,
useInteractions
} from '@floating-ui/react';
import { useState } from 'react';
// Basic transition status
function TransitionTooltip() {
const [isOpen, setIsOpen] = useState(false);
const { refs, floatingStyles, context } = useFloating({
open: isOpen,
onOpenChange: setIsOpen,
});
const click = useClick(context);
const { getReferenceProps, getFloatingProps } = useInteractions([click]);
const { isMounted, status } = useTransitionStatus(context, {
duration: 200,
});
return (
<>
<button ref={refs.setReference} {...getReferenceProps()}>
Toggle
</button>
{isMounted && (
<div
ref={refs.setFloating}
style={{
...floatingStyles,
background: 'black',
color: 'white',
padding: '8px',
borderRadius: '4px',
transition: 'opacity 200ms',
opacity: status === 'open' ? 1 : 0,
}}
{...getFloatingProps()}
>
Transitioning tooltip
</div>
)}
</>
);
}
// Different durations for open/close
function AsymmetricTransition() {
const [isOpen, setIsOpen] = useState(false);
const { refs, floatingStyles, context } = useFloating({
open: isOpen,
onOpenChange: setIsOpen,
});
const hover = useHover(context);
const { getReferenceProps, getFloatingProps } = useInteractions([hover]);
const { isMounted, status } = useTransitionStatus(context, {
duration: { open: 150, close: 300 },
});
return (
<>
<button ref={refs.setReference} {...getReferenceProps()}>
Hover me
</button>
{isMounted && (
<div
ref={refs.setFloating}
style={{
...floatingStyles,
background: 'lightblue',
padding: '12px',
borderRadius: '6px',
transition: `all ${status === 'close' ? '300ms' : '150ms'} ease-in-out`,
transform: status === 'open' ? 'scale(1)' : 'scale(0.8)',
opacity: status === 'open' ? 1 : 0,
}}
{...getFloatingProps()}
>
Asymmetric transition
</div>
)}
</>
);
}Provides pre-configured styles for common CSS transitions with built-in timing and easing.
/**
* Provides styles for CSS transitions with built-in configurations
* @param context - Floating UI context
* @param props - Transition styles configuration
* @returns Transition styles and mounting state
*/
function useTransitionStyles(
context: FloatingContext,
props?: UseTransitionStylesProps
): {
isMounted: boolean;
styles: Record<string, React.CSSProperties>;
};
interface UseTransitionStylesProps {
duration?: number | { open?: number; close?: number };
initial?: React.CSSProperties | ((side: string) => React.CSSProperties);
open?: React.CSSProperties | ((side: string) => React.CSSProperties);
close?: React.CSSProperties | ((side: string) => React.CSSProperties);
common?: React.CSSProperties | ((side: string) => React.CSSProperties);
transform?: boolean;
}Usage Examples:
import { useTransitionStyles } from '@floating-ui/react';
// Basic transition styles
function StyledTransition() {
const [isOpen, setIsOpen] = useState(false);
const { refs, floatingStyles, context } = useFloating({
open: isOpen,
onOpenChange: setIsOpen,
});
const click = useClick(context);
const { getReferenceProps, getFloatingProps } = useInteractions([click]);
const { isMounted, styles } = useTransitionStyles(context, {
duration: 250,
initial: {
opacity: 0,
transform: 'scale(0.8)',
},
open: {
opacity: 1,
transform: 'scale(1)',
},
close: {
opacity: 0,
transform: 'scale(0.8)',
},
});
return (
<>
<button ref={refs.setReference} {...getReferenceProps()}>
Styled Transition
</button>
{isMounted && (
<div
ref={refs.setFloating}
style={{
...floatingStyles,
...styles,
background: 'white',
border: '1px solid gray',
padding: '16px',
borderRadius: '8px',
boxShadow: '0 4px 6px rgba(0, 0, 0, 0.1)',
}}
{...getFloatingProps()}
>
Styled transition tooltip
</div>
)}
</>
);
}
// Placement-based transitions
function PlacementBasedTransition() {
const [isOpen, setIsOpen] = useState(false);
const { refs, floatingStyles, context } = useFloating({
open: isOpen,
onOpenChange: setIsOpen,
placement: 'top',
});
const hover = useHover(context);
const { getReferenceProps, getFloatingProps } = useInteractions([hover]);
const { isMounted, styles } = useTransitionStyles(context, {
duration: 200,
// Function receives placement side for directional animations
initial: (side) => ({
opacity: 0,
transform: `translateY(${side === 'top' ? '10px' : '-10px'})`,
}),
open: {
opacity: 1,
transform: 'translateY(0)',
},
close: (side) => ({
opacity: 0,
transform: `translateY(${side === 'top' ? '10px' : '-10px'})`,
}),
common: {
transition: 'opacity 200ms ease, transform 200ms ease',
},
});
return (
<>
<button ref={refs.setReference} {...getReferenceProps()}>
Directional Animation
</button>
{isMounted && (
<div
ref={refs.setFloating}
style={{
...floatingStyles,
...styles,
background: 'purple',
color: 'white',
padding: '8px 12px',
borderRadius: '4px',
}}
{...getFloatingProps()}
>
Slides in from placement direction
</div>
)}
</>
);
}
// Complex multi-stage transition
function MultiStageTransition() {
const [isOpen, setIsOpen] = useState(false);
const { refs, floatingStyles, context } = useFloating({
open: isOpen,
onOpenChange: setIsOpen,
});
const click = useClick(context);
const { getReferenceProps, getFloatingProps } = useInteractions([click]);
const { isMounted, styles } = useTransitionStyles(context, {
duration: { open: 300, close: 200 },
initial: {
opacity: 0,
transform: 'scale(0.5) rotate(-10deg)',
filter: 'blur(4px)',
},
open: {
opacity: 1,
transform: 'scale(1) rotate(0deg)',
filter: 'blur(0px)',
},
close: {
opacity: 0,
transform: 'scale(0.8) rotate(5deg)',
filter: 'blur(2px)',
},
common: {
transformOrigin: 'center',
},
});
return (
<>
<button ref={refs.setReference} {...getReferenceProps()}>
Complex Transition
</button>
{isMounted && (
<div
ref={refs.setFloating}
style={{
...floatingStyles,
...styles,
background: 'linear-gradient(45deg, #ff6b6b, #4ecdc4)',
color: 'white',
padding: '20px',
borderRadius: '12px',
fontWeight: 'bold',
}}
{...getFloatingProps()}
>
Complex multi-property transition
</div>
)}
</>
);
}The transition status follows this lifecycle:
// Use transform and opacity for best performance
const { styles } = useTransitionStyles(context, {
initial: { opacity: 0, transform: 'scale(0.9)' },
open: { opacity: 1, transform: 'scale(1)' },
// Avoid animating properties that cause layout recalculation
});// Respect user's motion preferences
const { styles } = useTransitionStyles(context, {
duration: window.matchMedia('(prefers-reduced-motion: reduce)').matches ? 0 : 200,
// Shorter or no transitions for users who prefer reduced motion
});// Use CSS keyframes for complex animations
const { styles } = useTransitionStyles(context, {
common: {
animation: 'complex-entrance 400ms ease-out',
},
});function PortalWithTransitions() {
const { isMounted, styles } = useTransitionStyles(context);
return (
<>
{isMounted && (
<FloatingPortal>
<div style={{ ...floatingStyles, ...styles }}>
Portaled content with transitions
</div>
</FloatingPortal>
)}
</>
);
}{
initial: { opacity: 0 },
open: { opacity: 1 },
close: { opacity: 0 },
}{
initial: { opacity: 0, transform: 'scale(0.8)' },
open: { opacity: 1, transform: 'scale(1)' },
close: { opacity: 0, transform: 'scale(0.8)' },
}{
initial: (side) => ({
opacity: 0,
transform: `translateY(${side === 'top' ? '10px' : '-10px'})`
}),
open: { opacity: 1, transform: 'translateY(0)' },
close: (side) => ({
opacity: 0,
transform: `translateY(${side === 'top' ? '10px' : '-10px'})`
}),
}