Building a Decoupled Custom Dialog System with ArkUI's Navigation.Dialog

Custom dialogs are essential in mobile application interfaces, covering modal, semi-modal, toast, and other overlay styles. A well-designed dialog component should be independent of specific UI pages and easily triggered from business logic.

Common real-world needs include:

  • Triggering dialogs from shared services (login prompts, full-screen ads, network alerts).
  • Intercepting back gestures or swipe actions (privacy consent, logout confirmations).
  • Keeping a dialog visible across page navigations.
  • Applying custom enter/exit transitions (drawer-style animations).
  • Using transparent or semi-transparent backdrops.

Leveraging Navigation.Dialog for Custom Overlays

Navigation.Dialog behaves like a tarnsparent page within the naivgation stack, making it a natural fit for custom dialog implementations while preserving the ability to persist across page changes.

Key considerations:

  • Animation effects inside the dialog must be handled manually by the developer.
  • The dialog lacks built-in background shading; developers need to construct a mask layer and manage gesture interception.

Implementation Steps

1. Set Up a Router with NavPathStack

Create a centralized router singleton that wraps NavPathStack.

export class NavigationRouter {
  private static instance: NavigationRouter;
  private routeStack: NavPathStack = new NavPathStack();

  static getInstance(): NavigationRouter {
    if (!NavigationRouter.instance) {
      NavigationRouter.instance = new NavigationRouter();
    }
    return NavigationRouter.instance;
  }

  getStack(): NavPathStack {
    return this.routeStack;
  }

  // Additional helper methods for pushing dialog pages...
}

2. Initialize the Navigation Container

In the root component, bind the shared stack to a Navigation component.

@Entry
@Component
struct AppRoot {
  build() {
    Navigation(NavigationRouter.getInstance().getStack()) {
      // Main content area
    }
  }
}

3. Register Custom Dialog in .navDestination

Map a route name to your custom dialog component using the navDestination builder. This example uses a BaseDialog as the wrapper.

@Builder
buildPageMap(name: string) {
  if (name === 'CUSTOM_DIALOG_ROUTE') {
    BaseDialog()
  }
  // ... other pages
}

Navigation(NavigationRouter.getInstance().getStack()) {
  // ...
}
.navDestination(this.buildPageMap)

4. Define the Custom Dialog Componetn

Design BaseDialog as a stateful component that renders its content over a tap-through prevention layer.

@Component
export struct BaseDialog {
  private navController: NavDestinationController = new NavDestinationController();
  @State showContent: boolean = false;

  aboutToAppear() {
    // Animate content appearance
    this.showContent = true;
  }

  build() {
    NavDestination() {
      Stack() {
        // Semi-transparent backdrop that blocks interactions
        Column()
          .width('100%')
          .height('100%')
          .backgroundColor('rgba(0,0,0,0.5)')
          .onClick(() => {
            // Optional: close dialog on backdrop tap
          })

        // Actual dialog panel with custom animation
        Column() {
          // Dialog content goes here
          Text('Custom Dialog Content')
          Button('Dismiss')
            .onClick(() => {
              this.navController.pop();
            })
        }
        .width('80%')
        .height('40%')
        .backgroundColor(Color.White)
        .borderRadius(16)
        .translate({
          y: this.showContent ? 0 : 200,
        })
        .animation({ duration: 300, curve: Curve.EaseOut })
      }
      .width('100%')
      .height('100%')
    }
    .hideTitleBar(true)
  }
}

With this setup, any business logic can trigger the dialog by pushing CUSTOM_DIALOG_ROUTE onto the shared NavPathStack. The dialog remains visible even if the underlying page navigates to a sub-page, and developers have full control over visual styling and animation.

Tags: ArkUI HarmonyOS Navigation Dialog CustomDialog

Posted on Wed, 03 Jun 2026 16:24:26 +0000 by sitorush