# Creating Popups and Modal Dialogs in OutSystems

- Tool: OutSystems
- Difficulty: Beginner
- Time required: 25-35 min
- Compatibility: OutSystems 11 and ODC
- Last updated: March 2026

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

## Before you start

- 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

```
/* OpenDeleteConfirm Client Action flow:
   Start
   → Assign: ShowDeleteConfirm = True
   → End

   CloseDeleteConfirm Client Action flow:
   Start
   → Assign: ShowDeleteConfirm = False
   → End

   ConfirmDelete Client Action flow:
   Start
   → DeleteTask(TaskId: SelectedTaskId)  [Server Action]
   → Assign: ShowDeleteConfirm = False
   → Refresh Data: GetTasks
   → End
*/
```

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

```
/* OpenDeleteConfirm Client Action:
   Input Parameter: TaskId (Task Identifier)

   Flow:
   Start
   → Assign
       SelectedTaskId = TaskId
       ShowDeleteConfirm = True
   → End

   ConfirmDelete Client Action:
   Start
   → DeleteTask(TaskId: SelectedTaskId)  [Server Action]
   → Assign: ShowDeleteConfirm = False
   → Refresh Data: GetTasks
   → If(DeleteTask.Success)
       [True] → SetFeedbackMessage("Task deleted", Success)
       [False] → SetFeedbackMessage("Error deleting task", Error)
   → End
*/
```

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

```
/* Popup_Editor Block properties:
   IsOpen:        ShowDeleteConfirm (local variable)
   Title:         "Confirm Deletion"
   EnterAnimation: EnterAnimation.EnterBottom
   Events:
     OnToggle:    CloseDeleteConfirm (Client Action)

   Inside the Content placeholder:
   ├─ Text: "Are you sure you want to delete '" + GetTask.Task.Title + "'?"
   ├─ Container (row, margin-top-m)
   │   ├─ Button: "Delete" → Style: Danger → OnClick: ConfirmDelete
   │   └─ Button: "Cancel" → Style: Neutral → OnClick: CloseDeleteConfirm
*/
```

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

```
/* OpenNewTaskPopup Client Action:
   Start
   → Assign
       NewTaskRecord = Default.Task
       ShowNewTaskPopup = True
   → End

   SaveNewTask Client Action:
   Start
   → If(Form1.Valid)
     [True]
     → CreateTask(TaskRecord: NewTaskRecord)
     → Assign: ShowNewTaskPopup = False
     → Refresh Data: GetTasks
     → End
     [False]
     → End
*/
```

**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 code example

File: `popup_patterns_summary.os`

```outsystems-expression
/* OutSystems Popup Patterns Reference

=== PATTERN 1: Built-in Popup Widget ===

Local Variables:
  ShowPopup: Boolean = False

Client Actions:
  OpenPopup:
    Start → Assign ShowPopup = True → End

  ClosePopup:
    Start → Assign ShowPopup = False → End

Widget tree:
  Screen
  ├─ Button "Open"   → OnClick: OpenPopup
  └─ Popup
       IsOpen = ShowPopup
       └─ [content]
           └─ Button "Close" → OnClick: ClosePopup

=== PATTERN 2: OutSystems UI Popup_Editor ===

Local Variables:
  ShowEditPopup: Boolean = False
  SelectedRecordId: Task Identifier = NullIdentifier()

Client Actions:
  OpenEditPopup(TaskId: Task Identifier):
    Start
    → Assign
        SelectedRecordId = TaskId
        ShowEditPopup = True
    → Refresh Data: GetTaskById
    → End

  CloseEditPopup:
    Start → Assign ShowEditPopup = False → End

Widget tree:
  Screen
  └─ Popup_Editor
       IsOpen = ShowEditPopup
       Title = "Edit Task"
       OnToggle = CloseEditPopup
       └─ [Content placeholder]
           ├─ Form
           │   └─ [Input widgets bound to GetTaskById]
           └─ Buttons row
               ├─ Button "Save" → OnClick: SaveTaskEdit
               └─ Button "Cancel" → OnClick: CloseEditPopup

=== EXPRESSION EXAMPLES ===

/* Dynamic popup title */
If(SelectedRecordId = NullIdentifier(),
   "New Task",
   "Edit: " + GetTaskById.Task.Title)

/* Show popup only if user has edit permission */
ShowEditPopup and CheckRole(Roles.Admin)
*/
```

## Common mistakes

- **Setting IsOpen on the Popup widget to a static expression (True/False) instead of a local variable** — undefined Fix: 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.
- **Popup opens but data inside it shows empty or stale values** — undefined Fix: 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.
- **Using the Popup widget's TriggerWidget property instead of a Client Action to control open/close** — undefined Fix: 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.
- **Placing the Popup_Editor Block at a nested position inside the Widget Tree instead of directly on the screen** — undefined Fix: 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

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

---

Source: https://www.rapidevelopers.com/outsystems-tutorial/outsystems-popup-modal
© RapidDev — https://www.rapidevelopers.com/outsystems-tutorial/outsystems-popup-modal
