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 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 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 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 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 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 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 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 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