The Basics#

This section explains how to use disnake-ext-components to create and manage your components, and how to send them to Discord so that your users can interact with them.

Components#

Component classes are all attrs classes. If you’re unfamiliar with attrs, know that they work very similarly to dataclasses. A component class has two types of fields:

  • Internal fields

    These are reserved for parameters that directly influence the component, such as the label field for a button. For the most part, these match the attributes on the standard disnake.ui component classes.

  • Custom id fields

    These are entirely user-defined, and their values are stored inside the components’ custom ids. Custom ids are limited by the discord api to have a maximum length of 100 characters, so the information you can store here is limited!

Important

Both types of fields are created in the same way as normal attrs/dataclass fields. In particular, keep in mind that typehinting fields is required.

The examples in the tabs below show all available custom id parameters for each component type, and provide a very minimal example of how one such component could look.

The disnake-ext-components-equivalent of a disnake.ui.Button.

 1class MyButton(components.RichButton):
 2    # All valid internal fields:
 3    label: str = "My button"
 4    style: disnake.ButtonStyle = disnake.ButtonStyle.primary
 5    emoji: str = "\N{WHITE HEAVY CHECK MARK}"
 6    disabled: bool = False
 7
 8    # Custom id fields:
 9    foo: int      # field without default
10    bar: int = 3  # field with default
11
12    # A callback is required:
13    async def callback(self, inter: disnake.MessageInteraction):
14        await inter.response.send_message("Click!")

Important

Since callbacks are required and URL-buttons cannot have a callback, disnake-ext-components does not support the url attribute.

The disnake-ext-components-equivalent of a disnake.ui.StringSelect.

 1class MyButton(components.RichStringSelect):
 2    # All valid internal fields:
 3    placeholder: str = "My string select"
 4    min_values: int = 1
 5    max_values: int = 2
 6    disabled: bool = False
 7    options: List[disnake.SelectOption] = [
 8        disnake.SelectOption(label="foo", value=1),
 9        disnake.SelectOption(label="bar", value=2),
10    ]
11
12    # Custom id fields:
13    foo: int      # field without default
14    bar: int = 3  # field with default
15
16    # A callback is required:
17    async def callback(self, inter: disnake.MessageInteraction):
18        await inter.response.send_message("Click!")

The disnake-ext-components-equivalent of a disnake.ui.UserSelect.

 1class MyButton(components.RichUserSelect):
 2    # All valid internal fields:
 3    placeholder: str = "My user select"
 4    min_values: int = 1
 5    max_values: int = 2
 6    disabled: bool = False
 7
 8    # Custom id fields:
 9    foo: int      # field without default
10    bar: int = 3  # field with default
11
12    # A callback is required:
13    async def callback(self, inter: disnake.MessageInteraction):
14        await inter.response.send_message("Click!")

The disnake-ext-components-equivalent of a disnake.ui.RoleSelect.

 1class MyButton(components.RichRoleSelect):
 2    # All valid internal fields:
 3    placeholder: str = "My role select"
 4    min_values: int = 1
 5    max_values: int = 2
 6    disabled: bool = False
 7
 8    # Custom id fields:
 9    foo: int      # field without default
10    bar: int = 3  # field with default
11
12    # A callback is required:
13    async def callback(self, inter: disnake.MessageInteraction):
14        await inter.response.send_message("Click!")

The disnake-ext-components-equivalent of a disnake.ui.ChannelSelect.

 1class MyButton(components.RichChannelSelect):
 2    # All valid internal fields:
 3    placeholder: str = "My channel select"
 4    min_values: int = 1
 5    max_values: int = 2
 6    disabled: bool = False
 7
 8    # Custom id fields:
 9    foo: int      # field without default
10    bar: int = 3  # field with default
11
12    # A callback is required:
13    async def callback(self, inter: disnake.MessageInteraction):
14        await inter.response.send_message("Click!")

The disnake-ext-components-equivalent of a disnake.ui.MentionableSelect.

 1class MyButton(components.RichMentionableSelect):
 2    # All valid internal fields:
 3    placeholder: str = "My mentionable select"
 4    min_values: int = 1
 5    max_values: int = 2
 6    disabled: bool = False
 7
 8    # Custom id fields:
 9    foo: int      # field without default
10    bar: int = 3  # field with default
11
12    # A callback is required:
13    async def callback(self, inter: disnake.MessageInteraction):
14        await inter.response.send_message("Click!")

