1# Using Tabs (Tabs) 2 3 4When there is a large amount of page information, to enable the user to focus on the currently displayed content, the page content needs to be classified to improve the page space utilization. The [Tabs](../reference/apis-arkui/arkui-ts/ts-container-tabs.md) component can quickly switch between views on a page, improving information search efficiency and reducing the amount of information that users receive at a time. 5 6 7## Basic Layout 8 9 The **Tabs** component consists of two parts: **TabContent** and **TabBar**. **TabContent** is the content page, and **TabBar** is the navigation tab bar. The following figure shows the page structure. The layout varies according to the navigation type. In bottom navigation, top navigation, and side navigation, the navigation tab bar is located at the bottom, top, and edge, respectively. 10 11 **Figure 1** Tabs component layout 12 13 14 15 16>**NOTE** 17> 18> - The **TabContent** component does not support setting of the common width attribute. By default, its width is the same as that of the parent **Tabs** component. 19> 20> - The **TabContent** component does not support setting of the common height attribute. Its height is determined by the height of the parent **Tabs** component and the **TabBar** component. 21 22 23**Tabs** use braces to enclose the tab content, as shown in Figure 2. 24 25 26 **Figure 2** Using Tabs and TabContent 27 28 29 30 31Each **TabContent** component should be mapped to a tab page, which can be configured through the **tabBar** attribute. The following is an example. 32 33```ts 34 TabContent() { 35 Text('Home tab content').fontSize(30) 36 } 37.tabBar('Home') 38``` 39 40 41When setting multiple **TabContent** components, place them in sequence in the **Tabs** component. 42 43```ts 44Tabs() { 45 TabContent() { 46 Text('Home tab content').fontSize(30) 47 } 48 .tabBar('Home') 49 50 TabContent() { 51 Text('Recommended tab content').fontSize(30) 52 } 53 .tabBar('Recommended') 54 55 TabContent() { 56 Text('Discover tab content').fontSize(30) 57 } 58 .tabBar('Discover') 59 60 TabContent() { 61 Text('Me tab content').fontSize(30) 62 } 63 .tabBar("Me") 64} 65``` 66 67 68## Bottom Navigation 69 70Bottom navigation is the most common navigation mode in applications. The bottom navigation bar is located at the bottom of the level-1 page of the application. It enables the user to quickly have a picture of the feature categories the moment they open the application. In addition, it facilitates one-hand operations of the user. Bottom navigation generally exists as a main navigation form of an application, in that it provides convenient access to primary destinations anywhere in the application. 71 72 73 **Figure 3** Bottom navigation bar 74 75 76 77 78You set the position of the navigation bar through the **barPosition** parameter of the **Tabs** component. By default, **barPosition** is set to **BarPosition.Start**, which means that the navigation bar is located on the top. To display the navigation bar at the bottom, set **barPosition** to **BarPosition.End**. 79 80 81```ts 82Tabs({ barPosition: BarPosition.End }) { 83 // TabContent: Home, Discover, Recommended, and Me 84 // ... 85} 86``` 87 88 89## Top Navigation 90 91Top navigation comes in handy when there are many content categories and users need to frequently switch between them. It is usually a further subdivision of the categories in the bottom navigation bar. For example, a theme application may provide a top navigation bar that classifies themes into image, video, and font. 92 93 **Figure 4** Top navigation bar 94 95 96 97 98```ts 99Tabs({ barPosition: BarPosition.Start }) { 100 // TabContent: Following, Video, Game, Digital, Technology, Sports, Movie 101 // ... 102} 103``` 104 105 106## Side Navigation 107 108Side navigation is seldom used in applications. It is more applicable to landscape screens. Because the natural eye movement pattern is from left to right, the side navigation bar is located on the left side by default. 109 110 111 **Figure 5** Side navigation bar 112 113 114 115 116To implement the side navigation bar, set the **vertical** attribute of the **Tabs** component to **true**. By default, **vertical** is set to **false**, indicating that the content page and navigation bar are aligned vertically. 117 118 119 120```ts 121Tabs({ barPosition: BarPosition.Start }) { 122 // TabContent: Home, Discover, Recommended, and Me 123 // ... 124} 125.vertical(true) 126.barWidth(100) 127.barHeight(200) 128``` 129 130 131>**NOTE** 132> 133> - When the **vertical** attribute is set to **false**, the tab bar takes up the whole screen width by default. Set **barWidth** to a proper value. 134> 135> - When the **vertical** attribute is set to **true**, the tab bar takes up the actual content height by default. Set **barWidth** to a proper value. 136 137 138## Restricting the Scrolling of the Navigation Bar 139 140 By default, the navigation bar is scrollable. On some pages that require multi-level classification of content, for example, when both bottom navigation and top navigation are used, the scroll effect of the bottom navigation bar may conflict with that of the top navigation bar. In this case, the scrolling of the bottom navigation bar needs to be restricted to improve user experience. 141 142 **Figure 6** Restricting the scrolling of the bottom navigation bar 143 144 145 146 147The attribute that enables or disables the scrolling is **scrollable**. Its default value is **true**, indicating that scrolling is enabled. To disable the scrolling, set the attribute to **false**. 148 149```ts 150Tabs({ barPosition: BarPosition.End }) { 151 TabContent(){ 152 Column(){ 153 Tabs(){ 154 // Content on the top navigation bar 155 // ... 156 } 157 } 158 .backgroundColor('#ff08a8f1') 159 .width('100%') 160 } 161 .tabBar('Home') 162 163 // Other TabContent content: Discover, Recommended, and Me 164 // ... 165} 166.scrollable(false) 167``` 168 169 170## Fixed Navigation Bar 171 172When the content categories are relatively fixed and not scalable, a fixed navigation bar can be used. For example, it can be used for the bottom navigation bar, which generally contains 3 to 5 categories. The fixed navigation bar cannot be scrolled or dragged. The tab bar width is evenly distributed among the categories. 173 174 175 **Figure 7** Fixed navigation bar 176 177 178 179 180To use a fixed navigation bar, set the **barMode** attribute of the **Tabs** component to **barMode.Fixed** (default). 181 182```ts 183Tabs({ barPosition: BarPosition.End }) { 184 // TabContent: Home, Discover, Recommended, and Me 185 // ... 186} 187.barMode(BarMode.Fixed) 188``` 189 190 191## Scrollable Navigation Bar 192 193The top navigation bar or side navigation bar can be set to be scrollable if the screen width cannot fully accommodate all the tabs. With a scrollable navigation bar, users can reveal tabs beyond the visible area by touching or swiping on the navigation bar. 194 195 196 **Figure 8** Scrollable navigation bar 197 198 199 200 201To use a scrollable navigation bar, set the **barMode** attribute of the **Tabs** component to **BarMode.Scrollable**. 202 203```ts 204Tabs({ barPosition: BarPosition.Start }) { 205 // TabContent: follow, video, game, digital, technology, sports, movie, humanities, art, nature, and military 206 // ... 207} 208.barMode(BarMode.Scrollable) 209``` 210 211 212## Customizing the Navigation Bar 213 214The bottom navigation bar is generally used on the home page of an application. To deliver a more vibrant experience, you can customize the style of the navigation bar, combining use of text and icons to signify the tab content. 215 216 217 **Figure 9** Custom navigation bar 218 219 220 221 222By default, the system uses an underscore (_) to indicate the active tab. For a custom navigation bar, you need to implement the corresponding style to distinguish active tabs from inactive tabs. 223 224 225To customize the navigation bar, use the **tabBar** parameter and pass in to it custom function component styles in **CustomBuilder** mode. In this example, a custom function component **tabBuilder** is declared, and the input parameters include **title** (tab title), **targetIndex** (target index of the tab), **selectedImg** (image for the selected state), and **normalImg** (image for the unselected state). The UI display style is determined based on whether the value of **currentIndex** (index of the active tab) matches that of **targetIndex** (target index of the tab). 226 227```ts 228@State currentIndex: number = 0; 229 230@Builder tabBuilder(title: string, targetIndex: number, selectedImg: Resource, normalImg: Resource) { 231 Column() { 232 Image(this.currentIndex === targetIndex ? selectedImg : normalImg) 233 .size({ width: 25, height: 25 }) 234 Text(title) 235 .fontColor(this.currentIndex === targetIndex ? '#1698CE' : '#6B6B6B') 236 } 237 .width('100%') 238 .height(50) 239 .justifyContent(FlexAlign.Center) 240} 241``` 242 243 244Pass the custom function component to the **tabBar** attribute corresponding to the tab content and transfer the corresponding parameters. 245 246```ts 247TabContent() { 248 Column(){ 249 Text('Me tab content') 250 } 251 .width('100%') 252 .height('100%') 253 .backgroundColor('#007DFF') 254} 255.tabBar(this.tabBuilder('Me', 0, $r('app.media.mine_selected'), $r('app.media.mine_normal'))) 256``` 257 258 259## Switching to a Specified Tab 260 261Non-custom navigation bars follow the default switching logic. If you are using a custom navigation bar, you must manually implement the logic for switching tabs so that when the user switches to a tab, the application displays the corresponding tab page. 262 263 264 **Figure 10** Content page and tab bar not synced 265 266 267 268To sync the content page with the tab bar, use the **onChange** event provided by **Tabs** to listen for changes in the index. Pass the currently active index value to **currentIndex** to enable tab switching. 269 270```ts 271@Entry 272@Component 273struct TabsExample1 { 274 @State currentIndex: number = 2 275 276 @Builder tabBuilder(title: string, targetIndex: number) { 277 Column() { 278 Text(title) 279 .fontColor(this.currentIndex === targetIndex ? '#1698CE' : '#6B6B6B') 280 } 281 } 282 283 build() { 284 Column() { 285 Tabs({ barPosition: BarPosition.End }) { 286 TabContent() { 287 // ... 288 }.tabBar(this.tabBuilder('Home', 0)) 289 290 TabContent() { 291 // ... 292 }.tabBar(this.tabBuilder('Discover', 1)) 293 294 TabContent() { 295 // ... 296 }.tabBar(this.tabBuilder('Recommended', 2)) 297 298 TabContent() { 299 // ... 300 }.tabBar(this.tabBuilder('Me',3)) 301 } 302 .animationDuration(0) 303 .backgroundColor('#F1F3F5') 304 .onChange((index: number) => { 305 this.currentIndex = index 306 }) 307 }.width('100%') 308 } 309} 310``` 311 **Figure 11** Content page and tab bar synced 312 313 314 315To enable switching between content pages and tabs without swiping, you can pass **currentIndex** to the **index** parameter of **Tabs**. By changing the value of **currentIndex**, you can navigate to the content page corresponding to a specific index. Alternatively, use **TabsController**, which is the controller for the **Tabs** component, to manage content page switches. By using the **changeIndex** API of **TabsController**, you can set your application to display the tab content corresponding to the specified index. 316```ts 317@State currentIndex: number = 2 318@State currentAnimationMode: AnimationMode = AnimationMode.CONTENT_FIRST 319private controller: TabsController = new TabsController() 320 321Tabs({ barPosition: BarPosition.End, index: this.currentIndex, controller: this.controller }) { 322 // ... 323} 324.height(600) 325.animationMode(this.currentAnimationMode) 326.onChange((index: number) => { 327 this.currentIndex = index 328}) 329 330Button('Dynamically Change AnimationMode').width('50%').margin({ top: 1 }).height(25) 331 .onClick(()=>{ 332 if (this.currentAnimationMode === AnimationMode.CONTENT_FIRST) { 333 this.currentAnimationMode = AnimationMode.ACTION_FIRST 334 } else if (this.currentAnimationMode === AnimationMode.ACTION_FIRST) { 335 this.currentAnimationMode = AnimationMode.NO_ANIMATION 336 } else if (this.currentAnimationMode === AnimationMode.NO_ANIMATION) { 337 this.currentAnimationMode = AnimationMode.CONTENT_FIRST_WITH_JUMP 338 } else if (this.currentAnimationMode === AnimationMode.CONTENT_FIRST_WITH_JUMP) { 339 this.currentAnimationMode = AnimationMode.ACTION_FIRST_WITH_JUMP 340 } else if (this.currentAnimationMode === AnimationMode.ACTION_FIRST_WITH_JUMP) { 341 this.currentAnimationMode = AnimationMode.CONTENT_FIRST 342 } 343}) 344 345Button('Dynamically Change Index').width('50%').margin({ top: 20 }) 346 .onClick(()=>{ 347 this.currentIndex = (this.currentIndex + 1) % 4 348}) 349 350Button('Change Index via Controller').width('50%').margin({ top: 20 }) 351 .onClick(()=>{ 352 let index = (this.currentIndex + 1) % 4 353 this.controller.changeIndex(index) 354}) 355``` 356 357 **Figure 12** Switching to a specific tab page 358 359 360 361You can use the **onContentWillChange** API of the **Tabs** component to customize the interception callback function. The interception callback function is called when a new page is about to be displayed. If the callback returns **true**, the tab can switch to the new page. If the callback returns **false**, the tab cannot switch to the new page and will remain on the current page. 362 363```ts 364Tabs({ barPosition: BarPosition.End, controller: this.controller, index: this.currentIndex }) { 365 // ... 366 } 367 .onContentWillChange((currentIndex, comingIndex) => { 368 if (comingIndex == 2) { 369 return false 370 } 371 return true 372 }) 373``` 374 **Figure 13** Customizing the page switching interception event 375 376 377<!--Del--> 378## Supporting Aging-Friendly Design 379 380In aging-friendly scenarios with large font sizes, the bottom tab bar offers a dialog box with large fonts for content display. When the component detects a large font setting, it constructs a long-press dialog box based on the configured text and icons. After the user long-presses the tab bar and then swipes in the dialog box to switch to the next tab, the dialog box updates with content of the new tab. Upon releasing, the dialog box closes and the UI switches to the corresponding tab page. 381 382> **NOTE** 383> 384> The dialog box applies only to bottom tab bars, that is, tab bars in the style of **BottomTabBarStyle**. 385 386**Figure 14** Displaying an aging-friendly dialog box by long-pressing the bottom tab bar in an aging-friendly scenario 387 388 389 390```ts 391import { abilityManager, Configuration } from '@kit.AbilityKit'; 392import { BusinessError } from '@kit.BasicServicesKit'; 393import { promptAction, uiAppearance } from '@kit.ArkUI'; 394 395@Entry 396@Component 397struct Demo { 398 @State fontColor: string = '#182431'; 399 @State selectedFontColor: string = '#007DFF'; 400 @State currentIndex: number = 0; 401 @State currentFontSizeScale: string = ''; 402 @State showBuilderTab: boolean = false; 403 @State fontSize: number = 15; 404 private darkModeKey: string[] = Object.keys(uiAppearance.DarkMode).filter( 405 key => typeof uiAppearance.DarkMode[key] === 'number') 406 407 async setFontScale(scale: number): Promise<void> { 408 let configInit: Configuration = { 409 fontSizeScale: scale, 410 }; 411 abilityManager.updateConfiguration(configInit, (err: BusinessError) => { 412 if (err) { 413 console.error(`updateConfiguration fail, err: ${JSON.stringify(err)}`); 414 promptAction.showToast({ message: `scale:${scale}, err:${JSON.stringify(err)}` }) 415 } else { 416 this.currentFontSizeScale = String(scale); 417 if (scale > 1) { 418 this.fontSize = 8; 419 } else { 420 this.fontSize = 15; 421 } 422 console.log('updateConfiguration success.'); 423 promptAction.showToast({ message: `scale:${scale}, updateConfiguration success.` }) 424 } 425 }); 426 } 427 428 darkMode(isDarkMode: boolean): void { 429 let mode: uiAppearance.DarkMode = uiAppearance.DarkMode.ALWAYS_LIGHT; 430 if (isDarkMode) { 431 mode = uiAppearance.DarkMode.ALWAYS_DARK; 432 } 433 if (mode == uiAppearance.getDarkMode()) { 434 console.info(`TitleDarkMode Set ${this.darkModeKey[mode]} successfully.`) 435 return; 436 } 437 try { 438 uiAppearance.setDarkMode(mode).then(() => { 439 console.info(`TitleDarkMode Set ${this.darkModeKey[mode]} successfully.`) 440 }).catch((error: Error) => { 441 console.error(`TitleDarkMode Set ${this.darkModeKey[mode]} failed, ${error.message}`); 442 }); 443 } catch (error) { 444 let message = (error as BusinessError).message; 445 console.error(`TitleDarkMode Set dark-mode failed, ${message}`); 446 } 447 } 448 449 build() { 450 Column() { 451 Column() { 452 Row() { 453 Text(`current fontSizeScale:${this.currentFontSizeScale}`) 454 .margin({ top: 5, bottom: 5 }) 455 .fontSize(this.fontSize) 456 } 457 458 Row() { 459 Button('1.75') 460 .margin({ top: 5, bottom: 5 }) 461 .fontSize(this.fontSize) 462 .width('40%') 463 .onClick(async () => { 464 await this.setFontScale(1.75); 465 }) 466 Button('2') 467 .margin({ top: 5, bottom: 5 }) 468 .fontSize(this.fontSize) 469 .width('40%') 470 .onClick(async () => { 471 await this.setFontScale(2); 472 }) 473 }.margin({ top: 25 }) 474 475 Row() { 476 Button('3.2') 477 .margin({ top: 5, bottom: 5 }) 478 .fontSize(this.fontSize) 479 .width('40%') 480 .onClick(async () => { 481 await this.setFontScale(3.2); 482 }) 483 Button('1') 484 .margin({ top: 5, bottom: 5 }) 485 .fontSize(this.fontSize) 486 .width('40%') 487 .onClick(async () => { 488 await this.setFontScale(1); 489 }) 490 } 491 492 Row() { 493 Button('Dark Mode') 494 .margin({ top: 5, bottom: 5 }) 495 .fontSize(this.fontSize) 496 .width('40%') 497 .onClick(async () => { 498 this.darkMode(true); 499 }) 500 Button('Light Mode') 501 .margin({ top: 5, bottom: 5 }) 502 .fontSize(this.fontSize) 503 .width('40%') 504 .onClick(async () => { 505 this.darkMode(false); 506 }) 507 } 508 }.alignItems(HorizontalAlign.Start) 509 510 Column() { 511 Tabs({ barPosition: BarPosition.End }) { 512 TabContent() { 513 Column().width('100%').height('100%').backgroundColor(Color.Pink) 514 }.tabBar(new BottomTabBarStyle($r('sys.media.ohos_app_icon'), 'OverLength')) 515 TabContent() { 516 Column().width('100%').height('100%').backgroundColor(Color.Yellow) 517 }.tabBar(new BottomTabBarStyle($r('sys.media.ohos_app_icon'), 'SixLine')) 518 TabContent() { 519 Column().width('100%').height('100%').backgroundColor(Color.Blue) 520 }.tabBar(new BottomTabBarStyle($r('sys.media.ohos_app_icon'), 'Blue')) 521 TabContent() { 522 Column().width('100%').height('100%').backgroundColor(Color.Green) 523 }.tabBar(new BottomTabBarStyle($r('sys.media.ohos_app_icon'), 'Green')) 524 } 525 .vertical(false) 526 .scrollable(true) 527 .barMode(BarMode.Fixed) 528 .onChange((index: number) => { 529 console.info(index.toString()) 530 }) 531 .width('100%') 532 .backgroundColor(0xF1F3F5) 533 }.width('80%').height(200) 534 .margin({ top: 200 }) 535 }.width('100%') 536 } 537} 538``` 539<!--DelEnd-->