• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Tabs
2
3The **\<Tabs>** component is a container component that allows users to switch between content views through tabs. Each tab page corresponds to a content view.
4
5>  **NOTE**
6>
7>  This component is supported since API version 7. Updates will be marked with a superscript to indicate their earliest API version.
8>
9>  Since API version 11, this component supports the safe area attribute by default, with the default attribute value being **expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.BOTTOM]))**. You can override this attribute to change the default behavior. In earlier versions, you need to use the [expandSafeArea](ts-universal-attributes-expand-safe-area.md) attribute to implement the safe area feature.
10
11
12## Child Components
13
14Only the [\<TabContent>](ts-container-tabcontent.md) child component is supported.
15
16>  **NOTE**
17>
18>  If the child component has the **visibility** attribute set to **None** or **Hidden**, it is hidden but takes up space in the layout.
19
20
21## APIs
22
23Tabs(value?: {barPosition?: [BarPosition](#barposition), index?: number, controller?: [TabsController](#tabscontroller)})
24
25**Parameters**
26
27| Name        | Type                             | Mandatory  | Description                                    |
28| ----------- | --------------------------------- | ---- | ---------------------------------------- |
29| barPosition | [BarPosition](#barposition)| No   | Position of the **\<Tabs>** component.<br>Default value: **BarPosition.Start**  |
30| index       | number                            | No   | Index of the currently displayed tab.<br>Default value: **0**<br>**NOTE**<br>A value less than 0 evaluates to the default value.<br>The value ranges from 0 to the number of **\<TabContent>** subnodes minus 1.<br>When the tab is switched by changing the index, the tab switching animation does not take effect. When **changeindex** of **TabController** is used for tab switching, the tab switching animation is enabled by default. You can disable the animation by setting **animationDuration** to **0**.<br>Since API version 10, this parameter supports [$$](../../quick-start/arkts-two-way-sync.md) for two-way binding of variables.|
31| controller  | [TabsController](#tabscontroller) | No   | Tab controller.                              |
32
33## BarPosition
34
35| Name   | Description                                      |
36| ----- | ---------------------------------------- |
37| Start | If the **vertical** attribute is set to **true**, the tab is on the left of the container. If the **vertical** attribute is set to **false**, the tab is on the top of the container.|
38| End   | If the **vertical** attribute is set to **true**, the tab is on the right of the container. If the **vertical** attribute is set to **false**, the tab is at the bottom of the container.|
39
40
41## Attributes
42
43In addition to the [universal attributes](ts-universal-attributes-size.md), the following attributes are supported.
44
45| Name                              | Type                                    | Description                                      |
46| -------------------------------- | ---------------------------------------- | ---------------------------------------- |
47| vertical                         | boolean                                  | Whether to use vertical tabs. The value **true** means to use vertical tabs, and **false** means to use horizontal tabs.<br>Default value: **false**|
48| scrollable                       | boolean                                  | Whether the tabs are scrollable. The value **true** means that the tabs are scrollable, and **false** means the opposite.<br>Default value: **true**|
49| barMode                          | [BarMode](#barmode),[ScrollableBarModeOptions](#scrollablebarmodeoptions10) | Tab bar layout mode. **BarMode** is mandatory, and **ScrollableBarModeOptions** is optional. For details, see **BarMode** and **ScrollableBarModeOptions**. Since API version 10, the optional **ScrollableBarModeOptions** parameter is supported. It is effective only when the tab bar is in scrollable mode.<br>Default value: **BarMode.Fixed**|
50| barWidth                         | number \| Length<sup>8+</sup>  | Width of the tab bar.<br>The default value varies.<br>If the tab bar has the **vertical** attribute set to **false** and does not have [SubTabBarStyle](ts-container-tabcontent.md#subtabbarstyle9) or [BottomTabBarStyle](ts-container-tabcontent.md#bottomtabbarstyle9) specified, the default value is the width of the **\<Tabs>** component.<br>If the tab bar has the **vertical** attribute set to **true** and does not have [SubTabBarStyle](ts-container-tabcontent.md#subtabbarstyle9) or [BottomTabBarStyle](ts-container-tabcontent.md#bottomtabbarstyle9) specified, the default value is **56vp**.<br>If the tab bar has the **vertical** attribute set to **false** and **SubTabbarStyle** specified, the default value is the width of the **\<Tabs>** component.<br>If the tab bar has the **vertical** attribute set to **true** and **SubTabbarStyle** specified, the default value is **56vp**.<br>If the tab bar has the **vertical** attribute set to **true** and **BottomTabbarStyle** specified, the default value is **96vp**.<br>If the tab bar has the **vertical** attribute set to **false** and **BottomTabbarStyle** specified, the default value is the width of the **\<Tabs>** component.<br>**NOTE**<br><br>A value less than 0 or greater than the width of the **\<Tabs>** component evaluates to the default value.|
51| barHeight                        | number \| Length<sup>8+</sup>  | Height of the tab bar.<br>The default value varies.<br>If the tab bar has the **vertical** attribute set to **false** and does not have a style specified, the default value is **56vp**.<br>If the tab bar has the **vertical** attribute set to **true** and does not have a style specified, the default value is the height of the **\<Tabs>** component.<br>If the tab bar has the **vertical** attribute set to **false** and **SubTabbarStyle** specified, the default value is **56vp**.<br>If the tab bar has the **vertical** attribute set to **true** and **SubTabbarStyle** specified, the default value is the height of the **\<Tabs>** component.<br>If the tab bar has the **vertical** attribute set to **true** and **BottomTabbarStyle** specified, the default value is the height of the **\<Tabs>** component.<br>If the tab bar has the **vertical** attribute set to **false** and **BottomTabbarStyle** specified, the default value is **56vp**.<br>**NOTE**<br><br>A value less than 0 or greater than the height of the **\<Tabs>** component evaluates to the default value.|
52| animationDuration                | number                                   | Length of time required to complete the tab switching animation that is initiated by clicking a specific tab.<br>The default value varies.<br>API version 10 and earlier versions: If this parameter is not set, the default value 0 ms is used, which means that no tab switching animation is displayed when a specific tab is clicked. If this parameter is set to a value less than 0, the default value 300 ms is used.<br>API version 11 and later versions: If this parameter is set to a value less than 0 or is not set, and the tab bar is set to **BottomTabBarStyle**, the default value 0 ms is used. If the tab bar is set to other styles, the default value 300 ms is used.<br>**NOTE**<br><br>This parameter cannot be set in percentage.|
53| divider<sup>10+</sup>            | [DividerStyle](#dividerstyle10) \| null | Whether the divider is displayed for the **\<TabBar>** and **\<TabContent>** components and the divider style. By default, the divider is not displayed.<br> **DividerStyle**: divider style.<br> **null**: The divider is not displayed.|
54| fadingEdge<sup>10+</sup>         | boolean                                  | Whether the tab fades out when it exceeds the container width.<br>Default value: **true**<br>**NOTE**<br>It is recommended that this attribute be used together with the **barBackgroundColor** attribute. If the **barBackgroundColor** attribute is not defined, the tab fades out in white when it exceeds the container width by default.|
55| barOverlap<sup>10+</sup>         | boolean                                  | Whether the tab bar is superimposed on the **\<TabContent>** component after having its background blurred.<br>Default value: **false**|
56| barBackgroundColor<sup>10+</sup> | [ResourceColor](ts-types.md#resourcecolor) | Background color of the tab bar.<br>Default value: transparent              |
57| barGridAlign<sup>10+</sup> | [BarGridColumnOptions](#bargridcolumnoptions10) | Visible area of the tab bar in grid mode. For details, see **BarGridColumnOptions**. This attribute is effective only in horizontal mode. It is not applicable to [XS, XL, and XXL devices](../../ui/arkts-layout-development-grid-layout.md#grid-breakpoints).             |
58
59## DividerStyle<sup>10+</sup>
60
61| Name         | Type                                    | Mandatory  | Description                                      |
62| ----------- | ---------------------------------------- | ---- | ---------------------------------------- |
63| strokeWidth | [Length](ts-types.md#length)             | Yes   | Width of the divider. It cannot be set in percentage.                       |
64| color       | [ResourceColor](ts-types.md#resourcecolor) | No   | Color of the divider.<br>Default value: **#33182431**               |
65| startMargin | [Length](ts-types.md#length)             | No   | Distance between the divider and the top of the sidebar. It cannot be set in percentage.<br>Default value: **0.0**<br>Unit: vp|
66| endMargin   | [Length](ts-types.md#length)             | No   | Distance between the divider and the bottom of the sidebar. It cannot be set in percentage.<br>Default value: **0.0**<br>Unit: vp|
67
68## BarGridColumnOptions<sup>10+</sup>
69
70| Name         | Type                                    | Mandatory  | Description                                      |
71| ----------- | ---------------------------------------- | ---- | ---------------------------------------- |
72| margin | [Dimension](ts-types.md#dimension10)             | No   | Column margin in grid mode. It cannot be set in percentage.<br>Default value: **24.0**<br>Unit: vp                       |
73| gutter      | [Dimension](ts-types.md#dimension10) | No   | Column gutter (that is, gap between columns) in grid mode. It cannot be set in percentage.<br>Default value: **24.0**<br>Unit: vp                    |
74| sm | number            | No   | Number of columns occupied by a tab on a screen whose width is greater than or equal to 320 vp but less than 600 vp.<br>The value must be a non-negative even number. The default value is **-1**, indicating that the tab takes up the entire width of the tab bar.|
75| md   | number          | No   | Number of columns occupied by a tab on a screen whose width is greater than or equal to 600 vp but less than 800 vp.<br>The value must be a non-negative even number. The default value is **-1**, indicating that the tab takes up the entire width of the tab bar.|
76| lg   | number           | No   | Number of columns occupied by a tab on a screen whose width is greater than or equal to 840 vp but less than 1024 vp.<br>The value must be a non-negative even number. The default value is **-1**, indicating that the tab takes up the entire width of the tab bar.|
77
78## ScrollableBarModeOptions<sup>10+</sup>
79
80| Name         | Type                                    | Mandatory  | Description                                      |
81| ----------- | ---------------------------------------- | ---- | ---------------------------------------- |
82| margin | [Dimension](ts-types.md#dimension10)          | No   | Left and right margin of the tab bar in scrollable mode. It cannot be set in percentage.<br>Default value: **0.0**<br>Unit: vp                   |
83| nonScrollableLayoutStyle      | [LayoutStyle](#layoutstyle10) | No   | Tab layout mode of the tab bar when not scrolling in scrollable mode.<br>Default value: **LayoutStyle.ALWAYS_CENTER**          |
84
85## BarMode
86
87| Name        | Description                                      |
88| ---------- | ---------------------------------------- |
89| Scrollable | The width of each tab is determined by the actual layout. The tabs are scrollable in the following case: In horizontal layout, the total width exceeds the tab bar width; in horizontal layout, the total height exceeds the tab bar height.|
90| Fixed      | The width of each tab is determined by equally dividing the number of tabs by the bar width (or bar height in the vertical layout).|
91
92## LayoutStyle<sup>10+</sup>
93
94| Name        | Description                                      |
95| ---------- | ---------------------------------------- |
96| ALWAYS_CENTER | When the tab content exceeds the tab bar width, the tabs are scrollable.<br>Otherwise, the tabs are compactly centered and not scrollable.|
97| ALWAYS_AVERAGE_SPLITE      | When the tab content exceeds the tab bar width, the tabs are scrollable.<br>Otherwise, the tabs are not scrollable, and the tab bar width is distributed evenly between all tabs.<br>This option is valid only in horizontal mode, and is equivalent to **LayoutStyle.ALWAYS_CENTER** otherwise.|
98| SPACE_BETWEEN_OR_CENTER      | When the tab content exceeds the tab bar width, the tabs are scrollable.<br>When the tab content exceeds half of the tab bar width but still within the tab bar width, the tabs are compactly centered and not scrollable.<br>When the tab content does not exceed half of the tab bar width, the tabs are centered within half of the tab bar width, with even spacing between, and not scrollable.|
99
100## Events
101
102In addition to the [universal events](ts-universal-events-click.md), the following events are supported.
103
104| Name                                      | Description                                    |
105| ---------------------------------------- | ---------------------------------------- |
106| onChange(event: (index: number) =&gt; void) | Triggered when a tab is switched.<br>- **index**: index of the active tab. The index starts from 0.<br>This event is triggered when any of the following conditions is met:<br>1. The **\<TabContent>** component supports sliding, and the user slides on the tab bar.<br>2. The [Controller](#tabscontroller) API is called.<br>3. The attribute value is updated using a [state variable](../../quick-start/arkts-state.md).<br>4. A tab is clicked.|
107| onTabBarClick(event: (index: number) =&gt; void)<sup>10+</sup> | Triggered when a tab is clicked.<br>- **index**: index of the clicked tab. The index starts from 0.|
108| onAnimationStart<sup>11+</sup>(handler: (index: number, targetIndex: number, event: [TabsAnimationEvent](ts-types.md#tabsanimationevent11)) => void) | Triggered when the tab switching animation starts.<br>- **index**: index of the currently displayed element.<br>- **targetIndex**: index of the target element to switch to.<br>- **event**: animation-related information, including the offset of the currently displayed element and target element relative to the start position of the **\<Tabs>** along the main axis, and the hands-off velocity.<br>**NOTE**<br>The **index** parameter indicates the index before the animation starts (not the one after).|
109| onAnimationEnd<sup>11+</sup>(handler: (index: number, event: [TabsAnimationEvent](ts-types.md#tabsanimationevent11)) => void) | Triggered when the tab switching animation ends.<br>- **index**: index of the currently displayed element.<br>- **event**: animation-related information, including the offset of the currently displayed element relative to the start position of the **\<Tabs>** along the main axis.<br>**NOTE**<br>This event is triggered when the tab switching animation ends, whether it is caused by gesture interruption or not. The **index** parameter indicates the index after the animation ends.|
110| onGestureSwipe<sup>11+</sup>(handler: (index: number, event: [TabsAnimationEvent](ts-types.md#tabsanimationevent11)) => void) | Triggered on a frame-by-frame basis when the tab is switched by a swipe.<br>- **index**: index of the currently displayed element.<br>- **event**: animation-related information, including the offset of the currently displayed element relative to the start position of the **\<Tabs>** along the main axis.|
111| customContentTransition<sup>11+</sup>(delegate: (from: number, to: number) => [TabContentAnimatedTransition](ts-types.md#tabcontentanimatedtransition11) \| undefined) | Custom tab switching animation. **from** and **to** indicate the return values.<br> - **from**: index of the currently displayed tab before the animation starts.<br>- **to**: index of the target tab before the animation starts.<br> Instructions:<br>  1. When the custom tab switching animation is used, the default switching animation of the **\<Tabs>** component is disabled, and tabs cannot be switched through swiping.<br> 2. The value **undefined** means not to use the custom tab switching animation, in which case the default switching animation is used.<br> 3. The custom tab switching animation cannot be interrupted.<br> 4. Currently, the custom tab switching animation can be triggered only by clicking a tab or by calling the **TabsController.changeIndex()** API.<br> 5. When the custom tab switching animation is used, the **\<Tabs>** component supports all events except **onGestureSwipe**.<br> 6. Notes about the **onChange** and **onAnimationEnd** events: If the second custom animation is triggered during the execution of the first custom animation, the **onChange** and **onAnimationEnd** events of the first custom animation will be triggered when the second custom animation starts.<br> 7. When the custom animation is used, the stack layout is used for pages involved in the animation. If the **zIndex** attribute is not set for related pages, the **zIndex** values of all pages are the same. In this case, the pages are rendered in the order in which they are added to the component tree (that is, the sequence of page indexes). In light of this, to control the rendering levels of pages, set the **zIndex** attribute of the pages.<br>|
112
113## TabsController
114
115Defines a tab controller, which is used to control switching of tabs. One **TabsController** cannot control multiple **\<Tabs>** components.
116
117### Objects to Import
118
119```ts
120let controller: TabsController = new TabsController()
121```
122
123### changeIndex
124
125changeIndex(value: number): void
126
127Switches to the specified tab.
128
129**Parameters**
130
131| Name  | Type  | Mandatory  | Description                                    |
132| ----- | ------ | ---- | ---------------------------------------- |
133| value | number | Yes   | Index of the tab. The value starts from 0.<br>**NOTE**<br>If this parameter is set to a value less than 0 or greater than the maximum number, the default value **0** is used.|
134
135
136## Example
137
138### Example 1
139
140```ts
141// xxx.ets
142@Entry
143@Component
144struct TabsExample {
145  @State fontColor: string = '#182431'
146  @State selectedFontColor: string = '#007DFF'
147  @State currentIndex: number = 0
148  private controller: TabsController = new TabsController()
149
150  @Builder tabBuilder(index: number, name: string) {
151    Column() {
152      Text(name)
153        .fontColor(this.currentIndex === index ? this.selectedFontColor : this.fontColor)
154        .fontSize(16)
155        .fontWeight(this.currentIndex === index ? 500 : 400)
156        .lineHeight(22)
157        .margin({ top: 17, bottom: 7 })
158      Divider()
159        .strokeWidth(2)
160        .color('#007DFF')
161        .opacity(this.currentIndex === index ? 1 : 0)
162    }.width('100%')
163  }
164
165  build() {
166    Column() {
167      Tabs({ barPosition: BarPosition.Start, index: this.currentIndex, controller: this.controller }) {
168        TabContent() {
169          Column().width('100%').height('100%').backgroundColor('#00CB87')
170        }.tabBar(this.tabBuilder(0, 'green'))
171
172        TabContent() {
173          Column().width('100%').height('100%').backgroundColor('#007DFF')
174        }.tabBar(this.tabBuilder(1, 'blue'))
175
176        TabContent() {
177          Column().width('100%').height('100%').backgroundColor('#FFBF00')
178        }.tabBar(this.tabBuilder(2, 'yellow'))
179
180        TabContent() {
181          Column().width('100%').height('100%').backgroundColor('#E67C92')
182        }.tabBar(this.tabBuilder(3, 'pink'))
183      }
184      .vertical(false)
185      .barMode(BarMode.Fixed)
186      .barWidth(360)
187      .barHeight(56)
188      .animationDuration(400)
189      .onChange((index: number) => {
190        this.currentIndex = index
191      })
192      .width(360)
193      .height(296)
194      .margin({ top: 52 })
195      .backgroundColor('#F1F3F5')
196    }.width('100%')
197  }
198}
199```
200
201![tabs2](figures/tabs2.gif)
202
203### Example 2
204
205```ts
206// xxx.ets
207@Entry
208@Component
209struct TabsDivider1 {
210  private controller1: TabsController = new TabsController()
211  @State dividerColor: string = 'red'
212  @State strokeWidth: number = 2
213  @State startMargin: number = 0
214  @State endMargin: number = 0
215  @State nullFlag: boolean = false
216
217  build() {
218    Column() {
219      Tabs({ controller: this.controller1 }) {
220        TabContent() {
221          Column().width('100%').height('100%').backgroundColor(Color.Pink)
222        }.tabBar('pink')
223
224        TabContent() {
225          Column().width('100%').height('100%').backgroundColor(Color.Yellow)
226        }.tabBar('yellow')
227
228        TabContent() {
229          Column().width('100%').height('100%').backgroundColor(Color.Blue)
230        }.tabBar('blue')
231
232        TabContent() {
233          Column().width('100%').height('100%').backgroundColor(Color.Green)
234        }.tabBar('green')
235
236        TabContent() {
237          Column().width('100%').height('100%').backgroundColor(Color.Red)
238        }.tabBar('red')
239      }
240      .vertical(true)
241      .scrollable(true)
242      .barMode(BarMode.Fixed)
243      .barWidth(70)
244      .barHeight(200)
245      .animationDuration(400)
246      .onChange((index: number) => {
247        console.info(index.toString())
248      })
249      .height('200vp')
250      .margin({ bottom: '12vp' })
251      .divider(this.nullFlag ? null : {
252        strokeWidth: this.strokeWidth,
253        color: this.dividerColor,
254        startMargin: this.startMargin,
255        endMargin: this.endMargin
256      })
257
258      Button ('Regular Divider').width('100%').margin({ bottom: '12vp'})
259        .onClick(() => {
260          this.nullFlag = false;
261          this.strokeWidth = 2;
262          this.dividerColor = 'red';
263          this.startMargin = 0;
264          this.endMargin = 0;
265        })
266      Button('Empty Divider').width('100%').margin({ bottom: '12vp' })
267        .onClick(() => {
268          this.nullFlag = true
269        })
270      Button('Change to Blue').width('100%').margin({ bottom: '12vp'})
271        .onClick(() => {
272          this.dividerColor = 'blue'
273        })
274      Button('Increase Width').width('100%').margin({ bottom: '12vp' })
275        .onClick(() => {
276          this.strokeWidth += 2
277        })
278      Button('Decrease Width').width('100%').margin({ bottom: '12vp'})
279        .onClick(() => {
280          if (this.strokeWidth > 2) {
281            this.strokeWidth -= 2
282          }
283        })
284      Button ('Increase Top Margin').width ('100%').margin ({ bottom:'12vp'})
285        .onClick(() => {
286          this.startMargin += 2
287        })
288      Button ('Decrease Top Margin').width ('100%').margin ({ bottom:'12vp' })
289        .onClick(() => {
290          if (this.startMargin > 2) {
291            this.startMargin -= 2
292          }
293        })
294      Button ('Increase Bottom Margin').width ('100%').margin ({ bottom:'12vp'})
295        .onClick(() => {
296          this.endMargin += 2
297        })
298      Button ('Decrease Bottom Margin').width ('100%').margin ({ bottom:'12vp' })
299        .onClick(() => {
300          if (this.endMargin > 2) {
301            this.endMargin -= 2
302          }
303        })
304    }.padding({ top: '24vp', left: '24vp', right: '24vp' })
305  }
306}
307```
308
309![tabs3](figures/tabs3.gif)
310
311### Example 3
312
313```ts
314// xxx.ets
315@Entry
316@Component
317struct TabsOpaque {
318  @State message: string = 'Hello World'
319  private controller: TabsController = new TabsController()
320  private controller1: TabsController = new TabsController()
321  @State selfFadingFade: boolean = true;
322
323  build() {
324    Column() {
325      Button (Set Tab to Fade').width ('100%').margin ({bottom: '12vp'})
326        .onClick((event?: ClickEvent) => {
327          this.selfFadingFade = true;
328        })
329      Button (Set Tab Not to Fade').width ('100%').margin ({bottom: '12vp'})
330        .onClick((event?: ClickEvent) => {
331          this.selfFadingFade = false;
332        })
333      Tabs({ barPosition: BarPosition.End, controller: this.controller }) {
334        TabContent() {
335          Column().width('100%').height('100%').backgroundColor(Color.Pink)
336        }.tabBar('pink')
337
338        TabContent() {
339          Column().width('100%').height('100%').backgroundColor(Color.Yellow)
340        }.tabBar('yellow')
341
342        TabContent() {
343          Column().width('100%').height('100%').backgroundColor(Color.Blue)
344        }.tabBar('blue')
345
346        TabContent() {
347          Column().width('100%').height('100%').backgroundColor(Color.Green)
348        }.tabBar('green')
349
350        TabContent() {
351          Column().width('100%').height('100%').backgroundColor(Color.Green)
352        }.tabBar('green')
353
354        TabContent() {
355          Column().width('100%').height('100%').backgroundColor(Color.Green)
356        }.tabBar('green')
357
358        TabContent() {
359          Column().width('100%').height('100%').backgroundColor(Color.Green)
360        }.tabBar('green')
361
362        TabContent() {
363          Column().width('100%').height('100%').backgroundColor(Color.Green)
364        }.tabBar('green')
365      }
366      .vertical(false)
367      .scrollable(true)
368      .barMode(BarMode.Scrollable)
369      .barHeight(80)
370      .animationDuration(400)
371      .onChange((index: number) => {
372        console.info(index.toString())
373      })
374      .fadingEdge(this.selfFadingFade)
375      .height('30%')
376      .width('100%')
377
378      Tabs({ barPosition: BarPosition.Start, controller: this.controller1 }) {
379        TabContent() {
380          Column().width('100%').height('100%').backgroundColor(Color.Pink)
381        }.tabBar('pink')
382
383        TabContent() {
384          Column().width('100%').height('100%').backgroundColor(Color.Yellow)
385        }.tabBar('yellow')
386
387        TabContent() {
388          Column().width('100%').height('100%').backgroundColor(Color.Blue)
389        }.tabBar('blue')
390
391        TabContent() {
392          Column().width('100%').height('100%').backgroundColor(Color.Green)
393        }.tabBar('green')
394
395        TabContent() {
396          Column().width('100%').height('100%').backgroundColor(Color.Green)
397        }.tabBar('green')
398
399        TabContent() {
400          Column().width('100%').height('100%').backgroundColor(Color.Green)
401        }.tabBar('green')
402      }
403      .vertical(true)
404      .scrollable(true)
405      .barMode(BarMode.Scrollable)
406      .barHeight(200)
407      .barWidth(80)
408      .animationDuration(400)
409      .onChange((index: number) => {
410        console.info(index.toString())
411      })
412      .fadingEdge(this.selfFadingFade)
413      .height('30%')
414      .width('100%')
415    }
416    .padding({ top: '24vp', left: '24vp', right: '24vp' })
417  }
418}
419```
420
421![tabs4](figures/tabs4.gif)
422
423### Example 4
424
425```ts
426// xxx.ets
427@Entry
428@Component
429struct barBackgroundColorTest {
430  private controller: TabsController = new TabsController()
431  @State barOverlap: boolean = true;
432  @State barBackgroundColor: string = '#88888888';
433
434  build() {
435    Column() {
436      Button ("Change barOverlap").width ('100%').margin ({ bottom:'12vp'})
437        .onClick((event?: ClickEvent) => {
438          if (this.barOverlap) {
439            this.barOverlap = false;
440          } else {
441            this.barOverlap = true;
442          }
443        })
444
445      Tabs({ barPosition: BarPosition.Start, index: 0, controller: this.controller }) {
446        TabContent() {
447          Column() {
448            Text(`barOverlap ${this.barOverlap}`).fontSize(16).margin({ top: this.barOverlap ? '56vp' : 0 })
449            Text(`barBackgroundColor ${this.barBackgroundColor}`).fontSize(16)
450          }.width('100%').width('100%').height('100%')
451          .backgroundColor(Color.Pink)
452        }
453        .tabBar(new BottomTabBarStyle($r('sys.media.ohos_app_icon'), "1"))
454
455        TabContent() {
456          Column() {
457            Text(`barOverlap ${this.barOverlap}`).fontSize(16).margin({ top: this.barOverlap ? '56vp' : 0 })
458            Text(`barBackgroundColor ${this.barBackgroundColor}`).fontSize(16)
459          }.width('100%').width('100%').height('100%')
460          .backgroundColor(Color.Yellow)
461        }
462        .tabBar(new BottomTabBarStyle($r('sys.media.ohos_app_icon'), "2"))
463
464        TabContent() {
465          Column() {
466            Text(`barOverlap ${this.barOverlap}`).fontSize(16).margin({ top: this.barOverlap ? '56vp' : 0 })
467            Text(`barBackgroundColor ${this.barBackgroundColor}`).fontSize(16)
468          }.width('100%').width('100%').height('100%')
469          .backgroundColor(Color.Green)
470        }
471        .tabBar(new BottomTabBarStyle($r('sys.media.ohos_app_icon'), "3"))
472      }
473      .vertical(false)
474      .barMode(BarMode.Fixed)
475      .height('60%')
476      .barOverlap(this.barOverlap)
477      .scrollable(true)
478      .animationDuration(10)
479      .barBackgroundColor(this.barBackgroundColor)
480    }
481    .height(500)
482    .padding({ top: '24vp', left: '24vp', right: '24vp' })
483  }
484}
485```
486
487![tabs5](figures/tabs5.gif)
488
489
490### Example 5
491
492```ts
493// xxx.ets
494@Entry
495@Component
496struct TabsExample5 {
497  private controller: TabsController = new TabsController()
498  @State gridMargin: number = 10
499  @State gridGutter: number = 10
500  @State sm: number = -2
501  @State clickedContent: string = "";
502
503  build() {
504    Column() {
505      Row() {
506        Button("gridMargin+10 " + this.gridMargin)
507          .width('47%')
508          .height(50)
509          .margin({ top: 5 })
510          .onClick((event?: ClickEvent) => {
511            this.gridMargin += 10
512          })
513          .margin({ right: '6%', bottom: '12vp' })
514        Button("gridMargin-10 " + this.gridMargin)
515          .width('47%')
516          .height(50)
517          .margin({ top: 5 })
518          .onClick((event?: ClickEvent) => {
519            this.gridMargin -= 10
520          })
521          .margin({ bottom: '12vp' })
522      }
523
524      Row() {
525        Button("gridGutter+10 " + this.gridGutter)
526          .width('47%')
527          .height(50)
528          .margin({ top: 5 })
529          .onClick((event?: ClickEvent) => {
530            this.gridGutter += 10
531          })
532          .margin({ right: '6%', bottom: '12vp' })
533        Button("gridGutter-10 " + this.gridGutter)
534          .width('47%')
535          .height(50)
536          .margin({ top: 5 })
537          .onClick((event?: ClickEvent) => {
538            this.gridGutter -= 10
539          })
540          .margin({ bottom: '12vp' })
541      }
542
543      Row() {
544        Button("sm+2 " + this.sm)
545          .width('47%')
546          .height(50)
547          .margin({ top: 5 })
548          .onClick((event?: ClickEvent) => {
549            this.sm += 2
550          })
551          .margin({ right: '6%' })
552        Button("sm-2 " + this.sm).width('47%').height(50).margin({ top: 5 })
553          .onClick((event?: ClickEvent) => {
554            this.sm -= 2
555          })
556      }
557
558      Text ("Tab clicks: "+ this.clickedContent).width ('100%').height (200).margin ({ top: 5 })
559
560
561      Tabs({ barPosition: BarPosition.End, controller: this.controller }) {
562        TabContent() {
563          Column().width('100%').height('100%').backgroundColor(Color.Pink)
564        }.tabBar(BottomTabBarStyle.of($r("sys.media.ohos_app_icon"), "1"))
565
566        TabContent() {
567          Column().width('100%').height('100%').backgroundColor(Color.Green)
568        }.tabBar(BottomTabBarStyle.of($r("sys.media.ohos_app_icon"), "2"))
569
570        TabContent() {
571          Column().width('100%').height('100%').backgroundColor(Color.Blue)
572        }.tabBar(BottomTabBarStyle.of($r("sys.media.ohos_app_icon"), "3"))
573      }
574      .width('350vp')
575      .animationDuration(300)
576      .height('60%')
577      .barGridAlign({ sm: this.sm, margin: this.gridMargin, gutter: this.gridGutter })
578      .backgroundColor(0xf1f3f5)
579      .onTabBarClick((index: number) => {
580        this.clickedContent += "index " + index + " was clicked\n";
581      })
582    }
583    .width('100%')
584    .height(500)
585    .margin({ top: 5 })
586    .padding('10vp')
587  }
588}
589```
590
591![tabs5](figures/tabs6.gif)
592
593### Example 6
594
595```ts
596// xxx.ets
597@Entry
598@Component
599struct TabsExample6 {
600  private controller: TabsController = new TabsController()
601  @State scrollMargin: number = 0
602  @State layoutStyle: LayoutStyle = LayoutStyle.ALWAYS_CENTER
603  @State text: string = "Text"
604
605  build() {
606    Column() {
607      Row() {
608        Button("scrollMargin+10 " + this.scrollMargin)
609          .width('47%')
610          .height(50)
611          .margin({ top: 5 })
612          .onClick((event?: ClickEvent) => {
613            this.scrollMargin += 10
614          })
615          .margin({ right: '6%', bottom: '12vp' })
616        Button("scrollMargin-10 " + this.scrollMargin)
617          .width('47%')
618          .height(50)
619          .margin({ top: 5 })
620          .onClick((event?: ClickEvent) => {
621            this.scrollMargin -= 10
622          })
623          .margin({ bottom: '12vp' })
624      }
625
626      Row() {
627        Button ("Add Text")
628          .width('47%')
629          .height(50)
630          .margin({ top: 5 })
631          .onClick((event?: ClickEvent) => {
632            this.text += 'Add Text'
633          })
634          .margin({ right: '6%', bottom: '12vp' })
635        Button ("Reset Text")
636          .width('47%')
637          .height(50)
638          .margin({ top: 5 })
639          .onClick((event?: ClickEvent) => {
640            this.text = "Text"
641          })
642          .margin({ bottom: '12vp' })
643      }
644
645      Row() {
646        Button("layoutStyle.ALWAYS_CENTER")
647          .width('100%')
648          .height(50)
649          .margin({ top: 5 })
650          .fontSize(15)
651          .onClick((event?: ClickEvent) => {
652            this.layoutStyle = LayoutStyle.ALWAYS_CENTER;
653          })
654          .margin({ bottom: '12vp' })
655      }
656
657      Row() {
658        Button("layoutStyle.ALWAYS_AVERAGE_SPLIT")
659          .width('100%')
660          .height(50)
661          .margin({ top: 5 })
662          .fontSize(15)
663          .onClick((event?: ClickEvent) => {
664            this.layoutStyle = LayoutStyle.ALWAYS_AVERAGE_SPLIT;
665          })
666          .margin({ bottom: '12vp' })
667      }
668
669      Row() {
670        Button("layoutStyle.SPACE_BETWEEN_OR_CENTER")
671          .width('100%')
672          .height(50)
673          .margin({ top: 5 })
674          .fontSize(15)
675          .onClick((event?: ClickEvent) => {
676            this.layoutStyle = LayoutStyle.SPACE_BETWEEN_OR_CENTER;
677          })
678          .margin({ bottom: '12vp' })
679      }
680
681      Tabs({ barPosition: BarPosition.End, controller: this.controller }) {
682        TabContent() {
683          Column().width('100%').height('100%').backgroundColor(Color.Pink)
684        }.tabBar(SubTabBarStyle.of(this.text))
685
686        TabContent() {
687          Column().width('100%').height('100%').backgroundColor(Color.Green)
688        }.tabBar(SubTabBarStyle.of(this.text))
689
690        TabContent() {
691          Column().width('100%').height('100%').backgroundColor(Color.Blue)
692        }.tabBar(SubTabBarStyle.of(this.text))
693      }
694      .animationDuration(300)
695      .height('60%')
696      .backgroundColor(0xf1f3f5)
697      .barMode(BarMode.Scrollable, { margin: this.scrollMargin, nonScrollableLayoutStyle: this.layoutStyle })
698    }
699    .width('100%')
700    .height(500)
701    .margin({ top: 5 })
702    .padding('24vp')
703  }
704}
705```
706
707![tabs5](figures/tabs7.gif)
708
709### Example 7
710
711```ts
712// xxx.ets
713@Entry
714@Component
715struct TabsCustomAnimationExample {
716  @State useCustomAnimation: boolean = true
717  @State tabContent0Scale: number = 1.0
718  @State tabContent1Scale: number = 1.0
719  @State tabContent0Opacity: number = 1.0
720  @State tabContent1Opacity: number = 1.0
721  @State tabContent2Opacity: number = 1.0
722  tabsController: TabsController = new TabsController()
723  private firstTimeout: number = 3000
724  private secondTimeout: number = 5000
725  private first2secondDuration: number = 3000
726  private second2thirdDuration: number = 5000
727  private first2thirdDuration: number = 2000
728  private baseCustomAnimation: (from: number, to: number) => TabContentAnimatedTransition = (from: number, to: number) => {
729    if ((from === 0 && to === 1) || (from === 1 && to === 0)) {
730      // Scale animation
731      let firstCustomTransition = {
732        timeout: this.firstTimeout,
733        transition: (proxy: TabContentTransitionProxy) => {
734          if (proxy.from === 0 && proxy.to === 1) {
735            this.tabContent0Scale = 1.0
736            this.tabContent1Scale = 0.5
737          } else {
738            this.tabContent0Scale = 0.5
739            this.tabContent1Scale = 1.0
740          }
741
742          animateTo({
743            duration: this.first2secondDuration,
744            onFinish: () => {
745              proxy.finishTransition()
746            }
747          }, () => {
748            if (proxy.from === 0 && proxy.to === 1) {
749              this.tabContent0Scale = 0.5
750              this.tabContent1Scale = 1.0
751            } else {
752              this.tabContent0Scale = 1.0
753              this.tabContent1Scale = 0.5
754            }
755          })
756        }
757      } as TabContentAnimatedTransition;
758      return firstCustomTransition;
759    } else {
760      // Opacity animation
761      let secondCustomTransition = {
762        timeout: this.secondTimeout,
763        transition: (proxy: TabContentTransitionProxy) => {
764          if ((proxy.from === 1 && proxy.to === 2) || (proxy.from === 2 && proxy.to === 1)) {
765            if (proxy.from === 1 && proxy.to === 2) {
766              this.tabContent1Opacity = 1.0
767              this.tabContent2Opacity = 0.5
768            } else {
769              this.tabContent1Opacity = 0.5
770              this.tabContent2Opacity = 1.0
771            }
772            animateTo({
773              duration: this.second2thirdDuration,
774              onFinish: () => {
775                proxy.finishTransition()
776              }
777            }, () => {
778              if (proxy.from === 1 && proxy.to === 2) {
779                this.tabContent1Opacity = 0.5
780                this.tabContent2Opacity = 1.0
781              } else {
782                this.tabContent1Opacity = 1.0
783                this.tabContent2Opacity = 0.5
784              }
785            })
786          } else if ((proxy.from === 0 && proxy.to === 2) || (proxy.from === 2 && proxy.to === 0)) {
787            if (proxy.from === 0 && proxy.to === 2) {
788              this.tabContent0Opacity = 1.0
789              this.tabContent2Opacity = 0.5
790            } else {
791              this.tabContent0Opacity = 0.5
792              this.tabContent2Opacity = 1.0
793            }
794            animateTo({
795              duration: this.first2thirdDuration,
796              onFinish: () => {
797                proxy.finishTransition()
798              }
799            }, () => {
800              if (proxy.from === 0 && proxy.to === 2) {
801                this.tabContent0Opacity = 0.5
802                this.tabContent2Opacity = 1.0
803              } else {
804                this.tabContent0Opacity = 1.0
805                this.tabContent2Opacity = 0.5
806              }
807            })
808          }
809        }
810      } as TabContentAnimatedTransition;
811      return secondCustomTransition;
812    }
813  }
814
815  build() {
816    Column() {
817      Tabs({ controller: this.tabsController }) {
818        TabContent() {
819          Text("Red")
820        }
821        .tabBar("Red")
822        .scale({ x: this.tabContent0Scale, y: this.tabContent0Scale })
823        .backgroundColor(Color.Red)
824        .opacity(this.tabContent0Opacity)
825        .width(100)
826        .height(100)
827
828        TabContent() {
829          Text("Yellow")
830        }
831        .tabBar("Yellow")
832        .scale({ x: this.tabContent1Scale, y: this.tabContent1Scale })
833        .backgroundColor(Color.Yellow)
834        .opacity(this.tabContent1Opacity)
835        .width(200)
836        .height(200)
837
838        TabContent() {
839          Text("Blue")
840        }
841        .tabBar("Blue")
842        .backgroundColor(Color.Blue)
843        .opacity(this.tabContent2Opacity)
844        .width(300)
845        .height(300)
846
847      }
848      .backgroundColor(0xf1f3f5)
849      .width('100%')
850      .height(500)
851      .margin({ top: 5 })
852      .customContentTransition(this.useCustomAnimation ? this.baseCustomAnimation : undefined)
853      .barMode(BarMode.Scrollable)
854      .onChange((index: number) => {
855        console.info("onChange index: " + index)
856      })
857      .onTabBarClick((index: number) => {
858        console.info("onTabBarClick index: " + index)
859      })
860    }
861  }
862}
863```
864
865![tabs5](figures/tabs8.gif)
866