Composables
ComposableOriginal

Transition Panel

Direction-aware animated swap between named views with auto-animated height

The transition panel animates the swap between named views and grows or shrinks its height to fit the active one. Use it for multi-step wizards, settings panels, and popover or dialog content that changes in place, where you want a direction-aware slide or an in-place crossfade between states.

Preview

Sign in

Enter your email to receive a code.

Installation

Usage

Composition

Match each TransitionPanelView's viewKey to the panel's activeKey to control which view is visible. Set transition on TransitionPanel to choose how views swap.

ValueDescription
slideDirection-aware horizontal slide (forward right, back left). Default.
fadeNon-directional crossfade with a subtle 0.96 to 1 scale.

Examples

Crossfade

Set transition="fade" to swap views with a non-directional crossfade instead of a slide, for in-place swaps like dialogs, popovers, or settings panels.

Options

Pick what you want to reveal. Each choice swaps the content in place with a crossfade.

Multi-step dialog

Give each step its own TransitionPanelView so the surface crossfades and the Dialog resizes to fit the active step, with focus moving to the active view's first tabbable element on each swap.

API Reference

TransitionPanel is a pure-CSS compound component with no underlying Base UI primitive. Views register through context, so TransitionPanelView doesn't need to be a direct child. Wrappers, fragments, conditional rendering, and Suspense boundaries all work.

The entrance animation uses @starting-style. Browsers that lack it fall back to an instant swap with no slide, which is harmless: no broken state, just no motion. The exit defers display:none with transition-behavior: allow-discrete where it's honored (Chrome, Safari) and uses a JS-deferred hide in Firefox (which doesn't honor allow-discrete on exit), so the exit animation runs in every modern engine. The component also respects prefers-reduced-motion.

Props

TransitionPanel

Outer container. Renders a <div> and accepts any native div prop (id, role, aria-label, ref, data-*, style, etc.). Composes a consumer ref with the internal ref used for height measurement.

Prop
Data attributes

Set on the root element so consumers can style based on transition state.

Prop
CSS custom properties

Set on the root and inherited by view wrappers. Override via the style prop or a CSS rule.

Prop

TransitionPanelView

A single view registered with the parent panel. Renders a <div> and accepts any native div prop (className, style, id, aria-label, aria-labelledby, ref, data-*, etc.) alongside those documented below; spread props land on the view wrapper. The consumer ref is composed with the internal ref used for registration. Visibility, slide offset, and inert state come from the parent's activeKey. Inactive views stay mounted (their internal state survives swaps) but are marked inert and aria-hidden so focus and assistive tech skip them.

Prop
Data attributes
Prop

Notes

  • Composability. Views can be wrapped in HOCs, fragments, error boundaries, or Suspense boundaries, or rendered via .map() and conditional logic. Registration routes through context, not React children.
  • Direction inference. Direction is computed by comparing the new and previous activeKey against the registered view order, so back-navigation slides the opposite way automatically. Re-deriving from DOM position on each registration means a view that unmounts and remounts (e.g. {isPro && <TransitionPanelView ...>}) keeps its source-order slot.
  • Focus management. Focus moves in a useLayoutEffect, before the browser paints, to minimize the gap between the outgoing view becoming inert and the incoming view receiving focus.
  • Height animation. Animates the real height property (via ResizeObserver) rather than transform: scale, so descendant layout doesn't warp. The transition runs only while swapping views, so content that animates its own height inside the active view (an expanding error, an accordion) drives a single height tween the panel tracks exactly, instead of the panel running a second, competing one. That's fine for typical panels. If you need compositor-only height animation for a high-frequency surface, fork to a Motion / FLIP implementation.