Skip to main content
RapidDev - Software Development Agency
outsystems-tutorial

Creating Popups and Modal Dialogs in OutSystems

OutSystems provides two approaches for modal dialogs: the built-in Popup widget (simplest, drag-and-drop) and the OutSystems UI Modal/Popup_Editor pattern from the Forge (more flexible, with overlay and close button). Open and close both using a Client Action that sets a Boolean local variable, which the dialog's IsOpen parameter is bound to.

What you'll learn

  • How to add a Popup widget to a screen and configure its open/close behavior
  • How to pass data into a popup and return data back to the parent screen
  • How to use the OutSystems UI Popup_Editor pattern for a fully styled modal with overlay
  • How to build a confirmation dialog that waits for user input before proceeding
  • How to control popup visibility with a local Boolean variable and Client Actions
Book a free consultation
4.9Clutch rating
600+Happy partners
17+Countries served
190+Team members
Beginner11 min read25-35 minOutSystems 11 and ODCMarch 2026RapidDev Engineering Team
TL;DR

OutSystems provides two approaches for modal dialogs: the built-in Popup widget (simplest, drag-and-drop) and the OutSystems UI Modal/Popup_Editor pattern from the Forge (more flexible, with overlay and close button). Open and close both using a Client Action that sets a Boolean local variable, which the dialog's IsOpen parameter is bound to.

Modals, Popups, and Confirmation Dialogs

Modal dialogs are essential for forms, confirmations, and detail views. OutSystems offers multiple approaches: the native Popup widget for simple cases, and the OutSystems UI Popup_Editor Block for production-quality modals with full overlay, accessible close behavior, and styling. Both work on the same principle — a Boolean local variable controls visibility, and a Client Action toggles it. This tutorial builds both types, covering data passing, close handling, and confirmation patterns.

Prerequisites

  • A Reactive Web application open in Service Studio with at least one screen
  • OutSystems UI Forge component installed (Trusted — comes pre-installed in most Reactive Web apps from OutSystems)
  • A local Boolean variable named 'ShowDeleteConfirm' added to your screen (right-click screen → Add Local Variable → Name: ShowDeleteConfirm, Type: Boolean, Default: False)

Step-by-step guide

1

Add a Popup Widget to the Screen

Open your screen in the Screen Editor (Interface tab → UI Flows → MainFlow → double-click your screen). In the Toolbox (left panel), search for **Popup**. The built-in Popup widget appears in the widget list. Drag it onto the screen canvas. The Popup widget appears as a bordered box in the Screen Editor. Everything inside the Popup is its content — it will appear in a centered overlay when opened at runtime. **Configure the Popup properties (Properties panel with Popup selected):** - **Name:** PopupDeleteConfirm - **TriggerWidget:** Leave empty — you will control this programmatically - **IsOpen:** Click the expression editor → select your local variable `ShowDeleteConfirm` **Add content to the Popup:** 1. Inside the Popup, drag a Container → Style Classes: `"popup-content padding-m"` 2. Inside the Container, add: - Text widget: `"Are you sure you want to delete this item?"` - Button: Label 'Delete', Style 'Danger' - Button: Label 'Cancel', Style 'Neutral' The Popup is invisible by default (IsOpen = False). It becomes visible when ShowDeleteConfirm = True.

Expected result: A Popup widget appears on the screen canvas containing your confirmation message and two buttons. The Properties panel shows IsOpen bound to the ShowDeleteConfirm variable.

2

Create Open and Close Client Actions

Add two Client Actions to the screen to open and close the popup: **OpenDeleteConfirm action:** 1. Right-click the screen node (in the Interface tab) → **Add Client Action** 2. Name it `OpenDeleteConfirm` 3. In the Action Flow Editor: Start → Assign → End 4. In the Assign node, set: `ShowDeleteConfirm` = `True` **CloseDeleteConfirm action:** 1. Right-click the screen → **Add Client Action** → Name: `CloseDeleteConfirm` 2. Action flow: Start → Assign → End 3. In the Assign node, set: `ShowDeleteConfirm` = `False` **Wire up the buttons:** For the Cancel button inside the Popup: - Select it → Properties → Events → OnClick → CloseDeleteConfirm For the Delete button inside the Popup: - Select it → Events → OnClick → New Client Action → Name: `ConfirmDelete` - Action flow: Start → [call your delete Server Action] → Assign (ShowDeleteConfirm = False) → Refresh Aggregate → End For the trigger button on the main screen (the 'Delete' button next to each record): - Events → OnClick → OpenDeleteConfirm

