• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Popup
2You can bind the **Popup** attribute to a component to create a popup, specifying its content and interaction logic, and display state. It is mainly used for screen recording and message notification.
3
4Popups can be defined with [PopupOptions](../reference/apis-arkui/arkui-ts/ts-universal-attributes-popup.md#popupoptions) or [CustomPopupOptions](../reference/apis-arkui/arkui-ts/ts-universal-attributes-popup.md#custompopupoptions8). In **PopupOptions**, you can set **primaryButton** and **secondaryButton** to include buttons in the popup. In **CustomPopupOptions**, you can create a custom popup using [builder](../../application-dev/ui/state-management/arkts-builder.md). For **PopupOptions**, the maximum font scale factor is 2.
5
6You can configure the modality of a popup through [mask](../reference/apis-arkui/arkui-ts/ts-universal-attributes-popup.md#popupoptions). Setting **mask** to **true** or a color value makes the popup a modal, and setting **mask** to **false** makes the popup a non-modal.
7
8When multiple popups are displayed at the same time, popups displayed in child windows have a higher z-index than those in the main window. When in the same window, popups displayed later have a higher z-index than those displayed earlier.
9
10## Creating a Text Popup
11
12Text popups are usually used to display informational text messages, suitable for non-interactive scenarios. Bind the **Popup** attribute to a component. When the **show** parameter of **bindPopup** is set to **true**, a popup is displayed.
13
14In the example below, with the **Popup** attribute bound to a **Button** component, each click toggles the boolean value in **handlePopup**. When the value becomes **true**, it triggers **bindPopup** to display the popup.
15
16```ts
17@Entry
18@Component
19struct PopupExample {
20  @State handlePopup: boolean = false;
21
22  build() {
23    Column() {
24      Button('PopupOptions')
25        .onClick(() => {
26          this.handlePopup = !this.handlePopup;
27        })
28        .bindPopup(this.handlePopup, {
29          message: 'This is a popup with PopupOptions',
30        })
31    }.width('100%').padding({ top: 5 })
32  }
33}
34```
35
36![en-us_image_0000001511740524](figures/en-us_image_0000001511740524.png)
37
38## Adding an Event Listener for Popup State Changes
39
40You can use the **onStateChange** parameter to add an event callback for popup state changes, so as to determine the current state of the popup.
41
42```ts
43@Entry
44@Component
45struct PopupExample {
46  @State handlePopup: boolean = false;
47
48  build() {
49    Column() {
50      Button('PopupOptions')
51        .onClick(() => {
52          this.handlePopup = !this.handlePopup;
53        })
54        .bindPopup(this.handlePopup, {
55          message: 'This is a popup with PopupOptions',
56          onStateChange: (e)=> {// Return the current popup state.
57            if (!e.isVisible) {
58              this.handlePopup = false;
59            }
60          }
61        })
62    }.width('100%').padding({ top: 5 })
63  }
64}
65```
66
67![PopupOnStateChange](figures/PopupOnStateChange.gif)
68
69## Creating a Popup with Buttons
70
71You can add a maximum of two buttons to a popup through the **primaryButton** and **secondaryButton** attributes. For each of the buttons, you can set the **action** parameter to specify the operation to be triggered.
72
73```ts
74@Entry
75@Component
76struct PopupExample22 {
77  @State handlePopup: boolean = false;
78
79  build() {
80    Column() {
81      Button('PopupOptions').margin({ top: 200 })
82        .onClick(() => {
83          this.handlePopup = !this.handlePopup;
84        })
85        .bindPopup(this.handlePopup, {
86          message: 'This is a popup with PopupOptions',
87          primaryButton: {
88            value: 'Confirm',
89            action: () => {
90              this.handlePopup = !this.handlePopup;
91              console.info('confirm Button click');
92            }
93          },
94          secondaryButton: {
95            value: 'Cancel',
96            action: () => {
97              this.handlePopup = !this.handlePopup;
98            }
99          },
100          onStateChange: (e) => {
101            if (!e.isVisible) {
102              this.handlePopup = false;
103            }
104          }
105        })
106    }.width('100%').padding({ top: 5 })
107  }
108}
109```
110
111![en-us_other_0000001500740342](figures/en-us_other_0000001500740342.jpeg)
112
113## Implementing Popup Animation
114
115You implement the entrance and exit animation effects of popups through **transition**.
116
117```ts
118// xxx.ets
119@Entry
120@Component
121struct PopupExample {
122  @State handlePopup: boolean = false;
123  @State customPopup: boolean = false;
124
125  // Define the popup content in the popup builder.
126  @Builder popupBuilder() {
127    Row() {
128      Text('Custom Popup with transitionEffect').fontSize(10)
129    }.height(50).padding(5)
130  }
131
132  build() {
133    Flex({ direction: FlexDirection.Column }) {
134      // PopupOptions for setting the popup
135      Button('PopupOptions')
136        .onClick(() => {
137          this.handlePopup = !this.handlePopup;
138        })
139        .bindPopup(this.handlePopup, {
140          message: 'This is a popup with transitionEffect',
141          placementOnTop: true,
142          showInSubWindow: false,
143          onStateChange: (e) => {
144            if (!e.isVisible) {
145              this.handlePopup = false;
146            }
147          },
148          // Set the popup animation to a combination of opacity and translation effects, with no exit animation.
149          transition:TransitionEffect.asymmetric(
150            TransitionEffect.OPACITY.animation({ duration: 1000, curve: Curve.Ease }).combine(
151              TransitionEffect.translate({ x: 50, y: 50 })),
152            TransitionEffect.IDENTITY)
153        })
154        .position({ x: 100, y: 150 })
155
156      // CustomPopupOptions for setting the popup
157      Button('CustomPopupOptions')
158        .onClick(() => {
159          this.customPopup = !this.customPopup;
160        })
161        .bindPopup(this.customPopup, {
162          builder: this.popupBuilder,
163          placement: Placement.Top,
164          showInSubWindow: false,
165          onStateChange: (e) => {
166            if (!e.isVisible) {
167              this.customPopup = false;
168            }
169          },
170          // Set the popup entrance and exit animations to be a scaling effect.
171          transition:TransitionEffect.scale({ x: 1, y: 0 }).animation({ duration: 500, curve: Curve.Ease })
172        })
173        .position({ x: 80, y: 300 })
174    }.width('100%').padding({ top: 5 })
175  }
176}
177```
178
179![popup_transition](figures/popup_transition.gif)
180
181## Creating a Custom Popup
182
183You can create a custom popup with **builder** in **CustomPopupOptions**, defining custom content in \@Builder. In addition, you can use parameters such as **popupColor** to control the popup style.
184
185```ts
186@Entry
187@Component
188struct Index {
189  @State customPopup: boolean = false;
190  // Define the popup content in the popup builder.
191  @Builder popupBuilder() {
192    Row({ space: 2 }) {
193      Image($r("app.media.icon")).width(24).height(24).margin({ left: 5 })
194      Text('This is Custom Popup').fontSize(15)
195    }.width(200).height(50).padding(5)
196  }
197  build() {
198    Column() {
199      Button('CustomPopupOptions')
200        .position({x:100,y:200})
201        .onClick(() => {
202          this.customPopup = !this.customPopup;
203        })
204        .bindPopup(this.customPopup, {
205          builder: this.popupBuilder, // Content of the popup.
206          placement:Placement.Bottom, // Position of the popup.
207          popupColor:Color.Pink, // Background color of the popup.
208          onStateChange: (e) => {
209            if (!e.isVisible) {
210              this.customPopup = false;
211            }
212          }
213        })
214    }
215    .height('100%')
216  }
217}
218```
219
220To place the popup in a specific position, set the **placement** parameter. The popup builder triggers a popup message to instruct the user to complete the operation.
221
222![en-us_other_0000001500900234](figures/en-us_other_0000001500900234.jpeg)
223
224## Defining the Popup Style
225
226You can define the popup style using both builder-based customization and through specific API configurations.
227
228Background color: While popups initially come with a transparent background, they have a blur effect, which is **COMPONENT_ULTRA_THICK** on phones.
229Mask style: Popups come with a default mask that is transparent.
230Size: The size of a popup is determined by the content within the builder or the length of the message it holds.
231Position: Popups are located below their host component by default. You can control the display position and alignment using the **Placement** API.
232The following example demonstrates how to configure a popup's style using **popupColor**, **mask**, **width**, and **placement**.
233
234```ts
235// xxx.ets
236
237@Entry
238@Component
239struct PopupExample {
240  @State handlePopup: boolean = false;
241
242  build() {
243    Column({ space: 100 }) {
244      Button('PopupOptions')
245        .onClick(() => {
246          this.handlePopup = !this.handlePopup;
247        })
248        .bindPopup(this.handlePopup, {
249          width: 200,
250          message: 'This is a popup.',
251          popupColor: Color.Red, // Set the background color for the popup.
252          mask: {
253            color: '#33d9d9d9'
254          },
255          placement: Placement.Top,
256          backgroundBlurStyle: BlurStyle.NONE // Remove the blur effect for the popup.
257        })
258    }
259    .width('100%')
260  }
261}
262```
263
264![image](figures/UIpopupStyle.gif)
265
266## Enabling the Popup to Avoid the Soft Keyboard
267
268By default, popups do not avoid the soft keyboard and may be obscured by it. Setting **keyboardAvoidMode** to **KeyboardAvoidMode.DEFAULT** enables keyboard avoidance. If there is insufficient space, the popup will shift from its default position to overlay its host component.
269
270```ts
271// xxx.ets
272
273@Entry
274@Component
275struct PopupExample {
276  @State handlePopup: boolean = false;
277
278  @Builder popupBuilder() {
279    Column({ space: 2 }) {
280      Text('Custom Popup').fontSize(20)
281        .borderWidth(2)
282      TextInput()
283    }.width(200).padding(5)
284  }
285
286  build() {
287    Column({ space: 100 }) {
288      TextInput()
289      Button('PopupOptions')
290        .onClick(() => {
291          this.handlePopup = !this.handlePopup;
292        })
293        .bindPopup(this.handlePopup!!, {
294          width: 200,
295          builder: this.popupBuilder(),
296          placement: Placement.Bottom,
297          mask: false,
298          autoCancel: false,
299          keyboardAvoidMode: KeyboardAvoidMode.DEFAULT
300        })
301        .position({x: 100, y: 300})
302    }
303    .width('100%')
304  }
305}
306```
307
308![image](figures/avoidKeyboard.gif)
309
310
311## Setting Polymorphic Effects in the Popup
312
313When @Builder is used for custom popup content, polymorphic styles are not supported by default. To achieve background color changes on press, implement a component with @Component.
314
315```ts
316@Entry
317@Component
318struct PopupPage {
319  private menus: Array<string> = ["Scan", "Create Group Chat", "Employee ID Card"]
320
321  // Define the popup content in the popup builder.
322  @Builder
323  popupItemBuilder(name: string, action: string) {
324    PopupItemChild({ childName: name, childAction: action })
325  }
326
327  // Define the popup content in the popup builder.
328  @Builder
329  popupBuilder() {
330    Column() {
331      ForEach(
332        this.menus,
333        (item: string, index) => {
334          this.popupItemBuilder(item, String(index))
335        },
336        (item: string, index) => {
337          return item
338        })
339    }
340    .padding(8)
341  }
342
343  @State customPopup: boolean = false;
344
345  build() {
346    Column() {
347      Button('Click Me')
348        .onClick(() => {
349          this.customPopup = !this.customPopup
350        })
351        .bindPopup(
352          this.customPopup,
353          {
354            builder: this.popupBuilder, // Content of the popup.
355            placement: Placement.Bottom, // Position of the popup.
356            popupColor: Color.White, // Background color of the popup.
357            onStateChange: (event) => {
358              if (!event.isVisible) {
359                this.customPopup = false
360              }
361            }
362          })
363    }
364    .width('100%')
365    .justifyContent(FlexAlign.Center)
366  }
367}
368
369@Component
370struct PopupItemChild {
371  @Prop childName: string = '';
372  @Prop childAction: string = '';
373
374  build() {
375    Row({ space: 8 }) {
376      Image($r('app.media.startIcon'))
377        .width(24)
378        .height(24)
379      Text(this.childName)
380        .fontSize(16)
381    }
382    .width(200)
383    .height(50)
384    .padding(8)
385    .onClick(() => {
386      this.getUIContext().getPromptAction().showToast({ message: 'Selected ' + this.childName })
387    })
388    .stateStyles({
389      normal: {
390        .backgroundColor(Color.White)
391      },
392      pressed: {
393        .backgroundColor('#1fbb7d')
394      }
395    })
396  }
397}
398```
399
400![popupStateStyle](figures/popupStateStyle.gif)
401
402## Implementing Axis Avoidance
403
404Since API version 18, popups support center axis avoidance. Since API version 20, it is enabled by default on 2-in-1 devices (avoidance only occurs when the window is in waterfall mode). You can control whether to enable axis avoidance through the **enableHoverMode** property in [PopupOptions](../reference/apis-arkui/arkui-ts/ts-universal-attributes-popup.md#popupoptions).
405
406> **NOTE**
407> - Popups will not avoid the center axis if clicked in the axis area.
408> - On 2-in-1 devices, axis avoidance occurs only when the window is in waterfall mode.
409
410```ts
411@Entry
412@Component
413struct Index {
414  @State message: string = 'Hello World';
415  @State index: number = 0;
416  @State arrayStr: Array<string> = ['Upper half screen', 'Central axis', 'Lower half screen'];
417  @State enableHoverMode: boolean | undefined = true;
418  @State showInSubwindow: boolean = false;
419  @State placement: Placement | undefined = undefined;
420  @State isShow: boolean = false;
421
422  build() {
423    RelativeContainer() {
424      Column() {
425        Button('Area: ' + this.arrayStr[this.index])
426          .onClick(() => {
427            if (this.index < 2) {
428              this.index++
429            } else {
430              this.index = 0
431            }
432          })
433
434        Button('Subwindow: ' + (this.showInSubwindow ? 'Yes' : 'No'))
435          .onClick(() => {
436            this.showInSubwindow = !this.showInSubwindow
437          })
438
439        Button('Hover Mode: ' + this.enableHoverMode)
440          .onClick(() => {
441            if (this.enableHoverMode == undefined) {
442              this.enableHoverMode = true
443            } else if (this.enableHoverMode == true) {
444              this.enableHoverMode = false
445            } else {
446              this.enableHoverMode = undefined
447            }
448          })
449      }
450
451      Row() {
452        Button('Popup')
453          .fontWeight(FontWeight.Bold)
454          .bindPopup(this.isShow, {
455            message: 'popup',
456            enableHoverMode: this.enableHoverMode,
457            showInSubWindow: this.showInSubwindow,
458          })
459          .onClick(() => {
460            this.isShow = !this.isShow
461          })
462      }
463      .alignRules({
464        center: { anchor: '__container__', align: VerticalAlign.Center },
465        middle: { anchor: '__container__', align: HorizontalAlign.Center }
466      })
467      .margin({
468        top: this.index == 2 ? 330 : this.index == 1 ? 50 : 0,
469        bottom: this.index == 0 ? 330 : 0
470      })
471    }
472    .height('100%')
473    .width('100%')
474  }
475}
476```
477