Read the original article:How to Implement Rudder-style Bottom Navigation?
Problem Description
The question explores how to make the center icon of a bottom navigation bar extend beyond the bar’s height to achieve a “rudder-style” navigation.
Background Knowledge
-
Tabs component is used for switching content views, where
TabBaris the navigation bar andTabContentis the content area. - Rudder-style navigation is an extension of basic bottom navigation, where the center button (usually the core function) visually extends above the navigation bar, while side buttons remain standard.
- margin is a size attribute that sets outer margins. Elevating the margin of a tab button’s container can make it appear raised.
-
offset allows a component to shift from its original layout position. A negative
yvalue moves it upward.
Solution
Both offset and margin properties can achieve rudder-style navigation but require a custom navigation bar.
Option 1: Using the offset Property
- Shift the tab button’s container upward via
offset. - Implementation reference: Rudder-style Bottom Navigation.
Option 2: Using the margin Property
- Elevate the tab button’s container by adjusting its margin.
@Entry
@Component
struct Index {
@State tabArray: Array<number> = [0, 1, 2]
@State focusIndex: number = 0
private controller: TabsController = new TabsController()
@Builder
TabBuilder(tabName: string, tabIndex: number) {
Column({ space: 10 }) {
Stack() {
Row() {
Image($r('app.media.startIcon'))
.width(40).height(40)
Text(tabName).fontSize(14)
.margin({ top: 60, left: -40 })
}
.width(80)
.height(80)
.backgroundColor($r('sys.color.white'))
.justifyContent(FlexAlign.Center)
.border({
radius: {
topLeft: 130,
topRight: 130,
bottomLeft: 40,
bottomRight: 80
},
})
}
.margin({ bottom: tabIndex === 1 ? 60 : 0 })
}
.justifyContent(FlexAlign.Center)
.onClick(() => {
this.controller.changeIndex(tabIndex)
this.focusIndex = tabIndex
})
.backgroundColor(Color.White)
.width('33.34%')
.height(100)
}
build() {
Column() {
Stack({ alignContent: Alignment.BottomStart }) {
Tabs({ barPosition: BarPosition.End, controller: this.controller }) {
ForEach(this.tabArray, (item: number, index: number) => {
TabContent() {
Text('The page ' + item + ' content')
.height('30%')
.width('100%')
.fontSize(30)
.padding({ bottom: 150 })
}
.backgroundColor('#5291FF')
})
}
.barHeight(0)
.animationDuration(100)
.onChange((index: number) => {
console.info('foo change')
this.focusIndex = index
})
// Tab
Row() {
Scroll() {
Row() {
ForEach(this.tabArray, (item: number, index: number) => {
this.TabBuilder('Tab ' + item, index)
})
}
.margin({ top: 20 })
.justifyContent(FlexAlign.SpaceBetween)
.alignItems(VerticalAlign.Bottom)
}
.scrollable(ScrollDirection.Horizontal)
.scrollBar(BarState.Off)
.width('100%')
}
.justifyContent(FlexAlign.SpaceBetween)
.alignItems(VerticalAlign.Bottom)
.width('100%')
}
.width('100%')
}
.height('100%')
}
}
Result:
Summary
- Rudder-style navigation enhances UX by emphasizing the central action.
- Customization via
offsetormarginoffers flexibility in design. - You can check these links for details:
- Tabs Component
- Rudder Navigation Guide
This example supports API Version 20 Release and later versions, HarmonyOS 6.0.0 Release SDK and later versions, and requires DevEco Studio 6.0.0 Release or later for building and running.








