• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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![tabs-layout](figures/tabs-layout.png)
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![tabs-tabscontent](figures/tabs-tabscontent.png)
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![bottom-navigation](figures/bottom-navigation.gif)
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![top-navigation](figures/top-navigation.gif)
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![side-navigation](figures/side-navigation.png)
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![restricted-navigation](figures/restricted-navigation.gif)
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![fixed-navigation](figures/fixed-navigation.gif)
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![scrollable-navigation](figures/scrollable-navigation.gif)
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![custom-navigation-bar](figures/custom-navigation-bar.png)
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![Content Page and Tab Bar Not Synced](figures/tabcontent_tabbar_not_sync.gif)
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![Content Page and Tab Bar Synced](figures/tabcontent_tabbar_sync.gif)
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![Switching to a Specified Tab Page](figures/TabsChange.gif)
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![TabsChange3](figures/TabsChange3.gif)
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![Aging-Friendly Design](figures/tabs11.png)
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-->