Implementing Tab Navigation in HarmonyOS Applications

Bottom Navigation Bar

@Entry
@Component
struct MainScreen {
  build() {
    Column() {
      Tabs({ barPosition: BarPosition.End }) {
        TabContent() {
          Text('Home Screen').fontSize(30)
        }.tabBar('Home')

        TabContent() {
          Text('Discovery Screen').fontSize(30)
        }.tabBar('Discovery')

        TabContent() {
          Text('Profile Screen').fontSize(30)
        }.tabBar('Profile')

        TabContent() {
          Text('Settings Screen').fontSize(30)
        }.tabBar("Settings")
      }
    }
    .width('100%')
  }
}

Top Navigation Bar

Tabs({ barPosition: BarPosition.Start })

Side Navigation Bar

Tabs({ barPosition: BarPosition.Start })
  .vertical(true)
  .barWidth(80)
  .barHeight('100%')

Disabling Swipe Gestures

To restrict users from swiping between tabs, use the scrollable property:

.scrollable(false)

Fixed vs Scrollable Tab Modes

.barMode(BarMode.Fixed) // All tabs visible, equal width
.barMode(BarMode.Scrollable) // Tabs scroll horizontally when overflow

Custom Tab Bar Implementation

@Entry
@Component
struct ApplicationInterface {
  @State activeTabIndex: number = 0

  @Builder
  tabBuilder(label: string, index: number, selectedIcon: Resource, normalIcon: Resource) {
    Column() {
      Image(this.activeTabIndex === index ? selectedIcon : normalIcon)
        .size({ width: 24, height: 24 })
      Text(label)
        .fontColor(this.activeTabIndex === index ? Color.Blue : Color.Gray)
    }
    .width('100%')
    .height(48)
    .justifyContent(FlexAlign.Center)
  }

  build() {
    Column() {
      Tabs({ barPosition: BarPosition.Start }) {
        TabContent() {
          Column() {
            Text('Settings Content')
          }
          .width('100%')
          .height('100%')
          .backgroundColor(Color.Yellow)
        }
        .tabBar(this.tabBuilder('Settings', 0, $r('app.media.active_icon'), $r('app.media.inactive_icon')))
      }
    }
    .width('100%')
  }
}

Programmatic Tab Swithcing

When implementing a custom tab bar, you need to handle the tab switching logic manually since the default Tabs component only manages content page transitions.

@Entry
@Component
struct NavigationController {
  @State selectedIndex: number = 1

  @Builder
  tabBuilder(label: string, targetIndex: number) {
    Column() {
      Text(label)
        .fontColor(this.selectedIndex === targetIndex ? '#2563EB' : '#71717A')
    }
  }

  build() {
    Column() {
      Tabs({ barPosition: BarPosition.End }) {
        TabContent() {
          Text('Dashboard Content').fontSize(30)
        }.tabBar(this.tabBuilder('Dashboard', 0))

        TabContent() {
          Text('Analytics Content').fontSize(30)
        }.tabBar(this.tabBuilder('Analytics', 1))

        TabContent() {
          Text('Notifications Content').fontSize(30)
        }.tabBar(this.tabBuilder('Notifications', 2))

        TabContent() {
          Text('Account Content').fontSize(30)
        }.tabBar(this.tabBuilder("Account", 3))
      }
      .animationDuration(300)
      .backgroundColor('#F4F4F5')
      .onChange((index: number) => {
        this.selectedIndex = index
      })
    }
    .width('100%')
  }
}

Using TabsController for External Control

The TabsController prvoides programmatic control over the Tabs component for switching between content pages.

@Entry
@Component
struct DynamicTabs {
  @State activeIndex: number = 2
  private tabController: TabsController = new TabsController()

  @Builder
  tabBuilder(label: string, indexValue: number) {
    Column() {
      Text(label)
        .fontColor(this.activeIndex === indexValue ? '#2563EB' : '#71717A')
    }
  }

  build() {
    Column() {
      Tabs({ barPosition: BarPosition.End, index: this.activeIndex, controller: this.tabController }) {
        TabContent() {
          Text('Feed Content').fontSize(30)
        }.tabBar(this.tabBuilder('Feed', 0))

        TabContent() {
          Text('Explore Content').fontSize(30)
        }.tabBar(this.tabBuilder('Explore', 1))

        TabContent() {
          Text('Bookmarks Content').fontSize(30)
        }.tabBar(this.tabBuilder('Bookmarks', 2))

        TabContent() {
          Text('User Content').fontSize(30)
        }.tabBar(this.tabBuilder("User", 3))
      }
      .animationDuration(300)
      .backgroundColor('#F4F4F5')
      .height(600)
      .onChange((index: number) => {
        this.activeIndex = index
      })

      Button('Update State Index')
        .width('60%')
        .margin({ top: 20 })
        .onClick(() => {
          this.activeIndex = (this.activeIndex + 1) % 4
        })

      Button('Trigger Controller')
        .width('60%')
        .margin({ top: 20 })
        .onClick(() => {
          let nextIndex = (this.activeIndex + 1) % 4
          this.tabController.changeIndex(nextIndex)
        })
    }
    .width('100%')
  }
}

The TabsController allows external buttons or other UI elements to control tab navigation independently from the tab bar itself.

Tags: HarmonyOS ArkTS Tabs Navigation UI Components

Posted on Tue, 23 Jun 2026 17:09:58 +0000 by rich1983