The supporting pane layout keeps the user's focus on the app's main content while displaying relevant supporting information. For example, the main pane might show details about a movie, while the supporting pane lists similar movies, films by the same director, or works featuring the same actors.
For more details, see the Material 3 supporting pane guidelines.
Implement a supporting pane with NavigableSupportingPaneScaffold
NavigableSupportingPaneScaffold
is a composable that simplifies implementing a
supporting pane layout in Jetpack Compose. It wraps SupportingPaneScaffold
and
adds built-in navigation and predictive back handling.
A supporting pane scaffold supports up to three panes:
- Main pane: Displays primary content.
- Supporting pane: Provides additional context or tools related to the main pane.
- Extra pane (optional): Used for supplementary content when needed.
The scaffold adapts based on window size:
- In large windows, the main and supporting panes appear side by side.
In small windows, only one pane is visible at a time, switching as users navigate.
Figure 1. Supporting pane layout.
Add dependencies
NavigableSupportingPaneScaffold
is part of the Material 3 adaptive layout
library.
Add the following three, related dependencies to the build.gradle
file of your
app or module:
Kotlin
implementation("androidx.compose.material3.adaptive:adaptive") implementation("androidx.compose.material3.adaptive:adaptive-layout") implementation("androidx.compose.material3.adaptive:adaptive-navigation")
Groovy
implementation 'androidx.compose.material3.adaptive:adaptive' implementation 'androidx.compose.material3.adaptive:adaptive-layout' implementation 'androidx.compose.material3.adaptive:adaptive-navigation'
- adaptive: Low-level building blocks such as
HingeInfo
andPosture
- adaptive-layout: Adaptive layouts such as
ListDetailPaneScaffold
andSupportingPaneScaffold
- adaptive-navigation: Composables for navigating within and between panes,
as well as adaptive layouts that support navigation by default such as
NavigableListDetailPaneScaffold
andNavigableSupportingPaneScaffold
Ensure your project includes compose-material3-adaptive version 1.1.0-beta1 or higher.
Opt-in to the predictive back gesture
To enable predictive back animations in Android 15 or lower, you must opt-in
to support the predictive back gesture. To opt-in, add
android:enableOnBackInvokedCallback="true"
to the <application>
[tag or
android:enableOnBackInvokedCallback="true"
to the <application>
tag or
individual <activity>
tags within your AndroidManifest.xml
file.
Once your app targets Android 16 (API level 36) or higher, predictive back is enabled by default.
Create a navigator
In small windows, only one pane displays at a time, so use a
ThreePaneScaffoldNavigator
to move to and from
panes. Create an instance of the navigator with
rememberSupportingPaneScaffoldNavigator
.
val scaffoldNavigator = rememberSupportingPaneScaffoldNavigator() val scope = rememberCoroutineScope()
Pass the navigator to the scaffold
The scaffold requires a ThreePaneScaffoldNavigator
which is an interface
representing the state of the scaffold, the ThreePaneScaffoldValue
and a
PaneScaffoldDirective
.
NavigableSupportingPaneScaffold( navigator = scaffoldNavigator, mainPane = { /*...*/ }, supportingPane = { /*...*/ }, )
The main pane and supporting pane are composables containing your content. Use
AnimatedPane
to apply the default pane animations during
navigation. Use the scaffold value to check whether the supporting pane is
hidden; if so, display a button that calls
navigateTo(SupportingPaneScaffoldRole.Supporting)
to display the
supporting pane.
Here's a complete implementation of the scaffold:
val scaffoldNavigator = rememberSupportingPaneScaffoldNavigator() val scope = rememberCoroutineScope() NavigableSupportingPaneScaffold( navigator = scaffoldNavigator, mainPane = { AnimatedPane( modifier = Modifier .safeContentPadding() .background(Color.Red) ) { if (scaffoldNavigator.scaffoldValue[SupportingPaneScaffoldRole.Supporting] == PaneAdaptedValue.Hidden) { Button( modifier = Modifier .wrapContentSize(), onClick = { scope.launch { scaffoldNavigator.navigateTo(SupportingPaneScaffoldRole.Supporting) } } ) { Text("Show supporting pane") } } else { Text("Supporting pane is shown") } } }, supportingPane = { AnimatedPane(modifier = Modifier.safeContentPadding()) { Text("Supporting pane") } } )
Extract pane composables
Extract the individual panes of a SupportingPaneScaffold
into their own
composables to make them reusable and testable. Use
ThreePaneScaffoldScope
to access AnimatedPane
if
you want the default animations:
@OptIn(ExperimentalMaterial3AdaptiveApi::class) @Composable fun ThreePaneScaffoldPaneScope.MainPane( shouldShowSupportingPaneButton: Boolean, onNavigateToSupportingPane: () -> Unit, modifier: Modifier = Modifier, ) { AnimatedPane( modifier = modifier.safeContentPadding() ) { // Main pane content if (shouldShowSupportingPaneButton) { Button(onClick = onNavigateToSupportingPane) { Text("Show supporting pane") } } else { Text("Supporting pane is shown") } } } @OptIn(ExperimentalMaterial3AdaptiveApi::class) @Composable fun ThreePaneScaffoldPaneScope.SupportingPane( modifier: Modifier = Modifier, ) { AnimatedPane(modifier = modifier.safeContentPadding()) { // Supporting pane content Text("This is the supporting pane") } }
Extracting the panes into composables simplifies the use of the
SupportingPaneScaffold
(compare the following to the complete implementation
of the scaffold in the previous section):
val scaffoldNavigator = rememberSupportingPaneScaffoldNavigator() val scope = rememberCoroutineScope() NavigableSupportingPaneScaffold( navigator = scaffoldNavigator, mainPane = { MainPane( shouldShowSupportingPaneButton = scaffoldNavigator.scaffoldValue.secondary == PaneAdaptedValue.Hidden, onNavigateToSupportingPane = { scope.launch { scaffoldNavigator.navigateTo(ThreePaneScaffoldRole.Secondary) } } ) }, supportingPane = { SupportingPane() }, )
If you need more control over specific aspects of the scaffold, consider using
SupportingPaneScaffold
instead of NavigableSupportingPaneScaffold
. This
accepts a PaneScaffoldDirective
and ThreePaneScaffoldValue
or
ThreePaneScaffoldState
separately. This flexibility lets you
implement custom logic for pane spacing and determine how many panes should be
displayed simultaneously. You can also enable predictive back support by adding
ThreePaneScaffoldPredictiveBackHandler
.
Add ThreePaneScaffoldPredictiveBackHandler
Attach the predictive back handler that takes a scaffold navigator instance and
specify the backBehavior
. This determines how destinations are popped
from the backstack during back navigation. Then pass the scaffoldDirective
and
scaffoldState
to SupportingPaneScaffold
. Use the overload that accepts a
ThreePaneScaffoldState
, passing in scaffoldNavigator.scaffoldState
.
Define the main and supporting panes within SupportingPaneScaffold
. Use
AnimatedPane
for default pane animations.
After you implement these steps, your code should look similar to the following:
val scaffoldNavigator = rememberSupportingPaneScaffoldNavigator() val scope = rememberCoroutineScope() ThreePaneScaffoldPredictiveBackHandler( navigator = scaffoldNavigator, backBehavior = BackNavigationBehavior.PopUntilScaffoldValueChange ) SupportingPaneScaffold( directive = scaffoldNavigator.scaffoldDirective, scaffoldState = scaffoldNavigator.scaffoldState, mainPane = { MainPane( shouldShowSupportingPaneButton = scaffoldNavigator.scaffoldValue.secondary == PaneAdaptedValue.Hidden, onNavigateToSupportingPane = { scope.launch { scaffoldNavigator.navigateTo(ThreePaneScaffoldRole.Secondary) } } ) }, supportingPane = { SupportingPane() }, )