# Buttons and menus

> Two ways to add buttons and select menus, and how to pick the right one.

There are two ways to make buttons and menus work. Picking the right one saves a lot of code.

| | View | Component + event |
|---|---|---|
| Lives in | The command itself | A separate event |
| Works for | A few minutes after the command | Forever, even after the bot restarts |
| Can see the command's variables | Yes | No |
| Good for | Confirm prompts, games, pickers, quizzes | Role panels, ticket buttons, "click any time" panels |

**Rule of thumb:** if the click happens soon after the command, use a **View**. If the button must keep working for days, use a **component and an event**.

## Views

A **View** is a group of buttons and menus inside the command. You write what each button does right there, next to the rest of the command.

Pre-hook:

```python
class Confirm(View):
    def __init__(self, user_id):
        self.user_id = user_id   # only this person can click
        self.choice = None

    @button(label="Yes", style="success", custom_id="yes")
    async def yes(self, interaction, component):
        await defer(interaction, action="update")
        self.choice = "yes"
        self.disable_all_components()
        await self.update_view()
        self.stop()

    @button(label="No", style="danger", custom_id="no")
    async def no(self, interaction, component):
        await defer(interaction, action="update")
        self.choice = "no"
        self.disable_all_components()
        await self.update_view()
        self.stop()

view = Confirm(user.id)
view.interaction = interaction
set_view(view)
set_content("Are you sure?")
```

Post-hook:

```python
if view.choice == "yes":
    send_message(channel, "Confirmed.")
elif view.choice is None:
    send_message(channel, "Timed out.")
```

The post-hook runs after the View stops, or after it times out.

Things to know:

- Start every button with `await defer(interaction, action="update")`. It tells Discord the click was received.
- `user_id` limits who can click. Leave it out and anyone can click.
- `self.stop()` ends the View.
- `await self.update_view()` redraws the buttons, for example after you disable them.
- The View times out after 3 minutes with no clicks.
- Never call `super().__init__()`.
- With `set_view`, the bot waits for the View for you. Do not call `view.wait()`.

Full list: [Views](https://ccbot.app/docs/reference/views).

### Reading what was picked

In a menu callback, read the choice from `interaction.values`. It is always a list.

```python
@text_menu(
    options=[TextOption(label="Red", value="red"), TextOption(label="Blue", value="blue")],
    custom_id="color",
)
async def color(self, interaction, component):
    await defer(interaction, action="update")
    self.picked = interaction.values[0]
    self.stop()
```

> **Watch out:** `component` is the menu you wrote, not what the user picked. `component.values` does not hold the choice. Use `interaction.values`.

## Components and events

For a button that must work forever, split it in two:

1. **A command posts the button** with a `custom_id`.
2. **An event listens** for that `custom_id` and does the work.

Command pre-hook:

```python
c = ComponentBuilder()
c.add_button("Open ticket", style="primary", custom_id="ticket_open")
set_components(c)
set_content("Need help? Press the button.")
```

Event, type **Button Click / Select Menu Change**, Custom ids: `ticket_open`:

```python
defer_response(ephemeral=True)
respond_interaction(interaction, "Your ticket is being made…")
```

The event does not know anything from the command. It only gets `custom_id`, `user`, `channel`, `message` and `interaction`.

Full list: [Components](https://ccbot.app/docs/reference/components).

## The 3-second rule

Discord waits 3 seconds for an answer to a click. Without one, the user sees "This interaction failed".

- In a View button, start with `await defer(interaction, action="update")`.
- In an event, start with `defer_response(...)`.

Then do the slow work.

See [Interactions](https://ccbot.app/docs/reference/interactions).

## Discord limits

- At most 5 rows.
- At most 5 buttons in a row.
- A select menu takes a whole row by itself.