Since these classes are created using attrs, the __init__ methods for your component classes are automatically generated. If you need further control, you can use attrs features like __attrs_post_init__ to process each instance before they are handled by disnake-ext-components.

Component Managers#

Now that we know how to create components, we need to learn how to hook these components into your bot. Luckily, this is pretty simple. All we need is a ComponentManager, which we get using get_manager(). For basic usage, we just call get_manager() without arguments and assign it to a variable. We then use add_to_bot() and pass the bot to it to allow the manager to communicate with the bot. Finally, we register components to the bot using register(). This can be done in a few different ways, but for now the easiest way is to just use it as a basic decorator.

 1import disnake
 2from disnake.ext import commands, components
 3
 4bot = disnake.Bot(...)
 5manager = components.get_manager()
 6manager.add_to_bot(bot)
 7
 8@manager.register
 9class MyButton(components.RichButton):
10    label: str = "My Button"
11
12    foo: int      # field without default
13    bar: int = 3  # field with default
14
15    async def callback(self, inter: disnake.MessageInteraction):
16        await inter.response.send_message("Click!")

Important

The use of disnake.Client with disnake-ext-components requires disnake version 2.10.0 or above. On lower versions of disnake, you need to use any of disnake’s bot classes.

Sending Components#

Last but not least, we need to send our components to discord. To do so, we first need to create an instance of our button class. This is simply done by instantiating the class as with any other class. Any custom id fields without a default value must be provided. Since the class is made using attrs, it is fully typehinted, and your typechecker will let you know if you are missing anything.

Tip

We definitely recommend using a type checker! pyright is particularly compatible as disnake-ext-components was developed with it.

Actually sending the component works slightly differently from normal disnake.ui components. We have two options:

  • Explicit conversion

    We can explicitly convert our disnake-ext-components-style component into a disnake.ui-style component using as_ui_component().

  • Interaction wrapping

    Alternatively, we can wrap an interaction into a new disnake-ext-components-style interaction, which can automatically deal with disnake-ext-components-style components. This is done using wrap_interaction().

    Important

    Interactions provided to component callbacks will automatically be wrapped. If you plan to use text commands, you must use explicit conversion, as wrap_interaction() does not support commands.Context.

The examples in the tabs below show you how either of these options would look. You are free to pick whichever syntax you are more comfortable with.

 1class MyButton(components.RichButton):
 2    label = "My button"
 3
 4    foo: int      # field without default
 5    bar: int = 3  # field with default
 6
 7    async def callback(self, inter: disnake.MessageInteraction):
 8        new_button = await self.as_ui_component()
 9        await inter.response.send_message("Click!", components=new_button)
10
11
12@commands.slash_command()
13async def my_command(inter: disnake.MessageInteraction):
14    button = MyButton(foo=1)
15    ui_button = await button.as_ui_component()
16    await inter.response.send_message(components=ui_button)
 1class MyButton(components.RichButton):
 2    label = "My button"
 3
 4    foo: int      # field without default
 5    bar: int = 3  # field with default
 6
 7    async def callback(self, inter: components.MessageInteraction):
 8        await inter.response.send_message("Click!", components=self)
 9
10
11@commands.slash_command()
12async def my_command(inter: disnake.MessageInteraction):
13    wrapped = components.wrap_interaction(inter)
14    button = MyButton(foo=1)
15    await inter.response.send_message(components=button)

Note

The interaction in the button callback is now typehinted as a components.MessageInteraction as opposed to a disnake.MessageInteraction. This is only relevant for type-checking purposes.

Example#

You know know enough to make a fully functional component with disnake-ext-components! Combining the examples of the above sections nets you the following bot main file:

 1import disnake
 2from disnake.ext import commands, components
 3
 4bot = disnake.Bot(...)
 5manager = components.get_manager()
 6manager.add_to_bot(bot)
 7
 8@manager.register
 9class MyButton(components.RichButton):
10    label: str = "My button"
11
12    foo: int      # field without default
13    bar: int = 3  # field with default
14
15    async def callback(self, inter: disnake.MessageInteraction):
16        await inter.response.send_message("Click!", components=self)
17
18
19@commands.slash_command()
20async def my_command(inter: disnake.MessageInteraction):
21    wrapped = components.wrap_interaction(inter)
22    button = MyButton(foo=1)
23    await inter.response.send_message(components=button)
24
25bot.run(...)