Skip to content

Layout & Navigation

CDX UI provides four layout / navigation primitives. They cover the common shells without forcing you into a full IDE-style workspace (use cdx_panes for that).

MasterDetailScaffold

Responsive master-detail with an animated, resizable detail panel. Picks docked vs. overlay presentation based on viewport width — or forces one explicitly.

dart
MasterDetailScaffold(
  main: AreasList(areas: areas, onSelect: store.select),
  panel: AreaDetailPanel(area: store.selected),
  showPanel: store.selected != null,
  panelWidth: 600,
  presentation: const PanelPresentation.auto(breakpoint: 1024),
  onPanelClosed: () => store.clearSelection(),
)

PanelPresentation

dart
PanelPresentation.docked()                                    // side-by-side row
PanelPresentation.overlay(showBarrier: true)                  // floating sheet from right
PanelPresentation.auto(breakpoint: 1024)                      // docked at wide, overlay at narrow
BehaviorDockedOverlay
LayoutRow(main, panel)Stack(main, scrim, panel)
Resizable edgeyes (drag handle on left of panel)no
Barrier dismissn/atap scrim closes (when showBarrier: true)
ESC keyboth — closes panel via onPanelClosedboth

Panel sizing defaults are baked in (min 360, default 480, max 720); pass panelWidth to override the initial width.

LazyIndexedStack

IndexedStack that only builds children when first selected. The unbuilt children render SizedBox.shrink(). Once built, children are preserved across switches via Offstage + TickerMode (so background animations pause but state survives).

dart
LazyIndexedStack(
  index: tabIndex,
  children: const [
    OverviewTab(),
    DetailsTab(),
    HistoryTab(),     // only built when the user opens this tab
  ],
)

LazyIndexedStackBuilder is the variant that takes a builder function and an itemCount — useful for very large fixed-tab sets.

RouteAwareTabController + RouteAwareLazyIndexedStack

Tab controllers wired to go_router. Switching tabs updates the URL; URL changes update the active tab. Combined with LazyIndexedStack they give you router-driven tabbed pages without manual sync code.

dart
RouteAwareTabBarView(
  tabRoutes: const ['overview', 'details', 'history'],
  baseRoute: '/lots/$lotId',
  tabs: const [
    Tab(text: 'Overview'),
    Tab(text: 'Details'),
    Tab(text: 'History'),
  ],
  children: const [
    OverviewTab(),
    DetailsTab(),
    HistoryTab(),
  ],
)

Internally this composes:

  • RouteAwareTabController(tabRoutes, baseRoute, length, vsync) — bidirectional sync
  • RouteAwareTabBarTabBar with the controller wired
  • RouteAwareLazyIndexedStack — lazy-loaded panes

Use the parts directly when you need a custom tab strip.

Wizard

A vertical-sidebar wizard. WizardSteps pair a label with a content builder; WizardController manages navigation, busy state, and sequential gating.

dart
final controller = WizardController(
  stepCount: 3,
  isSequential: true,                // disallow forward jumps to unvisited steps
  onStepChanged: (i) => analytics.log('wizard_step', {'step': i}),
);

Wizard(
  controller: controller,
  steps: [
    WizardStep(title: 'Basics', builder: (_) => const _BasicsStep()),
    WizardStep(title: 'Details', builder: (_) => const _DetailsStep()),
    WizardStep(title: 'Review', builder: (_) => const _ReviewStep()),
  ],
  decoration: BoxDecoration(color: Theme.of(context).colorScheme.surfaceContainerLow),
)

WizardController

dart
controller.currentStep;        // 0-based
controller.highestStepVisited;
controller.isFirstStep / isLastStep;
controller.canGoToNextStep / canGoToPreviousStep;
controller.canGoToStep(2);     // honors isSequential + stepGuard

controller.nextStep();
controller.previousStep();
controller.goToStep(2);

// Async work — block all navigation while busy
controller.setBusy(true);
await api.save();
controller.setBusy(false);
controller.nextStep();

Custom step indicators

Pass stepIndicatorBuilder and stepLabelBuilder to override the default themed circles + labels. Useful on dark / gradient backgrounds.

dart
Wizard(
  controller: controller,
  steps: steps,
  decoration: const BoxDecoration(gradient: myGradient),
  stepIndicatorBuilder: (context, index, state, size) => MyCustomDot(state: state, size: size),
  stepLabelBuilder: (context, title, state, isTappable) => MyLabel(title: title, state: state),
)

WizardStepState is completed / active / upcoming.

  • PanelsCdxSlideInPanel is the modal alternative to MasterDetailScaffold's overlay mode
  • ButtonsButton.filled for wizard "Next" actions
  • State ViewsAsyncView inside each tab pane