typescript
1/* OpenDeleteConfirm Client Action flow:
2 Start
3 Assign: ShowDeleteConfirm = True
4 End
5
6 CloseDeleteConfirm Client Action flow:
7 Start
8 Assign: ShowDeleteConfirm = False
9 End
10
11 ConfirmDelete Client Action flow:
12 Start
13 DeleteTask(TaskId: SelectedTaskId) [Server Action]
14 Assign: ShowDeleteConfirm = False
15 Refresh Data: GetTasks
16 End
17*/

Expected result: Clicking the Delete button on the main screen sets ShowDeleteConfirm = True, which triggers the Popup to open. Clicking Cancel sets it back to False, closing the Popup.

3

Pass Data into a Popup Using Local Variables

When a list has multiple rows and each row has a Delete button, you need to pass the specific record's Id to the confirmation popup before the user confirms deletion. **Pattern — store the selected record ID before opening the popup:** 1. Add a second local variable to the screen: `SelectedTaskId` (Type: Task Identifier, Default: NullIdentifier()) 2. Modify the OpenDeleteConfirm Client Action to accept the task ID: - Right-click OpenDeleteConfirm → Add Input Parameter → Name: `TaskId`, Type: Task Identifier - In the Assign node, add: `SelectedTaskId` = `TaskId` (store for later use) - Keep `ShowDeleteConfirm` = `True` 3. Update the Delete button in the List widget: - The List widget iterates over GetTasks.List. Each row's Delete button should call OpenDeleteConfirm. - Select the Delete button → Events → OnClick → OpenDeleteConfirm - Set the TaskId input parameter to: `GetTasks.List.Current.Task.Id` 4. In the ConfirmDelete action, use `SelectedTaskId` when calling the Server Action: - DeleteTask(TaskId: SelectedTaskId) This pattern — store selection in a local variable, open popup, use stored variable on confirm — is the standard way to pass context to popups in OutSystems.

typescript
1/* OpenDeleteConfirm Client Action:
2 Input Parameter: TaskId (Task Identifier)
3
4 Flow:
5 Start
6 Assign
7 SelectedTaskId = TaskId
8 ShowDeleteConfirm = True
9 End
10
11 ConfirmDelete Client Action:
12 Start
13 DeleteTask(TaskId: SelectedTaskId) [Server Action]
14 Assign: ShowDeleteConfirm = False
15 Refresh Data: GetTasks
16 If(DeleteTask.Success)
17 [True] SetFeedbackMessage("Task deleted", Success)
18 [False] SetFeedbackMessage("Error deleting task", Error)
19 End
20*/

Expected result: Clicking the Delete button in row 3 opens the popup with SelectedTaskId set to that row's Task.Id. The ConfirmDelete action deletes the correct task regardless of which row triggered the popup.

4

Use the OutSystems UI Popup_Editor Pattern for Production Dialogs

The OutSystems UI framework includes a **Popup_Editor** Block that provides a fully styled modal with: - Dark overlay behind the dialog - Accessible close (X) button and keyboard Escape support - Configurable title header - Scrollable content area - Proper focus management for accessibility **Adding the Popup_Editor Block:** 1. Ensure OutSystems UI dependency is referenced (Ctrl+Q → find 'OutSystemsUI' → add Popup_Editor Block) 2. In the Screen Editor, drag the **Popup_Editor** block from the Toolbox (find it under 'Interaction' or search 'Popup_Editor') 3. Place it anywhere on the screen canvas — its visual position on the canvas does not matter because it renders as an absolute-positioned overlay **Configuring Popup_Editor properties:** - **IsOpen** → expression: `ShowDeleteConfirm` - **Title** → `"Confirm Deletion"` - **EnterAnimation** → `EnterAnimation.EnterBottom` (or ScaleUp) - **UseSingleClickClose** → False (requires explicit close action) **Adding content inside Popup_Editor:** The Popup_Editor block has a Content placeholder. Drag your dialog content (Text, Buttons) into this placeholder. **Handling the OnClose event:** Popup_Editor exposes an `OnToggle` event. Set it to your CloseDeleteConfirm action — this fires when the user clicks the X button or Escape.

typescript
1/* Popup_Editor Block properties:
2 IsOpen: ShowDeleteConfirm (local variable)
3 Title: "Confirm Deletion"
4 EnterAnimation: EnterAnimation.EnterBottom
5 Events:
6 OnToggle: CloseDeleteConfirm (Client Action)
7
8 Inside the Content placeholder:
9 Text: "Are you sure you want to delete '" + GetTask.Task.Title + "'?"
10 Container (row, margin-top-m)
11 Button: "Delete" Style: Danger OnClick: ConfirmDelete
12 Button: "Cancel" Style: Neutral OnClick: CloseDeleteConfirm
13*/

