# Split button

> A main action plus an arrow that opens more options. A native Pyzed component for Python desktop GUIs, with a live preview, code examples and its API.

Source: https://albakore.github.io/pyzed/library/split-button/

:::tip[In plain words]
A button with two parts: the big part does the usual thing, the little arrow shows other ways to do it.
:::

A button with a separate chevron on the right. `on_press` runs the main action and `on_menu` usually opens a `ui.menu` inside a `ui.popover` with the alternatives.

`ui.split_button` · Category: [Actions](https://albakore.github.io/pyzed/library/#actions) · [Full reference →](https://albakore.github.io/pyzed/reference/ui/#split_button)

**Code:**

```python
pz.row([
    ui.split_button("Run", on_run, on_menu, icon="play"),
    ui.split_button("Deploy", on_deploy, on_menu, variant="solid"),
], spacing=12)
```

## When to use

- When one action is the usual one but there are a few variations (Run / Run with arguments).
- Avoid it when the options are equally important: use a [Select](https://albakore.github.io/pyzed/library/select/).

## Usage

```python
import pyzed as pz
from pyzed import ui

class Runner(pz.App):
    def __init__(self):
        self.menu_open = False

    def view(self):
        button = ui.split_button("Run", self.run, lambda: setattr(self, "menu_open", True), icon="play")
        menu = ui.menu([ui.MenuItem("Run with arguments…", self.run_args),
                        ui.MenuItem("Debug", self.debug, icon="bug")]) if self.menu_open else None
        return ui.popover(button, menu, y=32, on_dismiss=lambda: setattr(self, "menu_open", False))
```

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

### Variant

```python
ui.split_button("Run", on_run, on_menu, icon="play")
```

```python
ui.split_button("Run", on_run, on_menu, icon="play", variant="ghost")
```

```python
ui.split_button("Run", on_run, on_menu, icon="play", variant="outline")
```

```python
ui.split_button("Deploy", on_deploy, on_menu, icon="rocket", variant="solid")
```

### Size

```python
ui.split_button("Build", on_build, on_menu, size="sm")
```

```python
ui.split_button("Build", on_build, on_menu, size="md")
```

```python
ui.split_button("Build", on_build, on_menu, size="lg")
```

### Content

```python
ui.split_button("Merge", on_merge, on_menu)
```

```python
ui.split_button("Merge", on_merge, on_menu, icon="git-merge")
```

### States and events

```python
ui.split_button("Deploy", on_press, on_menu, tint="success")
```

```python
ui.split_button("Run", on_press, on_menu, disabled=True)
```

## Events

Split button has **2 events 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_press` | `()` | Message or callable sent on press. `None` disables the control. |
| `on_menu` | `()` | Message or callable for the menu button. |

**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 Runner(pz.App):
    def __init__(self):
        self.menu_open = False
        self.last = "nothing yet"

    def run(self, how="run"):
        self.last, self.menu_open = how, False

    def view(self):
        button = ui.split_button("Run", self.run,                     # on_press: the main action
                                 lambda: setattr(self, "menu_open", True),   # on_menu: the arrow
                                 icon="play")
        menu = ui.menu([ui.MenuItem("Debug", lambda: self.run("debug"), icon="bug")])
        return pz.column([
            ui.popover(button, menu if self.menu_open else None, y=32,
                       on_dismiss=lambda: setattr(self, "menu_open", False)),
            ui.label(f"Last: {self.last}"),
        ], spacing=8)
```

**Declarative:**

```python
from dataclasses import dataclass

@dataclass
class Run:
    how: str = "run"

class Runner(pz.App):
    menu_open = False
    last = "nothing yet"

    def update(self, message):
        match message:
            case Run(how):
                self.last, self.menu_open = how, False
            case "menu":
                self.menu_open = True
            case "dismiss":
                self.menu_open = False

    def view(self):
        menu = ui.menu([ui.MenuItem("Debug", Run("debug"), icon="bug")])
        return ui.popover(ui.split_button("Run", Run(), "menu", icon="play"),
                          menu if self.menu_open else None, y=32, on_dismiss="dismiss")
```

**Functional:**

```python
from dataclasses import dataclass

@dataclass(frozen=True)
class State:
    menu_open: bool = False
    last: str = "nothing yet"

def view(state, set_state):
    run = lambda how: set_state(last=how, menu_open=False)
    menu = ui.menu([ui.MenuItem("Debug", lambda: run("debug"), icon="bug")])
    return ui.popover(
        ui.split_button("Run", lambda: run("run"), lambda: set_state(menu_open=True), icon="play"),
        menu if state.menu_open else None, y=32, on_dismiss=lambda: set_state(menu_open=False))
```

## Properties and API

### `ui.split_button`

```python
split_button(
    content,
    on_press=None,
    on_menu=None,
    *,
    icon=None,
    variant='filled',
    tint=None,
    size='md',
    disabled=False,
    tooltip=None,
    theme=None,
)
```

A button with a separate arrow for more options. `on_press` runs the main action and `on_menu` usually opens a `ui.menu` in a `ui.popover`.

| Property | Default | Description |
| --- | --- | --- |
| `content` | *required* | Button text. |
| `icon` | `None` | Name of a Lucide icon (`pyzed icons` lists them). |
| `variant` | `"filled"` | As in `ui.button`. |
| `tint` | `None` | As in `ui.button`. |
| `size` | `"md"` | `"xs"`, `"sm"`, `"md"` or `"lg"`. |
| `disabled` | `False` | Disables both halves. |
| `tooltip` | `None` | Tooltip of the main half. |
| `theme` | `None` | Theme to use instead of the current one (`pyzed.theme.current()`). |

Events: `on_press`, `on_menu` ([events](#events)).

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

## Related

- [Button](https://albakore.github.io/pyzed/library/button/): The themed button: variants, sizes, icons and shortcuts.
- [Menu](https://albakore.github.io/pyzed/library/menu/): A context menu with icons, shortcuts and checks.
- [Popover](https://albakore.github.io/pyzed/library/popover/): Floating content at a point: context menus, color pickers.
