# Modal

> A dialog surface with title, content and footer buttons. A native Pyzed component for Python desktop GUIs, with a live preview, code examples and its API.

Source: https://albakore.github.io/pyzed/library/modal/

:::tip[In plain words]
A window inside the window that asks for your attention before you go on.
:::

A dialog surface with a title, a close button, your content and a footer of buttons. Use it as the overlay of `ui.layer` or `ui.dialog_layer`. The ready-made [dialogs](https://albakore.github.io/pyzed/library/confirm/) are built on it.

`ui.modal` · Category: [Overlays](https://albakore.github.io/pyzed/library/#overlays) · [Full reference →](https://albakore.github.io/pyzed/reference/ui/#modal)

**Code:**

```python
ui.modal(
    ui.muted("notes.py has unsaved changes. Save them before closing?"),
    title="Save changes?",
    on_close=on_close,
    footer=[ui.button("Don't save", on_discard, variant="ghost"), ui.button("Cancel", on_close),
            ui.button("Save", on_save, variant="solid")],
    width=440,
)
```

## When to use

- For dialogs with your own content: a form, settings, an About box.

## Usage

```python
from pyzed import ui

ui.modal(
    ui.muted("notes.py has unsaved changes. Save them before closing?"),
    title="Save changes?",
    on_close=on_close,
    footer=[ui.button("Don't save", on_discard, variant="ghost"), ui.button("Cancel", on_close),
            ui.button("Save", on_save, variant="solid")],
    width=440,
)
```

## Variants

:::note[About these captures]
Each capture is recorded by pyzed itself, off-screen at 2x, in One Dark or One Light to match this site's theme, and cropped to the component so it shows at its real size. The pointer hovers, presses or types into it; a component that does not react to the pointer cycles through a few themes. With reduced motion you see the first frame. Open **Code** under a capture to see the snippet that draws it; free names such as `on_save` stand for your own callbacks.
:::

### Content

```python
ui.modal(ui.muted("Your trial ends in 3 days."), width=320)
```

```python
ui.modal(ui.muted("Your trial ends in 3 days."), title="Trial", on_close=on_close, width=320)
```

```python
ui.modal(ui.muted("Discard the draft?"), title="Draft", on_close=on_close, width=320,
         footer=[ui.button("Cancel", on_close), ui.button("Discard", on_discard, variant="error")])
```

### States and events

`max_height=` scrolls long content, and Escape sends `on_close` while the dialog has focus.

```python
ui.modal(ui.input("notes.md", on_input), title="Rename", description="Choose a new name",
         icon="pencil", on_close=on_close, width="sm", footer=[ui.button("Rename", on_press, variant="solid")])
```

## Examples

### Over the app

Wrap the view with `ui.dialog_layer` to center it over a dimmed backdrop.

**Code:**

```python
ui.dialog_layer(main_view(), ui.modal(
    pz.column([ui.input("", on_input, placeholder="Project name"),
               ui.checkbox(True, on_toggle, label="Create a git repository")], spacing=10),
    title="New project", on_close=on_close,
    footer=[ui.button("Cancel", on_close), ui.button("Create", on_create, variant="solid")],
    width=380,
), on_dismiss=on_close)
```

## Events

Modal has **1 event of its own**, listed below. Like every widget it also has the **18 common events** (`click`, `double_click`, `right_click`, `mouse_enter`, `drag`, `key_down`, `resize`…), attached with `.on(...)`: see [Events](https://albakore.github.io/pyzed/guides/events/) and [Events of every component](https://albakore.github.io/pyzed/library/events/).

| Event | Handler gets | When |
| --- | --- | --- |
| `on_close` | `()` | The close button; also sent by Escape while the dialog has focus. |

**Handler gets** is what Pyzed passes to your callable: `()` means nothing. Any handler can also be a message (a value that is not callable), which goes to `update()`.

### Handling the events

The same events written three ways. **Imperative**: methods of the app change `self`. **Declarative**: the handlers produce messages and one `update()` decides. **Functional**: a pure function draws an immutable state and asks for changes with `set_state(...)`. See [Imperative, declarative and functional](https://albakore.github.io/pyzed/guides/code-styles/).

**Imperative:**

```python
class Notes(pz.App):
    def __init__(self):
        self.asking = True

    def save(self):
        self.asking = False

    def view(self):
        dialog = ui.modal(ui.muted("Save your changes before closing?"), title="Save changes?",
                          on_close=lambda: setattr(self, "asking", False),   # ✕ and Escape
                          footer=[ui.button("Save", self.save, variant="solid")], width=420)
        return ui.dialog_layer(main_view(), dialog if self.asking else None)
```

**Declarative:**

```python
class Notes(pz.App):
    asking = True

    def update(self, message):
        if message in ("save", "close"):
            self.asking = False

    def view(self):
        dialog = ui.modal(ui.muted("Save your changes?"), title="Save changes?", on_close="close",
                          footer=[ui.button("Save", "save", variant="solid")])
        return ui.dialog_layer(main_view(), dialog if self.asking else None)
```

**Functional:**

```python
from dataclasses import dataclass

@dataclass(frozen=True)
class State:
    asking: bool = True

def view(state, set_state):
    close = lambda: set_state(asking=False)
    dialog = ui.modal(ui.muted("Save your changes?"), title="Save changes?", on_close=close,
                      footer=[ui.button("Save", close, variant="solid")])
    return ui.dialog_layer(main_view(), dialog if state.asking else None)
```

## Properties and API

### `ui.modal`

```python
modal(
    content,
    *,
    title=None,
    on_close=None,
    width=480,
    footer=(),
    description=None,
    icon=None,
    max_height=None,
    theme=None,
)
```

A dialog. Use it as the `overlay` of `ui.layer`.

| Property | Default | Description |
| --- | --- | --- |
| `content` | *required* | Content: a `Widget`, a string or a number (converted to text). |
| `title` | `None` | Title. |
| `width` | `480` | Pixels or `"sm"` (360), `"md"` (480), `"lg"` (640) or `"xl"` (820). |
| `footer` | `()` | Footer buttons, aligned to the right. |
| `description` | `None` | Muted line under the title. |
| `icon` | `None` | Icon before the title. |
| `max_height` | `None` | Scrolls content taller than this, keeping the header and footer in view. |
| `theme` | `None` | Theme to use instead of the current one (`pyzed.theme.current()`). |

Events: `on_close` ([events](#events)).

[Full reference →](https://albakore.github.io/pyzed/reference/ui/#modal)

## Related

- [Dialog layer](https://albakore.github.io/pyzed/library/dialog-layer/): Centers a dialog over your view with a dimmed backdrop.
- [Layer](https://albakore.github.io/pyzed/library/layer/): Draws an overlay over your view; clicking outside dismisses it.
- [Confirm](https://albakore.github.io/pyzed/library/confirm/): A yes/no question, red for destructive actions.