Expected result: The Popup_Editor renders as a professional modal overlay with title, close button, and keyboard Escape support. The OnToggle event fires when the user tries to close via the X button or Escape key.

5

Build a Form Popup for Inline Record Creation

A common pattern is a 'New Record' popup with a form, so users can create records without leaving the current screen. **Setup:** 1. Add a local variable: `ShowNewTaskPopup` (Boolean, Default: False) 2. Add a local variable: `NewTaskRecord` (Type: Task, for the form data) 3. Add a 'New Task' button on the screen: Events → OnClick → Client Action that sets ShowNewTaskPopup = True and resets NewTaskRecord to empty **Form inside Popup_Editor:** 1. Drag Popup_Editor onto the screen, bind IsOpen to ShowNewTaskPopup 2. Inside the Content placeholder, drag a **Form** widget 3. Inside the Form, add Input widgets: - Input: Variable → NewTaskRecord.Title, Mandatory: Yes - TextArea: Variable → NewTaskRecord.Description - Input (DateType): Variable → NewTaskRecord.DueDate 4. Add a 'Save' Button inside the Form → OnClick: SaveNewTask Client Action **SaveNewTask Client Action:** Start → If(Form.Valid) → [True] CreateTask (Server Action, passing NewTaskRecord) → Assign ShowNewTaskPopup = False → Refresh GetTasks aggregate → [False] End

typescript
1/* OpenNewTaskPopup Client Action:
2 Start
3 Assign
4 NewTaskRecord = Default.Task
5 ShowNewTaskPopup = True
6 End
7
8 SaveNewTask Client Action:
9 Start
10 If(Form1.Valid)
11 [True]
12 CreateTask(TaskRecord: NewTaskRecord)
13 Assign: ShowNewTaskPopup = False
14 Refresh Data: GetTasks
15 End
16 [False]
17 End
18*/

Expected result: Clicking 'New Task' opens a modal form with empty fields. Filling in the title and clicking Save creates the record and refreshes the list, then closes the modal. The Cancel button closes without saving.

Complete working example

popup_patterns_summary.os
1/* OutSystems Popup Patterns Reference
2
3=== PATTERN 1: Built-in Popup Widget ===
4
5Local Variables:
6 ShowPopup: Boolean = False
7
8Client Actions:
9 OpenPopup:
10 Start Assign ShowPopup = True End
11
12 ClosePopup:
13 Start Assign ShowPopup = False End
14
15Widget tree:
16 Screen
17 Button "Open" OnClick: OpenPopup
18 Popup
19 IsOpen = ShowPopup
20 [content]
21 Button "Close" OnClick: ClosePopup
22
23=== PATTERN 2: OutSystems UI Popup_Editor ===
24
25Local Variables:
26 ShowEditPopup: Boolean = False
27 SelectedRecordId: Task Identifier = NullIdentifier()
28
29Client Actions:
30 OpenEditPopup(TaskId: Task Identifier):
31 Start
32 Assign
33 SelectedRecordId = TaskId
34 ShowEditPopup = True
35 Refresh Data: GetTaskById
36 End
37
38 CloseEditPopup:
39 Start Assign ShowEditPopup = False End
40
41Widget tree:
42 Screen
43 Popup_Editor
44 IsOpen = ShowEditPopup
45 Title = "Edit Task"
46 OnToggle = CloseEditPopup
47 [Content placeholder]
48 Form
49 [Input widgets bound to GetTaskById]
50 Buttons row
51 Button "Save" OnClick: SaveTaskEdit
52 Button "Cancel" OnClick: CloseEditPopup
53
54=== EXPRESSION EXAMPLES ===
55
56/* Dynamic popup title */
57If(SelectedRecordId = NullIdentifier(),
58 "New Task",
59 "Edit: " + GetTaskById.Task.Title)
60
61/* Show popup only if user has edit permission */
62ShowEditPopup and CheckRole(Roles.Admin)
63*/

Common mistakes

Why it's a problem: Setting IsOpen on the Popup widget to a static expression (True/False) instead of a local variable

How to avoid: A static True makes the popup always visible; static False makes it never show. Always bind IsOpen to a Boolean local variable that your Client Actions toggle. The popup opens when the variable becomes True and closes when it becomes False.

Why it's a problem: Popup opens but data inside it shows empty or stale values

How to avoid: After setting the ShowPopup variable to True in your OpenPopup action, refresh the relevant Aggregate or Data Action that populates the popup's content. Place the Refresh Data node BEFORE the Assign ShowPopup = True step so data is ready when the popup renders.

Why it's a problem: Using the Popup widget's TriggerWidget property instead of a Client Action to control open/close

How to avoid: The TriggerWidget approach works for simple toggle cases but does not allow custom logic (like refreshing data or storing selected IDs). Use the IsOpen binding with a local variable for any popup that needs data or context passed into it.

Why it's a problem: Placing the Popup_Editor Block at a nested position inside the Widget Tree instead of directly on the screen

How to avoid: Place Popup_Editor as a direct child of the screen (not inside another container). Modal overlays render based on absolute positioning — being nested inside a container with overflow:hidden or a transform will clip the overlay. Place it at the top level of the Widget Tree.

Best practices

  • Use a single Boolean local variable per popup to control visibility — keep the naming consistent: ShowXxxPopup
  • Always reset form data when opening a 'New Record' popup — set the record variable to its default before setting ShowPopup = True
  • Use the OutSystems UI Popup_Editor for production apps — it handles accessibility (focus trap, keyboard close) that the basic Popup widget does not
  • Never nest popups (a popup that opens another popup) — this confuses users and creates complex state management; use a wizard pattern instead
  • Handle the OnToggle event on Popup_Editor to close the popup when users press Escape or click the X — do not rely solely on button clicks
  • Keep popup content lightweight — popups that load complex aggregates on open should use a loading spinner (Boolean IsLoading variable + If widget) to prevent blank flash
  • For mobile apps, consider using a BottomSheet (OutSystems UI pattern) instead of a modal — bottom sheets are more ergonomic on small screens

Still stuck?

Copy one of these prompts to get a personalized, step-by-step explanation.

ChatGPT Prompt

In my OutSystems Reactive Web app, I have a Table widget showing a list of Tasks. Each row has a Delete button. When clicked, I want a confirmation modal to appear asking 'Are you sure you want to delete [Task Title]?' with Confirm and Cancel buttons. On Confirm, the task is deleted and the list refreshes. Show me: (1) the local variables needed, (2) the Client Action flows for Open, Close, and Confirm, and (3) the widget setup using OutSystems UI Popup_Editor.

OutSystems Prompt

I am using the OutSystems UI Popup_Editor block for a 'New Task' form modal. The modal has Input fields for Title and Description bound to a local NewTask variable. The Save button should validate the form, call a Server Action to create the task, close the modal, and refresh the list. Show me the complete SaveNewTask Client Action flow using OutSystems arrow notation and the correct form validation check.

Frequently asked questions

Can I have multiple different popups on the same screen?

Yes. Add one Boolean local variable per popup (e.g., ShowDeleteConfirm, ShowEditRecord, ShowNewTask) and one Popup or Popup_Editor widget for each. Each popup is controlled by its own variable. You can open only one at a time by ensuring other variables are False when opening a new one, or allow them to layer (unusual and generally not recommended for UX).

How do I prevent the popup from closing when the user clicks outside of it (the overlay)?

On the OutSystems UI Popup_Editor, set the UseSingleClickClose property to False. This disables the overlay click-to-close behavior, requiring users to use the X button or your explicit Close button. For the basic Popup widget, overlay click-close is not built in — it only closes when you set IsOpen to False via a Client Action.

How do I return data from a popup back to the parent screen?

Popups share the same screen scope as their parent — they are not separate screens. Local variables defined on the screen are accessible both in the parent screen logic and in event handlers of widgets inside the popup. To return data from a popup, have the popup's Save/Confirm action write to a screen-level local variable before closing. The parent screen reads that same variable after the popup closes.

What is the difference between a Popup widget and a Popup_Editor block from OutSystems UI?

The built-in Popup widget is a basic container widget that shows/hides based on IsOpen — it has minimal styling and no built-in overlay, close button, or accessibility features. The Popup_Editor is a pre-built Block from the OutSystems UI Forge component that adds a dark overlay, title bar, X close button, keyboard Escape support, and enter/exit animations. For production apps, use Popup_Editor. For quick prototyping or very simple cases, the basic Popup widget is faster to set up.

RapidDev

Talk to an Expert

Our team has built 600+ apps. Get personalized help with your project.

Book a free consultation

Need help with your project?

Our experts have built 600+ apps and can accelerate your development. Book a free consultation — no strings attached.

Book a free consultation

We put the rapid in RapidDev

Need a dedicated strategic tech and growth partner? Discover what RapidDev can do for your business! Book a call with our team to schedule a free, no-obligation consultation. We'll discuss your project and provide a custom quote at no cost.