From 63c3ffdb483c9263e02f85c240a5e23c5eee7a7c Mon Sep 17 00:00:00 2001 From: Nelo <41271523+NeloBlivion@users.noreply.github.com> Date: Tue, 20 Jan 2026 23:02:06 -0500 Subject: [PATCH 01/52] components --- discord/components.py | 371 +++++++++++++++++++++++++++++++++++- discord/enums.py | 3 + discord/types/components.py | 38 +++- 3 files changed, 409 insertions(+), 3 deletions(-) diff --git a/discord/components.py b/discord/components.py index 9775c97f01..587cd576d7 100644 --- a/discord/components.py +++ b/discord/components.py @@ -63,6 +63,11 @@ from .types.components import TextDisplayComponent as TextDisplayComponentPayload from .types.components import ThumbnailComponent as ThumbnailComponentPayload from .types.components import UnfurledMediaItem as UnfurledMediaItemPayload + from .types.components import CheckboxGroupComponent as CheckboxGroupComponentPayload + from .types.components import CheckboxGroupOption as CheckboxGroupOptionPayload + from .types.components import RadioGroupComponent as RadioGroupComponentPayload + from .types.components import RadioGroupOption as RadioGroupOptionPayload + from .types.components import CheckboxComponent as CheckboxComponentPayload __all__ = ( "Component", @@ -83,6 +88,11 @@ "Label", "SelectDefaultValue", "FileUpload", + "RadioGroup", + "RadioGroupOption", + "CheckboxGroup", + "CheckboxGroupOption", + "Checkbox" ) C = TypeVar("C", bound="Component") @@ -1331,7 +1341,7 @@ class Label(Component): __slots__: tuple[str, ...] = ("component", "label", "description") __repr_info__: ClassVar[tuple[str, ...]] = __slots__ - versions: tuple[int, ...] = () + versions: tuple[int, ...] = (2, ) def __init__(self, data: LabelComponentPayload): self.type: ComponentType = try_enum(ComponentType, data["type"]) @@ -1389,7 +1399,7 @@ class FileUpload(Component): ) __repr_info__: ClassVar[tuple[str, ...]] = __slots__ - versions: tuple[int, ...] = (1, 2) + versions: tuple[int, ...] = (2, ) def __init__(self, data: FileUploadComponentPayload): self.type = ComponentType.file_upload @@ -1419,6 +1429,360 @@ def to_dict(self) -> FileUploadComponentPayload: return payload # type: ignore +class RadioGroup(Component): + """Represents an Radio Group component from the Discord Bot UI Kit. + + This inherits from :class:`Component`. + + .. note:: + + This class is not useable by end-users; see :class:`discord.ui.RadioGroup` instead. + + .. versionadded:: 2.7.1 + + Attributes + ---------- + custom_id: Optional[:class:`str`] + The custom ID of the radio group that gets received during an interaction. + options: List[:class:`RadioGroupOption`] + A list of options that can be selected in this group. + required: Optional[:class:`bool`] + Whether the radio group requires a selection or not. Defaults to `True`. + id: Optional[:class:`int`] + The radio group's ID. + """ + + __slots__: tuple[str, ...] = ( + "type", + "custom_id", + "options", + "required", + "id", + ) + + __repr_info__: ClassVar[tuple[str, ...]] = __slots__ + versions: tuple[int, ...] = (2, ) + + def __init__(self, data: RadioGroupComponentPayload): + self.type = ComponentType.radio_group + self.id: int | None = data.get("id") + self.custom_id = data.get("custom_id") + self.options: list[RadioGroupOption] = [ + RadioGroupOption.from_dict(option) for option in data.get("options", []) + ] + self.required: bool = data.get("required", True) + + def to_dict(self) -> RadioGroupComponentPayload: + payload = { + "type": 21, + "custom_id": self.custom_id, + "options": [opt.to_dict() for opt in self.options] + } + if self.id is not None: + payload["id"] = self.id + + if not self.required: + payload["required"] = self.required + + return payload + + +class RadioGroupOption: + """Represents a :class:`discord.RadioGroup`'s option. + + These can be created by users. + + .. versionadded:: 2.7.1 + + Attributes + ---------- + label: :class:`str` + The label of the option. This is displayed to users. + Can only be up to 100 characters. + value: :class:`str` + The value of the option. This is not displayed to users. + If not provided when constructed then it defaults to the + label. Can only be up to 100 characters. + description: Optional[:class:`str`] + An additional description of the option, if any. + Can only be up to 100 characters. + default: :class:`bool` + Whether this option is selected by default. + """ + + __slots__: tuple[str, ...] = ( + "label", + "value", + "description", + "default", + ) + + def __init__( + self, + *, + label: str, + value: str = MISSING, + description: str | None = None, + default: bool = False, + ) -> None: + if len(label) > 100: + raise ValueError("label must be 100 characters or fewer") + + if value is not MISSING and len(value) > 100: + raise ValueError("value must be 100 characters or fewer") + + if description is not None and len(description) > 100: + raise ValueError("description must be 100 characters or fewer") + + self.label = label + self.value = label if value is MISSING else value + self.description = description + self.default = default + + def __repr__(self) -> str: + return ( + "" + ) + + def __str__(self) -> str: + if self.description: + return f"{self.label}\n{self.description}" + return self.label + + @classmethod + def from_dict(cls, data: RadioGroupOptionPayload) -> RadioGroupOption: + return cls( + label=data["label"], + value=data["value"], + description=data.get("description"), + default=data.get("default", False), + ) + + def to_dict(self) -> RadioGroupOptionPayload: + payload: RadioGroupOptionPayload = { + "label": self.label, + "value": self.value, + "default": self.default, + } + + if self.description: + payload["description"] = self.description + + return payload + + +class CheckboxGroup(Component): + """Represents an Checkbox Group component from the Discord Bot UI Kit. + + This inherits from :class:`Component`. + + .. note:: + + This class is not useable by end-users; see :class:`discord.ui.CheckboxGroup` instead. + + .. versionadded:: 2.7.1 + + Attributes + ---------- + custom_id: Optional[:class:`str`] + The custom ID of the checkbox group that gets received during an interaction. + options: List[:class:`CheckboxGroupOption`] + A list of options that can be selected in this group. + min_values: Optional[:class:`int`] + The minimum number of options that must be selected. + max_values: Optional[:class:`int`] + The maximum number of options that can be selected. + required: Optional[:class:`bool`] + Whether the checkbox group requires a selection or not. Defaults to `True`. + id: Optional[:class:`int`] + The checkbox group's ID. + """ + + __slots__: tuple[str, ...] = ( + "type", + "custom_id", + "options", + "min_values", + "max_values", + "required", + "id", + ) + + __repr_info__: ClassVar[tuple[str, ...]] = __slots__ + versions: tuple[int, ...] = (2, ) + + def __init__(self, data: CheckboxGroupComponentPayload): + self.type = ComponentType.checkbox_group + self.id: int | None = data.get("id") + self.custom_id = data.get("custom_id") + self.options: list[CheckboxGroupOption] = [ + CheckboxGroupOption.from_dict(option) for option in data.get("options", []) + ] + self.min_values: int | None = data.get("min_values", None) + self.max_values: int | None = data.get("max_values", None) + self.required: bool = data.get("required", True) + + def to_dict(self) -> CheckboxGroupComponentPayload: + payload = { + "type": 22, + "custom_id": self.custom_id, + "options": [opt.to_dict() for opt in self.options] + } + if self.id is not None: + payload["id"] = self.id + + if self.min_values is not None: + payload["min_values"] = self.min_values + + if self.max_values is not None: + payload["max_values"] = self.max_values + + if not self.required: + payload["required"] = self.required + + return payload + + +class CheckboxGroupOption: + """Represents a :class:`discord.CheckboxGroup`'s option. + + These can be created by users. + + .. versionadded:: 2.7.1 + + Attributes + ---------- + label: :class:`str` + The label of the option. This is displayed to users. + Can only be up to 100 characters. + value: :class:`str` + The value of the option. This is not displayed to users. + If not provided when constructed then it defaults to the + label. Can only be up to 100 characters. + description: Optional[:class:`str`] + An additional description of the option, if any. + Can only be up to 100 characters. + default: :class:`bool` + Whether this option is selected by default. + """ + + __slots__: tuple[str, ...] = ( + "label", + "value", + "description", + "default", + ) + + def __init__( + self, + *, + label: str, + value: str = MISSING, + description: str | None = None, + default: bool = False, + ) -> None: + if len(label) > 100: + raise ValueError("label must be 100 characters or fewer") + + if value is not MISSING and len(value) > 100: + raise ValueError("value must be 100 characters or fewer") + + if description is not None and len(description) > 100: + raise ValueError("description must be 100 characters or fewer") + + self.label = label + self.value = label if value is MISSING else value + self.description = description + self.default = default + + def __repr__(self) -> str: + return ( + "" + ) + + def __str__(self) -> str: + if self.description: + return f"{self.label}\n{self.description}" + return self.label + + @classmethod + def from_dict(cls, data: CheckboxGroupOptionPayload) -> CheckboxGroupOption: + return cls( + label=data["label"], + value=data["value"], + description=data.get("description"), + default=data.get("default", False), + ) + + def to_dict(self) -> CheckboxGroupOptionPayload: + payload: CheckboxGroupOptionPayload = { + "label": self.label, + "value": self.value, + "default": self.default, + } + + if self.description: + payload["description"] = self.description + + return payload + + +class Checkbox(Component): + """Represents an Checkbox component from the Discord Bot UI Kit. + + This inherits from :class:`Component`. + + .. note:: + + This class is not useable by end-users; see :class:`discord.ui.Checkbox` instead. + + .. versionadded:: 2.7.1 + + Attributes + ---------- + custom_id: Optional[:class:`str`] + The custom ID of the checkbox group that gets received during an interaction. + required: Optional[:class:`bool`] + Whether this checkbox is selected by default. + id: Optional[:class:`int`] + The checkbox group's ID. + """ + + __slots__: tuple[str, ...] = ( + "type", + "custom_id", + "default", + "id", + ) + + __repr_info__: ClassVar[tuple[str, ...]] = __slots__ + versions: tuple[int, ...] = (2, ) + + def __init__(self, data: CheckboxComponentPayload): + self.type = ComponentType.checkbox + self.id: int | None = data.get("id") + self.custom_id = data.get("custom_id") + self.default: bool = data.get("default", False) + + def to_dict(self) -> CheckboxComponentPayload: + payload = { + "type": 23, + "custom_id": self.custom_id, + "options": [opt.to_dict() for opt in self.options] + } + if self.id is not None: + payload["id"] = self.id + + if self.default is not None: + payload["default"] = self.default + + return payload + + COMPONENT_MAPPINGS = { 1: ActionRow, 2: Button, @@ -1437,6 +1801,9 @@ def to_dict(self) -> FileUploadComponentPayload: 17: Container, 18: Label, 19: FileUpload, + 21: RadioGroup, + 22: CheckboxGroup, + 23: Checkbox, } STATE_COMPONENTS = (Section, Container, Thumbnail, MediaGallery, FileComponent) diff --git a/discord/enums.py b/discord/enums.py index 63557c853b..2366a6c78b 100644 --- a/discord/enums.py +++ b/discord/enums.py @@ -736,6 +736,9 @@ class ComponentType(Enum): container = 17 label = 18 file_upload = 19 + radio_group = 21 + checkbox_group = 22 + checkbox = 23 def __int__(self): return self.value diff --git a/discord/types/components.py b/discord/types/components.py index f78d3c78a1..6def34d375 100644 --- a/discord/types/components.py +++ b/discord/types/components.py @@ -33,7 +33,7 @@ from .emoji import PartialEmoji from .snowflake import Snowflake -ComponentType = Literal[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 16, 17, 18, 19] +ComponentType = Literal[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 16, 17, 18, 19, 21, 22, 23] ButtonStyle = Literal[1, 2, 3, 4, 5, 6] InputTextStyle = Literal[1, 2] SeparatorSpacingSize = Literal[1, 2] @@ -174,6 +174,42 @@ class FileUploadComponent(BaseComponent): required: NotRequired[bool] +class RadioGroupOption(TypedDict): + value: str, + label: str, + description: NotRequired[str] + default: NotRequired[bool] + + +class RadioGroupComponent(BaseComponent): + type: Literal[21] + custom_id: str + required: NotRequired[bool] + options: list[RadioGroupOption] + + +class CheckboxGroupOption(TypedDict): + value: str, + label: str, + description: NotRequired[str] + default: NotRequired[bool] + + +class CheckboxGroupComponent(BaseComponent): + type: Literal[22] + custom_id: str + required: NotRequired[bool] + options: list[CheckboxGroupOption] + max_values: NotRequired[int] + min_values: NotRequired[int] + + +class CheckboxComponent(BaseComponent): + type: Literal[23] + custom_id: str + default: NotRequired[bool] + + Component = Union[ ActionRow, ButtonComponent, SelectMenu, InputText, FileUploadComponent ] From 1241ce3cac4c5b652667e9e79fd2b5e6150cd857 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 21 Jan 2026 04:05:07 +0000 Subject: [PATCH 02/52] style(pre-commit): auto fixes from pre-commit.com hooks --- discord/components.py | 30 ++++++++++++++++-------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/discord/components.py b/discord/components.py index 587cd576d7..7aa7fa605a 100644 --- a/discord/components.py +++ b/discord/components.py @@ -47,6 +47,11 @@ from .emoji import AppEmoji, GuildEmoji from .types.components import ActionRow as ActionRowPayload from .types.components import ButtonComponent as ButtonComponentPayload + from .types.components import CheckboxComponent as CheckboxComponentPayload + from .types.components import ( + CheckboxGroupComponent as CheckboxGroupComponentPayload, + ) + from .types.components import CheckboxGroupOption as CheckboxGroupOptionPayload from .types.components import Component as ComponentPayload from .types.components import ContainerComponent as ContainerComponentPayload from .types.components import FileComponent as FileComponentPayload @@ -55,6 +60,8 @@ from .types.components import LabelComponent as LabelComponentPayload from .types.components import MediaGalleryComponent as MediaGalleryComponentPayload from .types.components import MediaGalleryItem as MediaGalleryItemPayload + from .types.components import RadioGroupComponent as RadioGroupComponentPayload + from .types.components import RadioGroupOption as RadioGroupOptionPayload from .types.components import SectionComponent as SectionComponentPayload from .types.components import SelectDefaultValue as SelectDefaultValuePayload from .types.components import SelectMenu as SelectMenuPayload @@ -63,11 +70,6 @@ from .types.components import TextDisplayComponent as TextDisplayComponentPayload from .types.components import ThumbnailComponent as ThumbnailComponentPayload from .types.components import UnfurledMediaItem as UnfurledMediaItemPayload - from .types.components import CheckboxGroupComponent as CheckboxGroupComponentPayload - from .types.components import CheckboxGroupOption as CheckboxGroupOptionPayload - from .types.components import RadioGroupComponent as RadioGroupComponentPayload - from .types.components import RadioGroupOption as RadioGroupOptionPayload - from .types.components import CheckboxComponent as CheckboxComponentPayload __all__ = ( "Component", @@ -92,7 +94,7 @@ "RadioGroupOption", "CheckboxGroup", "CheckboxGroupOption", - "Checkbox" + "Checkbox", ) C = TypeVar("C", bound="Component") @@ -1341,7 +1343,7 @@ class Label(Component): __slots__: tuple[str, ...] = ("component", "label", "description") __repr_info__: ClassVar[tuple[str, ...]] = __slots__ - versions: tuple[int, ...] = (2, ) + versions: tuple[int, ...] = (2,) def __init__(self, data: LabelComponentPayload): self.type: ComponentType = try_enum(ComponentType, data["type"]) @@ -1399,7 +1401,7 @@ class FileUpload(Component): ) __repr_info__: ClassVar[tuple[str, ...]] = __slots__ - versions: tuple[int, ...] = (2, ) + versions: tuple[int, ...] = (2,) def __init__(self, data: FileUploadComponentPayload): self.type = ComponentType.file_upload @@ -1461,7 +1463,7 @@ class RadioGroup(Component): ) __repr_info__: ClassVar[tuple[str, ...]] = __slots__ - versions: tuple[int, ...] = (2, ) + versions: tuple[int, ...] = (2,) def __init__(self, data: RadioGroupComponentPayload): self.type = ComponentType.radio_group @@ -1476,7 +1478,7 @@ def to_dict(self) -> RadioGroupComponentPayload: payload = { "type": 21, "custom_id": self.custom_id, - "options": [opt.to_dict() for opt in self.options] + "options": [opt.to_dict() for opt in self.options], } if self.id is not None: payload["id"] = self.id @@ -1611,7 +1613,7 @@ class CheckboxGroup(Component): ) __repr_info__: ClassVar[tuple[str, ...]] = __slots__ - versions: tuple[int, ...] = (2, ) + versions: tuple[int, ...] = (2,) def __init__(self, data: CheckboxGroupComponentPayload): self.type = ComponentType.checkbox_group @@ -1628,7 +1630,7 @@ def to_dict(self) -> CheckboxGroupComponentPayload: payload = { "type": 22, "custom_id": self.custom_id, - "options": [opt.to_dict() for opt in self.options] + "options": [opt.to_dict() for opt in self.options], } if self.id is not None: payload["id"] = self.id @@ -1760,7 +1762,7 @@ class Checkbox(Component): ) __repr_info__: ClassVar[tuple[str, ...]] = __slots__ - versions: tuple[int, ...] = (2, ) + versions: tuple[int, ...] = (2,) def __init__(self, data: CheckboxComponentPayload): self.type = ComponentType.checkbox @@ -1772,7 +1774,7 @@ def to_dict(self) -> CheckboxComponentPayload: payload = { "type": 23, "custom_id": self.custom_id, - "options": [opt.to_dict() for opt in self.options] + "options": [opt.to_dict() for opt in self.options], } if self.id is not None: payload["id"] = self.id From 1bd3289f8212eff2c5c8384357393034e07133a8 Mon Sep 17 00:00:00 2001 From: Lala Sabathil Date: Wed, 21 Jan 2026 05:15:56 +0100 Subject: [PATCH 03/52] Apply suggestions from code review Signed-off-by: Lala Sabathil --- discord/types/components.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/discord/types/components.py b/discord/types/components.py index 6def34d375..4f4d4229a5 100644 --- a/discord/types/components.py +++ b/discord/types/components.py @@ -176,7 +176,8 @@ class FileUploadComponent(BaseComponent): class RadioGroupOption(TypedDict): value: str, - label: str, + value: str + label: str description: NotRequired[str] default: NotRequired[bool] @@ -189,8 +190,8 @@ class RadioGroupComponent(BaseComponent): class CheckboxGroupOption(TypedDict): - value: str, - label: str, + value: str + label: str description: NotRequired[str] default: NotRequired[bool] From b9d7c72cf41a98329979cc1a6a38b2a04b604575 Mon Sep 17 00:00:00 2001 From: Lala Sabathil Date: Wed, 21 Jan 2026 05:16:13 +0100 Subject: [PATCH 04/52] Apply suggestion from @Lulalaby Signed-off-by: Lala Sabathil --- discord/types/components.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/discord/types/components.py b/discord/types/components.py index 4f4d4229a5..569e705439 100644 --- a/discord/types/components.py +++ b/discord/types/components.py @@ -175,7 +175,7 @@ class FileUploadComponent(BaseComponent): class RadioGroupOption(TypedDict): - value: str, + value: str value: str label: str description: NotRequired[str] From b083cc7fb670d090c80d568c5cad47787a9a3835 Mon Sep 17 00:00:00 2001 From: Lala Sabathil Date: Wed, 21 Jan 2026 05:16:26 +0100 Subject: [PATCH 05/52] Apply suggestion from @Lulalaby Signed-off-by: Lala Sabathil --- discord/types/components.py | 1 - 1 file changed, 1 deletion(-) diff --git a/discord/types/components.py b/discord/types/components.py index 569e705439..7c2fb186da 100644 --- a/discord/types/components.py +++ b/discord/types/components.py @@ -175,7 +175,6 @@ class FileUploadComponent(BaseComponent): class RadioGroupOption(TypedDict): - value: str value: str label: str description: NotRequired[str] From 3b1cca6775f5815d5985fb80666959a597dea83f Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 21 Jan 2026 04:17:20 +0000 Subject: [PATCH 06/52] style(pre-commit): auto fixes from pre-commit.com hooks --- discord/types/components.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/discord/types/components.py b/discord/types/components.py index 7c2fb186da..c2973788a5 100644 --- a/discord/types/components.py +++ b/discord/types/components.py @@ -33,7 +33,9 @@ from .emoji import PartialEmoji from .snowflake import Snowflake -ComponentType = Literal[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 16, 17, 18, 19, 21, 22, 23] +ComponentType = Literal[ + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 16, 17, 18, 19, 21, 22, 23 +] ButtonStyle = Literal[1, 2, 3, 4, 5, 6] InputTextStyle = Literal[1, 2] SeparatorSpacingSize = Literal[1, 2] From fbd34beae2330bcf704e64ce9b058fadb3570c80 Mon Sep 17 00:00:00 2001 From: Nelo <41271523+NeloBlivion@users.noreply.github.com> Date: Tue, 20 Jan 2026 23:36:57 -0500 Subject: [PATCH 07/52] ui.RadioGroup --- discord/ui/file_upload.py | 2 +- discord/ui/radio_group.py | 121 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 122 insertions(+), 1 deletion(-) create mode 100644 discord/ui/radio_group.py diff --git a/discord/ui/file_upload.py b/discord/ui/file_upload.py index 1f4222b0e0..7f311bc6e3 100644 --- a/discord/ui/file_upload.py +++ b/discord/ui/file_upload.py @@ -23,7 +23,7 @@ class FileUpload(ModalItem): Parameters ---------- custom_id: Optional[:class:`str`] - The ID of the input text field that gets received during an interaction. + The ID of the file upload field that gets received during an interaction. min_values: Optional[:class:`int`] The minimum number of files that must be uploaded. Defaults to 0 and must be between 0 and 10, inclusive. diff --git a/discord/ui/radio_group.py b/discord/ui/radio_group.py new file mode 100644 index 0000000000..d34421e48a --- /dev/null +++ b/discord/ui/radio_group.py @@ -0,0 +1,121 @@ +from __future__ import annotations + +import os +from typing import TYPE_CHECKING + +from ..components import RadioGroup as RadioGroupComponent +from ..components import RadioGroupOption +from ..enums import ComponentType +from .item import ModalItem + +__all__ = ("RadioGroup",) + +if TYPE_CHECKING: + from ..interactions import Interaction + from ..types.components import RadioGroupComponent as RadioGroupComponentPayload + + +class RadioGroup(ModalItem): + """Represents a UI Radio Group component. + + .. versionadded:: 2.7.1 + + Parameters + ---------- + custom_id: Optional[:class:`str`] + The ID of the radio group that gets received during an interaction. + options: List[:class:`discord.RadioGroupOption`] + A list of options that can be selected from this group. + required: Optional[:class:`bool`] + Whether an option selection is required or not. Defaults to ``True``. + id: Optional[:class:`int`] + The radio group's ID. + """ + + __item_repr_attributes__: tuple[str, ...] = ( + "options", + "required", + "custom_id", + "id", + ) + + def __init__( + self, + *, + custom_id: str | None = None + options: list[RadioGroupOption] | None = None, + required: bool = True, + id: int | None = None, + ): + super().__init__() + if custom_id is not None and not isinstance(custom_id, str): + raise TypeError( + f"expected custom_id to be str, not {custom_id.__class__.__name__}" + ) + if not isinstance(required, bool): + raise TypeError(f"required must be bool not {required.__class__.__name__}") # type: ignore + custom_id = os.urandom(16).hex() if custom_id is None else custom_id + + self._underlying: RadioGroupComponent = RadioGroupComponent._raw_construct( + type=ComponentType.radio_group, + custom_id=custom_id, + options=options or [], + required=required, + id=id, + ) + self._selected_value: str | None = None + + def __repr__(self) -> str: + attrs = " ".join( + f"{key}={getattr(self, key)!r}" for key in self.__item_repr_attributes__ + ) + return f"<{self.__class__.__name__} {attrs}>" + + @property + def type(self) -> ComponentType: + return self._underlying.type + + @property + def id(self) -> int | None: + """The ID of this component. If not provided by the user, it is set sequentially by Discord.""" + return self._underlying.id + + @property + def custom_id(self) -> str: + """The custom id that gets received during an interaction.""" + return self._underlying.custom_id + + @custom_id.setter + def custom_id(self, value: str): + if not isinstance(value, str): + raise TypeError( + f"custom_id must be None or str not {value.__class__.__name__}" + ) + self._underlying.custom_id = value + + @property + def required(self) -> bool: + """Whether an option selection is required or not. Defaults to ``True``""" + return self._underlying.required + + @required.setter + def required(self, value: bool): + if not isinstance(value, bool): + raise TypeError(f"required must be bool, not {value.__class__.__name__}") # type: ignore + self._underlying.required = bool(value) + + @property + def value(self) -> str | None: + """The value selected by the user.""" + return self._selected_value + + def to_component_dict(self) -> RadioGroupComponentPayload: + return self._underlying.to_dict() + + def refresh_state(self, data) -> None: + self._selected_value = data.get("value", None) + + def refresh_from_modal( + self, interaction: Interaction, data: RadioGroupComponentPayload + ) -> None: + return self.refresh_state(data) From ce915561042ecb05d75cc63fd596a47fc7e2ff98 Mon Sep 17 00:00:00 2001 From: Nelo <41271523+NeloBlivion@users.noreply.github.com> Date: Tue, 20 Jan 2026 23:48:00 -0500 Subject: [PATCH 08/52] ui.CheckboxGroup --- discord/ui/checkbox_group.py | 163 +++++++++++++++++++++++++++++++++++ discord/ui/file_upload.py | 4 +- 2 files changed, 165 insertions(+), 2 deletions(-) create mode 100644 discord/ui/checkbox_group.py diff --git a/discord/ui/checkbox_group.py b/discord/ui/checkbox_group.py new file mode 100644 index 0000000000..d6e478fc6d --- /dev/null +++ b/discord/ui/checkbox_group.py @@ -0,0 +1,163 @@ +from __future__ import annotations + +import os +from typing import TYPE_CHECKING + +from ..components import CheckboxGroup as CheckboxGroupComponent +from ..components import CheckboxGroupOption +from ..enums import ComponentType +from .item import ModalItem + +__all__ = ("CheckboxGroup",) + +if TYPE_CHECKING: + from ..interactions import Interaction + from ..types.components import CheckboxGroupComponent as CheckboxGroupComponentPayload + + +class CheckboxGroup(ModalItem): + """Represents a UI Checkbox Group component. + + .. versionadded:: 2.7.1 + + Parameters + ---------- + custom_id: Optional[:class:`str`] + The ID of the checkbox group that gets received during an interaction. + options: List[:class:`discord.CheckboxGroupOption`] + A list of options that can be selected in this group. + min_values: Optional[:class:`int`] + The minimum number of options that must be selected. + Defaults to 0 and must be between 0 and 10, inclusive. + max_values: Optional[:class:`int`] + The maximum number of options that can be selected. + Must be between 1 and 10, inclusive. + required: Optional[:class:`bool`] + Whether an option selection is required or not. Defaults to ``True``. + id: Optional[:class:`int`] + The checkbox group's ID. + """ + + __item_repr_attributes__: tuple[str, ...] = ( + "options", + "required", + "min_values", + "max_values", + "custom_id", + "id", + ) + + def __init__( + self, + *, + custom_id: str | None = None + options: list[CheckboxGroupOption] | None = None, + min_values: int | None = None, + max_values: int | None = None, + required: bool = True, + id: int | None = None, + ): + super().__init__() + if min_values and (min_values < 0 or min_values > 10): + raise ValueError("min_values must be between 0 and 10") + if max_values and (max_values < 1 or max_values > 10): + raise ValueError("max_values must be between 1 and 10") + if custom_id is not None and not isinstance(custom_id, str): + raise TypeError( + f"expected custom_id to be str, not {custom_id.__class__.__name__}" + ) + if not isinstance(required, bool): + raise TypeError(f"required must be bool not {required.__class__.__name__}") # type: ignore + custom_id = os.urandom(16).hex() if custom_id is None else custom_id + + self._underlying: CheckboxGroupComponent = CheckboxGroupComponent._raw_construct( + type=ComponentType.checkbox_group, + custom_id=custom_id, + options=options or [], + min_values=min_values, + max_values=max_values, + required=required, + id=id, + ) + self._selected_values: list[str] = [] + + def __repr__(self) -> str: + attrs = " ".join( + f"{key}={getattr(self, key)!r}" for key in self.__item_repr_attributes__ + ) + return f"<{self.__class__.__name__} {attrs}>" + + @property + def type(self) -> ComponentType: + return self._underlying.type + + @property + def id(self) -> int | None: + """The ID of this component. If not provided by the user, it is set sequentially by Discord.""" + return self._underlying.id + + @property + def custom_id(self) -> str: + """The custom id that gets received during an interaction.""" + return self._underlying.custom_id + + @custom_id.setter + def custom_id(self, value: str): + if not isinstance(value, str): + raise TypeError( + f"custom_id must be None or str not {value.__class__.__name__}" + ) + self._underlying.custom_id = value + + @property + def min_values(self) -> int | None: + """The minimum number of options that must be selected.""" + return self._underlying.min_values + + @min_values.setter + def min_values(self, value: int | None): + if value and not isinstance(value, int): + raise TypeError(f"min_values must be None or int not {value.__class__.__name__}") # type: ignore + if value and (value < 0 or value > 10): + raise ValueError("min_values must be between 0 and 10") + self._underlying.min_values = value + + @property + def max_values(self) -> int | None: + """The maximum number of options that can be selected.""" + return self._underlying.max_values + + @max_values.setter + def max_values(self, value: int | None): + if value and not isinstance(value, int): + raise TypeError(f"max_values must be None or int not {value.__class__.__name__}") # type: ignore + if value and (value < 1 or value > 10): + raise ValueError("max_values must be between 1 and 10") + self._underlying.max_values = value + + @property + def required(self) -> bool: + """Whether an option selection is required or not. Defaults to ``True``""" + return self._underlying.required + + @required.setter + def required(self, value: bool): + if not isinstance(value, bool): + raise TypeError(f"required must be bool, not {value.__class__.__name__}") # type: ignore + self._underlying.required = bool(value) + + @property + def values(self) -> str | None: + """The values selected by the user.""" + return self._selected_values + + def to_component_dict(self) -> CheckboxGroupComponentPayload: + return self._underlying.to_dict() + + def refresh_state(self, data) -> None: + self._selected_values = data.get("values", []) + + def refresh_from_modal( + self, interaction: Interaction, data: CheckboxGroupComponentPayload + ) -> None: + return self.refresh_state(data) diff --git a/discord/ui/file_upload.py b/discord/ui/file_upload.py index 7f311bc6e3..16b5f84f3d 100644 --- a/discord/ui/file_upload.py +++ b/discord/ui/file_upload.py @@ -57,7 +57,7 @@ def __init__( if min_values and (min_values < 0 or min_values > 10): raise ValueError("min_values must be between 0 and 10") if max_values and (max_values < 1 or max_values > 10): - raise ValueError("max_length must be between 1 and 10") + raise ValueError("max_values must be between 1 and 10") if custom_id is not None and not isinstance(custom_id, str): raise TypeError( f"expected custom_id to be str, not {custom_id.__class__.__name__}" @@ -106,7 +106,7 @@ def custom_id(self, value: str): @property def min_values(self) -> int | None: - """The minimum number of files that must be uploaded. Defaults to 0.""" + """The minimum number of files that must be uploaded.""" return self._underlying.min_values @min_values.setter From 76c6b69f0ef9d5c012799e4343f2c1db23032d5a Mon Sep 17 00:00:00 2001 From: Nelo <41271523+NeloBlivion@users.noreply.github.com> Date: Tue, 20 Jan 2026 23:48:40 -0500 Subject: [PATCH 09/52] , --- discord/ui/checkbox_group.py | 2 +- discord/ui/radio_group.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/discord/ui/checkbox_group.py b/discord/ui/checkbox_group.py index d6e478fc6d..b22df62dce 100644 --- a/discord/ui/checkbox_group.py +++ b/discord/ui/checkbox_group.py @@ -50,7 +50,7 @@ class CheckboxGroup(ModalItem): def __init__( self, *, - custom_id: str | None = None + custom_id: str | None = None, options: list[CheckboxGroupOption] | None = None, min_values: int | None = None, max_values: int | None = None, diff --git a/discord/ui/radio_group.py b/discord/ui/radio_group.py index d34421e48a..3ae3b5bbcc 100644 --- a/discord/ui/radio_group.py +++ b/discord/ui/radio_group.py @@ -42,7 +42,7 @@ class RadioGroup(ModalItem): def __init__( self, *, - custom_id: str | None = None + custom_id: str | None = None, options: list[RadioGroupOption] | None = None, required: bool = True, id: int | None = None, From 466eea298e331d7d3acfa171b9ee8dfcce8e8ad4 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 21 Jan 2026 04:48:45 +0000 Subject: [PATCH 10/52] style(pre-commit): auto fixes from pre-commit.com hooks --- discord/ui/checkbox_group.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/discord/ui/checkbox_group.py b/discord/ui/checkbox_group.py index b22df62dce..e98c5dd099 100644 --- a/discord/ui/checkbox_group.py +++ b/discord/ui/checkbox_group.py @@ -12,7 +12,9 @@ if TYPE_CHECKING: from ..interactions import Interaction - from ..types.components import CheckboxGroupComponent as CheckboxGroupComponentPayload + from ..types.components import ( + CheckboxGroupComponent as CheckboxGroupComponentPayload, + ) class CheckboxGroup(ModalItem): From 3609b35bfc05ba5f41e67af14bc244f1d7c6b7dd Mon Sep 17 00:00:00 2001 From: Nelo <41271523+NeloBlivion@users.noreply.github.com> Date: Tue, 20 Jan 2026 23:54:55 -0500 Subject: [PATCH 11/52] ui.Checkbox --- discord/ui/checkbox.py | 117 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 117 insertions(+) create mode 100644 discord/ui/checkbox.py diff --git a/discord/ui/checkbox.py b/discord/ui/checkbox.py new file mode 100644 index 0000000000..8a07a5f7ac --- /dev/null +++ b/discord/ui/checkbox.py @@ -0,0 +1,117 @@ +from __future__ import annotations + +import os +from typing import TYPE_CHECKING + +from ..components import Checkbox as CheckboxComponent +from ..enums import ComponentType +from .item import ModalItem + +__all__ = ("Checkbox",) + +if TYPE_CHECKING: + from ..interactions import Interaction + from ..types.components import ( + CheckboxComponent as CheckboxComponentPayload, + ) + + +class Checkbox(ModalItem): + """Represents a UI Checkbox component. + + .. versionadded:: 2.7.1 + + Parameters + ---------- + custom_id: Optional[:class:`str`] + The ID of the checkbox that gets received during an interaction. + default: Optional[:class:`bool`] + Whether this checkbox is selected by default or not. + id: Optional[:class:`int`] + The checkbox's ID. + """ + + __item_repr_attributes__: tuple[str, ...] = ( + "default", + "custom_id", + "id", + ) + + def __init__( + self, + *, + custom_id: str | None = None, + default: bool = False, + id: int | None = None, + ): + super().__init__() + if custom_id is not None and not isinstance(custom_id, str): + raise TypeError( + f"expected custom_id to be str, not {custom_id.__class__.__name__}" + ) + if not isinstance(default, bool): + raise TypeError(f"default must be bool, not {default.__class__.__name__}") # type: ignore + custom_id = os.urandom(16).hex() if custom_id is None else custom_id + + self._underlying: CheckboxComponent = CheckboxComponent._raw_construct( + type=ComponentType.checkbox, + custom_id=custom_id, + default=default, + id=id, + ) + self._value: bool | None = None + + def __repr__(self) -> str: + attrs = " ".join( + f"{key}={getattr(self, key)!r}" for key in self.__item_repr_attributes__ + ) + return f"<{self.__class__.__name__} {attrs}>" + + @property + def type(self) -> ComponentType: + return self._underlying.type + + @property + def id(self) -> int | None: + """The ID of this component. If not provided by the user, it is set sequentially by Discord.""" + return self._underlying.id + + @property + def custom_id(self) -> str: + """The custom id that gets received during an interaction.""" + return self._underlying.custom_id + + @custom_id.setter + def custom_id(self, value: str): + if not isinstance(value, str): + raise TypeError( + f"custom_id must be None or str not {value.__class__.__name__}" + ) + self._underlying.custom_id = value + + @property + def default(self) -> bool: + """Whether this checkbox is selected by default or not. Defaults to ``False``""" + return self._underlying.default + + @default.setter + def default(self, value: bool): + if not isinstance(value, bool): + raise TypeError(f"default must be bool, not {value.__class__.__name__}") # type: ignore + self._underlying.default = bool(value) + + @property + def value(self) -> str | None: + """Whether this checkbox was selected or not by the user.""" + return self._value + + def to_component_dict(self) -> CheckboxComponentPayload: + return self._underlying.to_dict() + + def refresh_state(self, data) -> None: + self._value = data.get("value", None) + + def refresh_from_modal( + self, interaction: Interaction, data: CheckboxComponentPayload + ) -> None: + return self.refresh_state(data) From 4f1e35141b94033781807af84ce76883ae35f289 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 21 Jan 2026 04:55:23 +0000 Subject: [PATCH 12/52] style(pre-commit): auto fixes from pre-commit.com hooks --- discord/ui/checkbox.py | 4 +--- discord/ui/checkbox_group.py | 18 ++++++++++-------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/discord/ui/checkbox.py b/discord/ui/checkbox.py index 8a07a5f7ac..22212b8cdd 100644 --- a/discord/ui/checkbox.py +++ b/discord/ui/checkbox.py @@ -11,9 +11,7 @@ if TYPE_CHECKING: from ..interactions import Interaction - from ..types.components import ( - CheckboxComponent as CheckboxComponentPayload, - ) + from ..types.components import CheckboxComponent as CheckboxComponentPayload class Checkbox(ModalItem): diff --git a/discord/ui/checkbox_group.py b/discord/ui/checkbox_group.py index e98c5dd099..d75b1e465c 100644 --- a/discord/ui/checkbox_group.py +++ b/discord/ui/checkbox_group.py @@ -72,14 +72,16 @@ def __init__( raise TypeError(f"required must be bool not {required.__class__.__name__}") # type: ignore custom_id = os.urandom(16).hex() if custom_id is None else custom_id - self._underlying: CheckboxGroupComponent = CheckboxGroupComponent._raw_construct( - type=ComponentType.checkbox_group, - custom_id=custom_id, - options=options or [], - min_values=min_values, - max_values=max_values, - required=required, - id=id, + self._underlying: CheckboxGroupComponent = ( + CheckboxGroupComponent._raw_construct( + type=ComponentType.checkbox_group, + custom_id=custom_id, + options=options or [], + min_values=min_values, + max_values=max_values, + required=required, + id=id, + ) ) self._selected_values: list[str] = [] From 12b0ca012167a8864ce82c31ed0f63f068b2d64b Mon Sep 17 00:00:00 2001 From: Nelo <41271523+NeloBlivion@users.noreply.github.com> Date: Wed, 21 Jan 2026 00:06:46 -0500 Subject: [PATCH 13/52] docs and label set methods --- CHANGELOG.md | 2 + discord/types/components.py | 2 +- discord/ui/item.py | 3 + discord/ui/label.py | 116 +++++++++++++++++++++++++++++++++++- discord/ui/modal.py | 1 - docs/api/models.rst | 18 ++++++ docs/api/ui_kit.rst | 18 ++++++ 7 files changed, 157 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1e8a93b8dc..9d13c1ec04 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,8 @@ These changes are available on the `master` branch, but have not yet been releas - Added `.extension` attribute to emojis to get their file extension. ([#3055](https://github.com/Pycord-Development/pycord/pull/3055)) +- Added `RadioGroup`, `CheckboxGroup`, & `Checkbox` for modals. + ([#3073](https://github.com/Pycord-Development/pycord/pull/3073)) ### Changed diff --git a/discord/types/components.py b/discord/types/components.py index c2973788a5..4ff1ec828e 100644 --- a/discord/types/components.py +++ b/discord/types/components.py @@ -165,7 +165,7 @@ class LabelComponent(BaseComponent): type: Literal[18] label: str description: NotRequired[str] - component: SelectMenu | InputText | FileUploadComponent + component: SelectMenu | InputText | FileUploadComponent | RadioGroupComponent | CheckboxComponent | CheckboxGroupComponent class FileUploadComponent(BaseComponent): diff --git a/discord/ui/item.py b/discord/ui/item.py index 6f21c7684c..298410dfb9 100644 --- a/discord/ui/item.py +++ b/discord/ui/item.py @@ -253,6 +253,9 @@ class ModalItem(Item[M]): - :class:`discord.ui.InputText` - :class:`discord.ui.Select` - :class:`discord.ui.FileUpload` + - :class:`discord.ui.RadioGroup` + - :class:`discord.ui.CheckboxGroup` + - :class:`discord.ui.Checkbox` .. versionadded:: 2.7 """ diff --git a/discord/ui/label.py b/discord/ui/label.py index 1fba7a0b7a..bbb1cf9b1e 100644 --- a/discord/ui/label.py +++ b/discord/ui/label.py @@ -28,7 +28,7 @@ from typing import TYPE_CHECKING, Iterator, Literal, TypeVar, overload from ..components import Label as LabelComponent -from ..components import SelectDefaultValue, SelectOption, _component_factory +from ..components import SelectDefaultValue, SelectOption, _component_factory, RadioGroupOption, CheckboxGroupOption from ..enums import ButtonStyle, ChannelType, ComponentType, InputTextStyle from ..utils import find, get from .button import Button @@ -36,6 +36,9 @@ from .input_text import InputText from .item import ItemCallbackType, ModalItem from .select import Select +from .radio_group import RadioGroup +from .checkbox_group import CheckboxGroup +from .checkbox import Checkbox __all__ = ("Label",) @@ -61,6 +64,9 @@ class Label(ModalItem[M]): - :class:`discord.ui.Select` - :class:`discord.ui.InputText` - :class:`discord.ui.FileUpload` + - :class:`discord.ui.RadioGroup` + - :class:`discord.ui.CheckboxGroup` + - :class:`discord.ui.Checkbox` .. versionadded:: 2.7 @@ -369,6 +375,114 @@ def set_file_upload( return self.set_item(upload) + def set_radio_group( + self, + *, + custom_id: str | None = None, + options: list[RadioGroupOption] | None = None, + required: bool | None = True, + id: int | None = None, + ) -> Self: + """Set this label's item to a radio group. + + To set a pre-existing :class:`RadioGroup`, use the + :meth:`set_item` method, instead. + + Parameters + ---------- + custom_id: Optional[:class:`str`] + The ID of the radio group that gets received during an interaction. + options: List[:class:`discord.RadioGroupOption`] + A list of options that can be selected from this group. + required: Optional[:class:`bool`] + Whether an option selection is required or not. Defaults to ``True``. + id: Optional[:class:`int`] + The radio group's ID. + """ + + radio = RadioGroup( + custom_id=custom_id, + option=options, + required=required, + id=id, + ) + + return self.set_item(radio) + + def set_checkbox_group( + self, + *, + custom_id: str | None = None, + options: list[CheckboxGroupOption] | None = None, + min_values: int | None = None, + max_values: int | None = None, + required: bool | None = True, + id: int | None = None, + ) -> Self: + """Set this label's item to a checkbox group. + + To set a pre-existing :class:`CheckboxGroup`, use the + :meth:`set_item` method, instead. + + Parameters + ---------- + custom_id: Optional[:class:`str`] + The ID of the checkbox group that gets received during an interaction. + options: List[:class:`discord.CheckboxGroupOption`] + A list of options that can be selected in this group. + min_values: Optional[:class:`int`] + The minimum number of options that must be selected. + Defaults to 0 and must be between 0 and 10, inclusive. + max_values: Optional[:class:`int`] + The maximum number of options that can be selected. + Must be between 1 and 10, inclusive. + required: Optional[:class:`bool`] + Whether an option selection is required or not. Defaults to ``True``. + id: Optional[:class:`int`] + The checkbox group's ID. + """ + + checkboxes = CheckboxGroup( + custom_id=custom_id, + option=options, + min_values=min_values, + max_values=max_values, + required=required, + id=id, + ) + + return self.set_item(checkboxes) + + def set_checkbox( + self, + *, + custom_id: str | None = None, + default: bool | None = False, + id: int | None = None, + ) -> Self: + """Set this label's item to a checkbox. + + To set a pre-existing :class:`Checkbox`, use the + :meth:`set_item` method, instead. + + Parameters + ---------- + custom_id: Optional[:class:`str`] + The ID of the checkbox that gets received during an interaction. + default: Optional[:class:`bool`] + Whether this checkbox is selected by default or not. + id: Optional[:class:`int`] + The checkbox's ID. + """ + + checkbox = Checkbox( + custom_id=custom_id, + default=default, + id=id, + ) + + return self.set_item(checkbox) + @property def label(self) -> str: """The label text. Must be 45 characters or fewer.""" diff --git a/discord/ui/modal.py b/discord/ui/modal.py index 8620cec363..f900f0e868 100644 --- a/discord/ui/modal.py +++ b/discord/ui/modal.py @@ -35,7 +35,6 @@ from ..enums import ComponentType from ..utils import find from .core import ItemInterface -from .file_upload import FileUpload from .input_text import InputText from .item import ModalItem from .label import Label diff --git a/docs/api/models.rst b/docs/api/models.rst index a82416871b..36100c53c4 100644 --- a/docs/api/models.rst +++ b/docs/api/models.rst @@ -476,6 +476,24 @@ UI Components :members: :inherited-members: +.. attributetable:: RadioGroup + +.. autoclass:: RadioGroup() + :members: + :inherited-members: + +.. attributetable:: CheckboxGroup + +.. autoclass:: CheckboxGroup() + :members: + :inherited-members: + +.. attributetable:: Checkbox + +.. autoclass:: Checkbox() + :members: + :inherited-members: + Emoji ----- diff --git a/docs/api/ui_kit.rst b/docs/api/ui_kit.rst index 5963c2c4c9..3f02c18c10 100644 --- a/docs/api/ui_kit.rst +++ b/docs/api/ui_kit.rst @@ -183,3 +183,21 @@ Objects .. autoclass:: discord.ui.FileUpload :members: :inherited-members: + +.. attributetable:: discord.ui.RadioGroup + +.. autoclass:: discord.ui.RadioGroup + :members: + :inherited-members: + +.. attributetable:: discord.ui.CheckboxGroup + +.. autoclass:: discord.ui.CheckboxGroup + :members: + :inherited-members: + +.. attributetable:: discord.ui.Checkbox + +.. autoclass:: discord.ui.Checkbox + :members: + :inherited-members: From 0babbcca98d27122d470ed0d603e1b67410b9274 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 21 Jan 2026 05:07:18 +0000 Subject: [PATCH 14/52] style(pre-commit): auto fixes from pre-commit.com hooks --- discord/types/components.py | 9 ++++++++- discord/ui/label.py | 14 ++++++++++---- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/discord/types/components.py b/discord/types/components.py index 4ff1ec828e..24470481e4 100644 --- a/discord/types/components.py +++ b/discord/types/components.py @@ -165,7 +165,14 @@ class LabelComponent(BaseComponent): type: Literal[18] label: str description: NotRequired[str] - component: SelectMenu | InputText | FileUploadComponent | RadioGroupComponent | CheckboxComponent | CheckboxGroupComponent + component: ( + SelectMenu + | InputText + | FileUploadComponent + | RadioGroupComponent + | CheckboxComponent + | CheckboxGroupComponent + ) class FileUploadComponent(BaseComponent): diff --git a/discord/ui/label.py b/discord/ui/label.py index bbb1cf9b1e..3899446c5c 100644 --- a/discord/ui/label.py +++ b/discord/ui/label.py @@ -27,18 +27,24 @@ from collections.abc import Sequence from typing import TYPE_CHECKING, Iterator, Literal, TypeVar, overload +from ..components import CheckboxGroupOption from ..components import Label as LabelComponent -from ..components import SelectDefaultValue, SelectOption, _component_factory, RadioGroupOption, CheckboxGroupOption +from ..components import ( + RadioGroupOption, + SelectDefaultValue, + SelectOption, + _component_factory, +) from ..enums import ButtonStyle, ChannelType, ComponentType, InputTextStyle from ..utils import find, get from .button import Button +from .checkbox import Checkbox +from .checkbox_group import CheckboxGroup from .file_upload import FileUpload from .input_text import InputText from .item import ItemCallbackType, ModalItem -from .select import Select from .radio_group import RadioGroup -from .checkbox_group import CheckboxGroup -from .checkbox import Checkbox +from .select import Select __all__ = ("Label",) From 183b591c468b4b7519f30501a48a84d69102306d Mon Sep 17 00:00:00 2001 From: Nelo <41271523+NeloBlivion@users.noreply.github.com> Date: Wed, 21 Jan 2026 00:13:43 -0500 Subject: [PATCH 15/52] _component_to_item --- discord/ui/view.py | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/discord/ui/view.py b/discord/ui/view.py index ec4c0cd8a7..11f3ada0ef 100644 --- a/discord/ui/view.py +++ b/discord/ui/view.py @@ -96,7 +96,7 @@ def _walk_all_components_v2(components: list[Component]) -> Iterator[Component]: yield item -def _component_to_item(component: Component) -> ViewItem[V]: +def _component_to_item(component: Component) -> ViewItem[V] | ModalItem | Item: if isinstance(component, ButtonComponent): from .button import Button @@ -142,7 +142,19 @@ def _component_to_item(component: Component) -> ViewItem[V]: from .label import Label return Label.from_component(component) - return ViewItem.from_component(component) + if isinstance(component, RadioGroupComponent): + from .radio_group import RadioGroup + + return RadioGroup.from_component(component) + if isinstance(component, CheckboxgroupComponent): + from .checkbox_group import CheckboxGroup + + return CheckboxGroup.from_component(component) + if isinstance(component, CheckboxComponent): + from .checkbox import Checkbox + + return Checkbox.from_component(component) + return Item.from_component(component) class _ViewWeights: From 5b7cf97d43dbfb008425471b3aac6365b5b8864e Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 21 Jan 2026 05:14:10 +0000 Subject: [PATCH 16/52] style(pre-commit): auto fixes from pre-commit.com hooks --- discord/ui/label.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/discord/ui/label.py b/discord/ui/label.py index 3899446c5c..48b257ad20 100644 --- a/discord/ui/label.py +++ b/discord/ui/label.py @@ -27,7 +27,9 @@ from collections.abc import Sequence from typing import TYPE_CHECKING, Iterator, Literal, TypeVar, overload -from ..components import CheckboxGroupOption +from ..components import ( + CheckboxGroupOption, +) from ..components import Label as LabelComponent from ..components import ( RadioGroupOption, From 34c7a8ed75ecc817899a0e60930ff2ee7520d598 Mon Sep 17 00:00:00 2001 From: Nelo <41271523+NeloBlivion@users.noreply.github.com> Date: Wed, 21 Jan 2026 00:30:14 -0500 Subject: [PATCH 17/52] options --- discord/ui/checkbox_group.py | 78 ++++++++++++++++++++++++++++++++++++ discord/ui/radio_group.py | 78 ++++++++++++++++++++++++++++++++++++ discord/ui/view.py | 5 ++- 3 files changed, 160 insertions(+), 1 deletion(-) diff --git a/discord/ui/checkbox_group.py b/discord/ui/checkbox_group.py index d75b1e465c..4aa3ac3a3e 100644 --- a/discord/ui/checkbox_group.py +++ b/discord/ui/checkbox_group.py @@ -155,6 +155,84 @@ def values(self) -> str | None: """The values selected by the user.""" return self._selected_values + @property + def options(self) -> list[CheckboxGroupOption]: + """A list of options that can be selected in this group.""" + return self._underlying.options + + @options.setter + def options(self, value: list[CheckboxGroupOption]): + if not isinstance(value, list): + raise TypeError("options must be a list of CheckboxGroupOption") + if len(value) > 10: + raise ValueError("you may only provide up to 10 options.") + if not all(isinstance(obj, CheckboxGroupOption) for obj in value): + raise TypeError("all list items must subclass CheckboxGroupOption") + + self._underlying.options = value + + def add_option( + self, + *, + label: str, + value: str = MISSING, + description: str | None = None, + default: bool = False, + ) -> Self: + """Adds an option to the checkbox group. + + To append a pre-existing :class:`discord.CheckboxGroupOption` use the + :meth:`append_option` method instead. + + Parameters + ---------- + label: :class:`str` + The label of the option. This is displayed to users. + Can only be up to 100 characters. + value: :class:`str` + The value of the option. This is not displayed to users. + If not given, defaults to the label. Can only be up to 100 characters. + description: Optional[:class:`str`] + An additional description of the option, if any. + Can only be up to 100 characters. + default: :class:`bool` + Whether this option is selected by default. + + Raises + ------ + ValueError + The number of options exceeds 10. + """ + + option = CheckboxGroupOption( + label=label, + value=value, + description=description, + default=default, + ) + + return self.append_option(option) + + def append_option(self, option: SelectOption) -> Self: + """Appends an option to the checkbox group. + + Parameters + ---------- + option: :class:`discord.CheckboxGroupOption` + The option to append to the checkbox group. + + Raises + ------ + ValueError + The number of options exceeds 10. + """ + + if len(self._underlying.options) >= 10: + raise ValueError("maximum number of options already provided") + + self._underlying.options.append(option) + return self + def to_component_dict(self) -> CheckboxGroupComponentPayload: return self._underlying.to_dict() diff --git a/discord/ui/radio_group.py b/discord/ui/radio_group.py index 3ae3b5bbcc..df2388a208 100644 --- a/discord/ui/radio_group.py +++ b/discord/ui/radio_group.py @@ -109,6 +109,84 @@ def value(self) -> str | None: """The value selected by the user.""" return self._selected_value + @property + def options(self) -> list[RadioGroupOption]: + """A list of options that can be selected in this group.""" + return self._underlying.options + + @options.setter + def options(self, value: list[RadioGroupOption]): + if not isinstance(value, list): + raise TypeError("options must be a list of RadioGroupOption") + if len(value) > 10: + raise ValueError("you may only provide up to 10 options.") + if not all(isinstance(obj, RadioGroupOption) for obj in value): + raise TypeError("all list items must subclass RadioGroupOption") + + self._underlying.options = value + + def add_option( + self, + *, + label: str, + value: str = MISSING, + description: str | None = None, + default: bool = False, + ) -> Self: + """Adds an option to the radio group. + + To append a pre-existing :class:`discord.RadioGroupOption` use the + :meth:`append_option` method instead. + + Parameters + ---------- + label: :class:`str` + The label of the option. This is displayed to users. + Can only be up to 100 characters. + value: :class:`str` + The value of the option. This is not displayed to users. + If not given, defaults to the label. Can only be up to 100 characters. + description: Optional[:class:`str`] + An additional description of the option, if any. + Can only be up to 100 characters. + default: :class:`bool` + Whether this option is selected by default. + + Raises + ------ + ValueError + The number of options exceeds 10. + """ + + option = RadioGroupOption( + label=label, + value=value, + description=description, + default=default, + ) + + return self.append_option(option) + + def append_option(self, option: SelectOption) -> Self: + """Appends an option to the radio group. + + Parameters + ---------- + option: :class:`discord.RadioGroupOption` + The option to append to the radio group. + + Raises + ------ + ValueError + The number of options exceeds 10. + """ + + if len(self._underlying.options) >= 10: + raise ValueError("maximum number of options already provided") + + self._underlying.options.append(option) + return self + def to_component_dict(self) -> RadioGroupComponentPayload: return self._underlying.to_dict() diff --git a/discord/ui/view.py b/discord/ui/view.py index 11f3ada0ef..560a47fcf9 100644 --- a/discord/ui/view.py +++ b/discord/ui/view.py @@ -54,11 +54,14 @@ from ..components import Separator as SeparatorComponent from ..components import TextDisplay as TextDisplayComponent from ..components import Thumbnail as ThumbnailComponent +from ..components import RadioGroup as RadioGroupComponent +from ..components import CheckboxGroup as CheckboxGroupComponent +from ..components import Checkbox as CheckboxComponent from ..components import _component_factory from ..enums import ChannelType from ..utils import find from .core import ItemInterface -from .item import ItemCallbackType, ViewItem +from .item import ItemCallbackType, ViewItem, ModalItem, Item __all__ = ( "BaseView", From fa8352ea8f36ab7692965f376007efca3a2f29f0 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 21 Jan 2026 05:31:02 +0000 Subject: [PATCH 18/52] style(pre-commit): auto fixes from pre-commit.com hooks --- discord/ui/view.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/discord/ui/view.py b/discord/ui/view.py index 560a47fcf9..fa35249a7f 100644 --- a/discord/ui/view.py +++ b/discord/ui/view.py @@ -44,24 +44,24 @@ from ..components import ActionRow as ActionRowComponent from ..components import Button as ButtonComponent +from ..components import Checkbox as CheckboxComponent +from ..components import CheckboxGroup as CheckboxGroupComponent from ..components import Component from ..components import Container as ContainerComponent from ..components import FileComponent from ..components import Label as LabelComponent from ..components import MediaGallery as MediaGalleryComponent +from ..components import RadioGroup as RadioGroupComponent from ..components import Section as SectionComponent from ..components import SelectMenu as SelectComponent from ..components import Separator as SeparatorComponent from ..components import TextDisplay as TextDisplayComponent from ..components import Thumbnail as ThumbnailComponent -from ..components import RadioGroup as RadioGroupComponent -from ..components import CheckboxGroup as CheckboxGroupComponent -from ..components import Checkbox as CheckboxComponent from ..components import _component_factory from ..enums import ChannelType from ..utils import find from .core import ItemInterface -from .item import ItemCallbackType, ViewItem, ModalItem, Item +from .item import Item, ItemCallbackType, ModalItem, ViewItem __all__ = ( "BaseView", From 285ab7455e2386d7e37cc28a5f2b0b24e22b13f7 Mon Sep 17 00:00:00 2001 From: Nelo <41271523+NeloBlivion@users.noreply.github.com> Date: Wed, 21 Jan 2026 00:33:34 -0500 Subject: [PATCH 19/52] imports --- discord/ui/checkbox_group.py | 4 +++- discord/ui/radio_group.py | 4 +++- discord/ui/view.py | 2 +- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/discord/ui/checkbox_group.py b/discord/ui/checkbox_group.py index 4aa3ac3a3e..35ab6f6cf1 100644 --- a/discord/ui/checkbox_group.py +++ b/discord/ui/checkbox_group.py @@ -2,10 +2,12 @@ import os from typing import TYPE_CHECKING +from typing_extensions import Self from ..components import CheckboxGroup as CheckboxGroupComponent from ..components import CheckboxGroupOption from ..enums import ComponentType +from ..utils import MISSING from .item import ModalItem __all__ = ("CheckboxGroup",) @@ -213,7 +215,7 @@ def add_option( return self.append_option(option) - def append_option(self, option: SelectOption) -> Self: + def append_option(self, option: RadioGroupOption) -> Self: """Appends an option to the checkbox group. Parameters diff --git a/discord/ui/radio_group.py b/discord/ui/radio_group.py index df2388a208..167eaad850 100644 --- a/discord/ui/radio_group.py +++ b/discord/ui/radio_group.py @@ -2,10 +2,12 @@ import os from typing import TYPE_CHECKING +from typing_extensions import Self from ..components import RadioGroup as RadioGroupComponent from ..components import RadioGroupOption from ..enums import ComponentType +from ..utils import MISSING from .item import ModalItem __all__ = ("RadioGroup",) @@ -167,7 +169,7 @@ def add_option( return self.append_option(option) - def append_option(self, option: SelectOption) -> Self: + def append_option(self, option: RadioGroupOption) -> Self: """Appends an option to the radio group. Parameters diff --git a/discord/ui/view.py b/discord/ui/view.py index fa35249a7f..5d359fc8dd 100644 --- a/discord/ui/view.py +++ b/discord/ui/view.py @@ -149,7 +149,7 @@ def _component_to_item(component: Component) -> ViewItem[V] | ModalItem | Item: from .radio_group import RadioGroup return RadioGroup.from_component(component) - if isinstance(component, CheckboxgroupComponent): + if isinstance(component, CheckboxGroupComponent): from .checkbox_group import CheckboxGroup return CheckboxGroup.from_component(component) From fe17a04a95aec6ba37fd1ed822b44137db87b0cc Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 21 Jan 2026 05:34:01 +0000 Subject: [PATCH 20/52] style(pre-commit): auto fixes from pre-commit.com hooks --- discord/ui/checkbox_group.py | 1 + discord/ui/radio_group.py | 1 + 2 files changed, 2 insertions(+) diff --git a/discord/ui/checkbox_group.py b/discord/ui/checkbox_group.py index 35ab6f6cf1..4d8873845b 100644 --- a/discord/ui/checkbox_group.py +++ b/discord/ui/checkbox_group.py @@ -2,6 +2,7 @@ import os from typing import TYPE_CHECKING + from typing_extensions import Self from ..components import CheckboxGroup as CheckboxGroupComponent diff --git a/discord/ui/radio_group.py b/discord/ui/radio_group.py index 167eaad850..7452fa7ad3 100644 --- a/discord/ui/radio_group.py +++ b/discord/ui/radio_group.py @@ -2,6 +2,7 @@ import os from typing import TYPE_CHECKING + from typing_extensions import Self from ..components import RadioGroup as RadioGroupComponent From ed0fc0673306aa79cb697adfd8cdb6480876d514 Mon Sep 17 00:00:00 2001 From: Nelo <41271523+NeloBlivion@users.noreply.github.com> Date: Wed, 21 Jan 2026 00:35:10 -0500 Subject: [PATCH 21/52] check --- discord/ui/checkbox_group.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/discord/ui/checkbox_group.py b/discord/ui/checkbox_group.py index 4d8873845b..925106e873 100644 --- a/discord/ui/checkbox_group.py +++ b/discord/ui/checkbox_group.py @@ -216,7 +216,7 @@ def add_option( return self.append_option(option) - def append_option(self, option: RadioGroupOption) -> Self: + def append_option(self, option: CheckboxGroupOption) -> Self: """Appends an option to the checkbox group. Parameters From 71dde7f46857153973facce2c1542067be06f38c Mon Sep 17 00:00:00 2001 From: Nelo <41271523+NeloBlivion@users.noreply.github.com> Date: Wed, 21 Jan 2026 00:43:26 -0500 Subject: [PATCH 22/52] all --- discord/ui/__init__.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/discord/ui/__init__.py b/discord/ui/__init__.py index e0260952dc..b4032b2140 100644 --- a/discord/ui/__init__.py +++ b/discord/ui/__init__.py @@ -25,3 +25,6 @@ from .text_display import * from .thumbnail import * from .view import * +from .radio_group import * +from .checkbox_group import * +from .checkbox import * From f4d3ec1913e9d38b7924213cd18c6b51d9a291e1 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 21 Jan 2026 05:43:54 +0000 Subject: [PATCH 23/52] style(pre-commit): auto fixes from pre-commit.com hooks --- discord/ui/__init__.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/discord/ui/__init__.py b/discord/ui/__init__.py index b4032b2140..e4044b00ab 100644 --- a/discord/ui/__init__.py +++ b/discord/ui/__init__.py @@ -10,6 +10,8 @@ from .action_row import * from .button import * +from .checkbox import * +from .checkbox_group import * from .container import * from .core import * from .file import * @@ -19,12 +21,10 @@ from .label import * from .media_gallery import * from .modal import * +from .radio_group import * from .section import * from .select import * from .separator import * from .text_display import * from .thumbnail import * from .view import * -from .radio_group import * -from .checkbox_group import * -from .checkbox import * From 44ae9341e22e6957ddf529d5b62f17ac5c354ab8 Mon Sep 17 00:00:00 2001 From: Nelo <41271523+NeloBlivion@users.noreply.github.com> Date: Wed, 21 Jan 2026 00:46:17 -0500 Subject: [PATCH 24/52] fix checkbox --- discord/components.py | 1 - 1 file changed, 1 deletion(-) diff --git a/discord/components.py b/discord/components.py index 7aa7fa605a..6de60462ed 100644 --- a/discord/components.py +++ b/discord/components.py @@ -1774,7 +1774,6 @@ def to_dict(self) -> CheckboxComponentPayload: payload = { "type": 23, "custom_id": self.custom_id, - "options": [opt.to_dict() for opt in self.options], } if self.id is not None: payload["id"] = self.id From 505329c2e1ba797a560eb1ede5d33f08bc969e89 Mon Sep 17 00:00:00 2001 From: Nelo <41271523+NeloBlivion@users.noreply.github.com> Date: Wed, 21 Jan 2026 00:53:25 -0500 Subject: [PATCH 25/52] option docs --- docs/api/data_classes.rst | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/docs/api/data_classes.rst b/docs/api/data_classes.rst index 87f86f1732..a28cb2a726 100644 --- a/docs/api/data_classes.rst +++ b/docs/api/data_classes.rst @@ -41,6 +41,16 @@ dynamic attributes in mind. .. autoclass:: UnfurledMediaItem :members: +.. attributetable:: RadioGroupOption + +.. autoclass:: RadioGroupOption + :members: + +.. attributetable:: CheckboxGroupOption + +.. autoclass:: CheckboxGroupOption + :members: + .. attributetable:: Intents .. autoclass:: Intents From 6eb19a58fd1566a097a153aa4e973526dd902464 Mon Sep 17 00:00:00 2001 From: Nelo <41271523+NeloBlivion@users.noreply.github.com> Date: Wed, 21 Jan 2026 00:54:49 -0500 Subject: [PATCH 26/52] Checkbox.value --- discord/ui/checkbox.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/discord/ui/checkbox.py b/discord/ui/checkbox.py index 22212b8cdd..3600c88ef6 100644 --- a/discord/ui/checkbox.py +++ b/discord/ui/checkbox.py @@ -99,7 +99,7 @@ def default(self, value: bool): self._underlying.default = bool(value) @property - def value(self) -> str | None: + def value(self) -> bool | None: """Whether this checkbox was selected or not by the user.""" return self._value From 186719dda7010f56c3f364413a530af32cd948a1 Mon Sep 17 00:00:00 2001 From: Nelo <41271523+NeloBlivion@users.noreply.github.com> Date: Sun, 8 Feb 2026 00:09:01 +0000 Subject: [PATCH 27/52] Update discord/ui/radio_group.py Co-authored-by: Paillat Signed-off-by: Nelo <41271523+NeloBlivion@users.noreply.github.com> --- discord/ui/radio_group.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/discord/ui/radio_group.py b/discord/ui/radio_group.py index 7452fa7ad3..6389a93b6a 100644 --- a/discord/ui/radio_group.py +++ b/discord/ui/radio_group.py @@ -23,7 +23,7 @@ class RadioGroup(ModalItem): .. versionadded:: 2.7.1 - Parameters + Attributes ---------- custom_id: Optional[:class:`str`] The ID of the radio group that gets received during an interaction. From 7b99d5431878da17ab3b00ed17512c0756e8af47 Mon Sep 17 00:00:00 2001 From: Nelo <41271523+NeloBlivion@users.noreply.github.com> Date: Sun, 8 Feb 2026 00:09:27 +0000 Subject: [PATCH 28/52] Update CHANGELOG.md Co-authored-by: JustaSqu1d <89910983+JustaSqu1d@users.noreply.github.com> Signed-off-by: Nelo <41271523+NeloBlivion@users.noreply.github.com> --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e3a271daa6..e2bcf4d37a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,7 +14,7 @@ These changes are available on the `master` branch, but have not yet been releas - Added `.extension` attribute to the `AppEmoji` and `GuildEmoji` classes. ([#3055](https://github.com/Pycord-Development/pycord/pull/3055)) -- Added `RadioGroup`, `CheckboxGroup`, & `Checkbox` for modals. +- Added `RadioGroup`, `CheckboxGroup`, and `Checkbox` for modals. ([#3073](https://github.com/Pycord-Development/pycord/pull/3073)) - Added the ability to compare instances of `PrimaryGuild`. ([#3077](https://github.com/Pycord-Development/pycord/pull/3077)) From c5a17e80008179f968b71fdc0711e815aad1b999 Mon Sep 17 00:00:00 2001 From: Nelo <41271523+NeloBlivion@users.noreply.github.com> Date: Sun, 8 Feb 2026 00:09:35 +0000 Subject: [PATCH 29/52] Update discord/components.py Co-authored-by: JustaSqu1d <89910983+JustaSqu1d@users.noreply.github.com> Signed-off-by: Nelo <41271523+NeloBlivion@users.noreply.github.com> --- discord/components.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/discord/components.py b/discord/components.py index d7da579122..a2280ef7d3 100644 --- a/discord/components.py +++ b/discord/components.py @@ -1597,7 +1597,7 @@ class CheckboxGroup(Component): max_values: Optional[:class:`int`] The maximum number of options that can be selected. required: Optional[:class:`bool`] - Whether the checkbox group requires a selection or not. Defaults to `True`. + Whether the checkbox group requires a selection or not. Defaults to ``True``. id: Optional[:class:`int`] The checkbox group's ID. """ From ad531999f64ff48aa10865fb5d9437497afb6b05 Mon Sep 17 00:00:00 2001 From: Nelo <41271523+NeloBlivion@users.noreply.github.com> Date: Sun, 8 Feb 2026 00:09:43 +0000 Subject: [PATCH 30/52] Update discord/components.py Co-authored-by: JustaSqu1d <89910983+JustaSqu1d@users.noreply.github.com> Signed-off-by: Nelo <41271523+NeloBlivion@users.noreply.github.com> --- discord/components.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/discord/components.py b/discord/components.py index a2280ef7d3..71e9884622 100644 --- a/discord/components.py +++ b/discord/components.py @@ -1449,7 +1449,7 @@ class RadioGroup(Component): options: List[:class:`RadioGroupOption`] A list of options that can be selected in this group. required: Optional[:class:`bool`] - Whether the radio group requires a selection or not. Defaults to `True`. + Whether the radio group requires a selection or not. Defaults to ``True``. id: Optional[:class:`int`] The radio group's ID. """ From 0588f1cef961969cad1961bdcdc713ab5b6637da Mon Sep 17 00:00:00 2001 From: Nelo <41271523+NeloBlivion@users.noreply.github.com> Date: Tue, 10 Feb 2026 19:04:53 -0500 Subject: [PATCH 31/52] convert to generate_underlying --- discord/ui/checkbox.py | 38 +++++++++++-------- discord/ui/checkbox_group.py | 72 ++++++++++++++++++++---------------- discord/ui/file_upload.py | 11 +----- discord/ui/input_text.py | 2 + discord/ui/item.py | 1 + discord/ui/modal.py | 2 +- discord/ui/radio_group.py | 48 ++++++++++++++---------- discord/ui/select.py | 2 +- 8 files changed, 97 insertions(+), 79 deletions(-) diff --git a/discord/ui/checkbox.py b/discord/ui/checkbox.py index 3600c88ef6..62c91b59ce 100644 --- a/discord/ui/checkbox.py +++ b/discord/ui/checkbox.py @@ -50,14 +50,13 @@ def __init__( if not isinstance(default, bool): raise TypeError(f"default must be bool, not {default.__class__.__name__}") # type: ignore custom_id = os.urandom(16).hex() if custom_id is None else custom_id + self._value: bool | None = None - self._underlying: CheckboxComponent = CheckboxComponent._raw_construct( - type=ComponentType.checkbox, + self._underlying: CheckboxComponent = self._generate_underlying( custom_id=custom_id, default=default, id=id, ) - self._value: bool | None = None def __repr__(self) -> str: attrs = " ".join( @@ -65,19 +64,24 @@ def __repr__(self) -> str: ) return f"<{self.__class__.__name__} {attrs}>" - @property - def type(self) -> ComponentType: - return self._underlying.type - - @property - def id(self) -> int | None: - """The ID of this component. If not provided by the user, it is set sequentially by Discord.""" - return self._underlying.id + def _generate_underlying( + self, + custom_id: str | None = None, + default: bool | None = None, + id: int | None = None, + ) -> CheckboxComponent: + super()._generate_underlying(CheckboxComponent) + return CheckboxComponent._raw_construct( + type=ComponentType.checkbox, + custom_id=custom_id or self.custom_id, + required=default if default is not None else self.default, + id=id or self.id, + ) @property def custom_id(self) -> str: """The custom id that gets received during an interaction.""" - return self._underlying.custom_id + return self.underlying.custom_id @custom_id.setter def custom_id(self, value: str): @@ -85,18 +89,20 @@ def custom_id(self, value: str): raise TypeError( f"custom_id must be None or str not {value.__class__.__name__}" ) - self._underlying.custom_id = value + if value and len(value) > 100: + raise ValueError("custom_id must be 100 characters or fewer") + self.underlying.custom_id = value @property def default(self) -> bool: """Whether this checkbox is selected by default or not. Defaults to ``False``""" - return self._underlying.default + return self.underlying.default @default.setter def default(self, value: bool): if not isinstance(value, bool): raise TypeError(f"default must be bool, not {value.__class__.__name__}") # type: ignore - self._underlying.default = bool(value) + self.underlying.default = bool(value) @property def value(self) -> bool | None: @@ -104,7 +110,7 @@ def value(self) -> bool | None: return self._value def to_component_dict(self) -> CheckboxComponentPayload: - return self._underlying.to_dict() + return self.underlying.to_dict() def refresh_state(self, data) -> None: self._value = data.get("value", None) diff --git a/discord/ui/checkbox_group.py b/discord/ui/checkbox_group.py index 925106e873..ef8825a623 100644 --- a/discord/ui/checkbox_group.py +++ b/discord/ui/checkbox_group.py @@ -74,19 +74,16 @@ def __init__( if not isinstance(required, bool): raise TypeError(f"required must be bool not {required.__class__.__name__}") # type: ignore custom_id = os.urandom(16).hex() if custom_id is None else custom_id + self._selected_values: list[str] = [] - self._underlying: CheckboxGroupComponent = ( - CheckboxGroupComponent._raw_construct( - type=ComponentType.checkbox_group, - custom_id=custom_id, - options=options or [], - min_values=min_values, - max_values=max_values, - required=required, - id=id, - ) + self._underlying: CheckboxGroupComponent = self._generate_underlying( + custom_id=custom_id, + options=options or [], + min_values=min_values, + max_values=max_values, + required=required, + id=id, ) - self._selected_values: list[str] = [] def __repr__(self) -> str: attrs = " ".join( @@ -94,19 +91,28 @@ def __repr__(self) -> str: ) return f"<{self.__class__.__name__} {attrs}>" - @property - def type(self) -> ComponentType: - return self._underlying.type - - @property - def id(self) -> int | None: - """The ID of this component. If not provided by the user, it is set sequentially by Discord.""" - return self._underlying.id + def _generate_underlying( + self, + custom_id: str | None = None, + options: list[CheckboxGroupOption] | None = None, + required: bool | None = None, + id: int | None = None, + ) -> CheckboxGroupComponent: + super()._generate_underlying(CheckboxGroupComponent) + return CheckboxGroupComponent._raw_construct( + type=ComponentType.checkbox_group, + custom_id=custom_id or self.custom_id, + options=options if options is not None else self.options, + min_values=min_values or self.min_values, + max_values=max_values or self.max_values, + required=required if required is not None else self.required, + id=id or self.id, + ) @property def custom_id(self) -> str: """The custom id that gets received during an interaction.""" - return self._underlying.custom_id + return self.underlying.custom_id @custom_id.setter def custom_id(self, value: str): @@ -114,12 +120,14 @@ def custom_id(self, value: str): raise TypeError( f"custom_id must be None or str not {value.__class__.__name__}" ) - self._underlying.custom_id = value + if value and len(value) > 100: + raise ValueError("custom_id must be 100 characters or fewer") + self.underlying.custom_id = value @property def min_values(self) -> int | None: """The minimum number of options that must be selected.""" - return self._underlying.min_values + return self.underlying.min_values @min_values.setter def min_values(self, value: int | None): @@ -127,12 +135,12 @@ def min_values(self, value: int | None): raise TypeError(f"min_values must be None or int not {value.__class__.__name__}") # type: ignore if value and (value < 0 or value > 10): raise ValueError("min_values must be between 0 and 10") - self._underlying.min_values = value + self.underlying.min_values = value @property def max_values(self) -> int | None: """The maximum number of options that can be selected.""" - return self._underlying.max_values + return self.underlying.max_values @max_values.setter def max_values(self, value: int | None): @@ -140,18 +148,18 @@ def max_values(self, value: int | None): raise TypeError(f"max_values must be None or int not {value.__class__.__name__}") # type: ignore if value and (value < 1 or value > 10): raise ValueError("max_values must be between 1 and 10") - self._underlying.max_values = value + self.underlying.max_values = value @property def required(self) -> bool: """Whether an option selection is required or not. Defaults to ``True``""" - return self._underlying.required + return self.underlying.required @required.setter def required(self, value: bool): if not isinstance(value, bool): raise TypeError(f"required must be bool, not {value.__class__.__name__}") # type: ignore - self._underlying.required = bool(value) + self.underlying.required = bool(value) @property def values(self) -> str | None: @@ -161,7 +169,7 @@ def values(self) -> str | None: @property def options(self) -> list[CheckboxGroupOption]: """A list of options that can be selected in this group.""" - return self._underlying.options + return self.underlying.options @options.setter def options(self, value: list[CheckboxGroupOption]): @@ -172,7 +180,7 @@ def options(self, value: list[CheckboxGroupOption]): if not all(isinstance(obj, CheckboxGroupOption) for obj in value): raise TypeError("all list items must subclass CheckboxGroupOption") - self._underlying.options = value + self.underlying.options = value def add_option( self, @@ -230,14 +238,14 @@ def append_option(self, option: CheckboxGroupOption) -> Self: The number of options exceeds 10. """ - if len(self._underlying.options) >= 10: + if len(self.underlying.options) >= 10: raise ValueError("maximum number of options already provided") - self._underlying.options.append(option) + self.underlying.options.append(option) return self def to_component_dict(self) -> CheckboxGroupComponentPayload: - return self._underlying.to_dict() + return self.underlying.to_dict() def refresh_state(self, data) -> None: self._selected_values = data.get("values", []) diff --git a/discord/ui/file_upload.py b/discord/ui/file_upload.py index e22a87cc94..ada3911dc3 100644 --- a/discord/ui/file_upload.py +++ b/discord/ui/file_upload.py @@ -99,15 +99,6 @@ def _generate_underlying( id=id or self.id, ) - @property - def type(self) -> ComponentType: - return self.underlying.type - - @property - def id(self) -> int | None: - """The ID of this component. If not provided by the user, it is set sequentially by Discord.""" - return self.underlying.id - @property def custom_id(self) -> str: """The custom id that gets received during an interaction.""" @@ -119,6 +110,8 @@ def custom_id(self, value: str): raise TypeError( f"custom_id must be None or str not {value.__class__.__name__}" ) + if value and len(value) > 100: + raise ValueError("custom_id must be 100 characters or fewer") self.underlying.custom_id = value @property diff --git a/discord/ui/input_text.py b/discord/ui/input_text.py index 47470b3927..087834f36a 100644 --- a/discord/ui/input_text.py +++ b/discord/ui/input_text.py @@ -188,6 +188,8 @@ def custom_id(self, value: str): raise TypeError( f"custom_id must be None or str not {value.__class__.__name__}" ) + if value and len(value) > 100: + raise ValueError("custom_id must be 100 characters or fewer") self.underlying.custom_id = value @property diff --git a/discord/ui/item.py b/discord/ui/item.py index 7873af5eac..ed4ea569d3 100644 --- a/discord/ui/item.py +++ b/discord/ui/item.py @@ -92,6 +92,7 @@ def underlying(self, value: Component) -> None: @property def type(self) -> ComponentType: + """The underlying component's type.""" if not self.underlying: raise NotImplementedError return self.underlying.type diff --git a/discord/ui/modal.py b/discord/ui/modal.py index 6bb3a21d6a..4f75929d18 100644 --- a/discord/ui/modal.py +++ b/discord/ui/modal.py @@ -157,7 +157,7 @@ def custom_id(self, value: str): raise TypeError( f"expected custom_id to be str, not {value.__class__.__name__}" ) - if len(value) > 100: + if value and len(value) > 100: raise ValueError("custom_id must be 100 characters or fewer") self._custom_id = value diff --git a/discord/ui/radio_group.py b/discord/ui/radio_group.py index 6389a93b6a..5b80759ce8 100644 --- a/discord/ui/radio_group.py +++ b/discord/ui/radio_group.py @@ -58,15 +58,14 @@ def __init__( if not isinstance(required, bool): raise TypeError(f"required must be bool not {required.__class__.__name__}") # type: ignore custom_id = os.urandom(16).hex() if custom_id is None else custom_id + self._selected_value: str | None = None - self._underlying: RadioGroupComponent = RadioGroupComponent._raw_construct( - type=ComponentType.radio_group, + self._underlying: RadioGroupComponent = self._generate_underlying( custom_id=custom_id, options=options or [], required=required, id=id, ) - self._selected_value: str | None = None def __repr__(self) -> str: attrs = " ".join( @@ -74,19 +73,26 @@ def __repr__(self) -> str: ) return f"<{self.__class__.__name__} {attrs}>" - @property - def type(self) -> ComponentType: - return self._underlying.type - - @property - def id(self) -> int | None: - """The ID of this component. If not provided by the user, it is set sequentially by Discord.""" - return self._underlying.id + def _generate_underlying( + self, + custom_id: str | None = None, + options: list[RadioGroupOption] | None = None, + required: bool | None = None, + id: int | None = None, + ) -> RadioGroupComponent: + super()._generate_underlying(RadioGroupComponent) + return RadioGroupComponent._raw_construct( + type=ComponentType.radio_group, + custom_id=custom_id or self.custom_id, + options=options if options is not None else self.options, + required=required if required is not None else self.required, + id=id or self.id, + ) @property def custom_id(self) -> str: """The custom id that gets received during an interaction.""" - return self._underlying.custom_id + return self.underlying.custom_id @custom_id.setter def custom_id(self, value: str): @@ -94,18 +100,20 @@ def custom_id(self, value: str): raise TypeError( f"custom_id must be None or str not {value.__class__.__name__}" ) - self._underlying.custom_id = value + if value and len(value) > 100: + raise ValueError("custom_id must be 100 characters or fewer") + self.underlying.custom_id = value @property def required(self) -> bool: """Whether an option selection is required or not. Defaults to ``True``""" - return self._underlying.required + return self.underlying.required @required.setter def required(self, value: bool): if not isinstance(value, bool): raise TypeError(f"required must be bool, not {value.__class__.__name__}") # type: ignore - self._underlying.required = bool(value) + self.underlying.required = bool(value) @property def value(self) -> str | None: @@ -115,7 +123,7 @@ def value(self) -> str | None: @property def options(self) -> list[RadioGroupOption]: """A list of options that can be selected in this group.""" - return self._underlying.options + return self.underlying.options @options.setter def options(self, value: list[RadioGroupOption]): @@ -126,7 +134,7 @@ def options(self, value: list[RadioGroupOption]): if not all(isinstance(obj, RadioGroupOption) for obj in value): raise TypeError("all list items must subclass RadioGroupOption") - self._underlying.options = value + self.underlying.options = value def add_option( self, @@ -184,14 +192,14 @@ def append_option(self, option: RadioGroupOption) -> Self: The number of options exceeds 10. """ - if len(self._underlying.options) >= 10: + if len(self.underlying.options) >= 10: raise ValueError("maximum number of options already provided") - self._underlying.options.append(option) + self.underlying.options.append(option) return self def to_component_dict(self) -> RadioGroupComponentPayload: - return self._underlying.to_dict() + return self.underlying.to_dict() def refresh_state(self, data) -> None: self._selected_value = data.get("value", None) diff --git a/discord/ui/select.py b/discord/ui/select.py index 7045de7d97..841ed6318b 100644 --- a/discord/ui/select.py +++ b/discord/ui/select.py @@ -377,7 +377,7 @@ def custom_id(self) -> str: def custom_id(self, value: str): if not isinstance(value, str): raise TypeError("custom_id must be None or str") - if len(value) > 100: + if value and len(value) > 100: raise ValueError("custom_id must be 100 characters or fewer") self.underlying.custom_id = value self._provided_custom_id = value is not None From c18ff0a49d07e84f63c0d22e67dc09b90656468e Mon Sep 17 00:00:00 2001 From: Nelo <41271523+NeloBlivion@users.noreply.github.com> Date: Tue, 10 Feb 2026 19:06:43 -0500 Subject: [PATCH 32/52] values --- discord/ui/checkbox_group.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/discord/ui/checkbox_group.py b/discord/ui/checkbox_group.py index ef8825a623..40fa428e80 100644 --- a/discord/ui/checkbox_group.py +++ b/discord/ui/checkbox_group.py @@ -95,6 +95,8 @@ def _generate_underlying( self, custom_id: str | None = None, options: list[CheckboxGroupOption] | None = None, + min_values: int | None = None, + max_values: int | None = None, required: bool | None = None, id: int | None = None, ) -> CheckboxGroupComponent: From 276fce03968fdbbe10c051758f1fe1e162210f64 Mon Sep 17 00:00:00 2001 From: Nelo <41271523+NeloBlivion@users.noreply.github.com> Date: Tue, 10 Feb 2026 19:11:35 -0500 Subject: [PATCH 33/52] copyright --- discord/ui/checkbox.py | 24 ++++++++++++++++++++++++ discord/ui/checkbox_group.py | 24 ++++++++++++++++++++++++ discord/ui/file_upload.py | 24 ++++++++++++++++++++++++ discord/ui/radio_group.py | 24 ++++++++++++++++++++++++ 4 files changed, 96 insertions(+) diff --git a/discord/ui/checkbox.py b/discord/ui/checkbox.py index 62c91b59ce..3977e2bcc8 100644 --- a/discord/ui/checkbox.py +++ b/discord/ui/checkbox.py @@ -1,3 +1,27 @@ +""" +The MIT License (MIT) + +Copyright (c) 2021-present Pycord Development + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. +""" + from __future__ import annotations import os diff --git a/discord/ui/checkbox_group.py b/discord/ui/checkbox_group.py index 40fa428e80..a3e76f2492 100644 --- a/discord/ui/checkbox_group.py +++ b/discord/ui/checkbox_group.py @@ -1,3 +1,27 @@ +""" +The MIT License (MIT) + +Copyright (c) 2021-present Pycord Development + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. +""" + from __future__ import annotations import os diff --git a/discord/ui/file_upload.py b/discord/ui/file_upload.py index ada3911dc3..2f2e6158b3 100644 --- a/discord/ui/file_upload.py +++ b/discord/ui/file_upload.py @@ -1,3 +1,27 @@ +""" +The MIT License (MIT) + +Copyright (c) 2021-present Pycord Development + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. +""" + from __future__ import annotations import os diff --git a/discord/ui/radio_group.py b/discord/ui/radio_group.py index 5b80759ce8..3d541b5ac1 100644 --- a/discord/ui/radio_group.py +++ b/discord/ui/radio_group.py @@ -1,3 +1,27 @@ +""" +The MIT License (MIT) + +Copyright (c) 2021-present Pycord Development + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. +""" + from __future__ import annotations import os From ea23381a5ef028f38de51e7ffcd1334ab31045cf Mon Sep 17 00:00:00 2001 From: Nelo <41271523+NeloBlivion@users.noreply.github.com> Date: Tue, 10 Feb 2026 19:14:04 -0500 Subject: [PATCH 34/52] RadioGroup.value clarification --- discord/ui/radio_group.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/discord/ui/radio_group.py b/discord/ui/radio_group.py index 3d541b5ac1..20edf4888c 100644 --- a/discord/ui/radio_group.py +++ b/discord/ui/radio_group.py @@ -141,7 +141,7 @@ def required(self, value: bool): @property def value(self) -> str | None: - """The value selected by the user.""" + """The value selected by the user. May return ``None`` if this radio group is optional or has not been sent yet.""" return self._selected_value @property From 2aa6f461ecf73a149ea3691499de40fd3393d2cb Mon Sep 17 00:00:00 2001 From: Nelo <41271523+NeloBlivion@users.noreply.github.com> Date: Tue, 10 Feb 2026 19:20:02 -0500 Subject: [PATCH 35/52] typing --- discord/ui/checkbox_group.py | 2 +- discord/ui/file_upload.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/discord/ui/checkbox_group.py b/discord/ui/checkbox_group.py index a3e76f2492..688859bb6f 100644 --- a/discord/ui/checkbox_group.py +++ b/discord/ui/checkbox_group.py @@ -188,7 +188,7 @@ def required(self, value: bool): self.underlying.required = bool(value) @property - def values(self) -> str | None: + def values(self) -> list[str]: """The values selected by the user.""" return self._selected_values diff --git a/discord/ui/file_upload.py b/discord/ui/file_upload.py index 2f2e6158b3..1bb10a01f6 100644 --- a/discord/ui/file_upload.py +++ b/discord/ui/file_upload.py @@ -89,7 +89,7 @@ def __init__( if not isinstance(required, bool): raise TypeError(f"required must be bool not {required.__class__.__name__}") # type: ignore custom_id = os.urandom(16).hex() if custom_id is None else custom_id - self._attachments: list[Attachment] | None = None + self._attachments: list[Attachment] = [] self._underlying: FileUploadComponent = self._generate_underlying( custom_id=custom_id, @@ -176,7 +176,7 @@ def required(self, value: bool): self.underlying.required = bool(value) @property - def values(self) -> list[Attachment] | None: + def values(self) -> list[Attachment]: """The files that were uploaded to the field.""" return self._attachments From 0d4169466d71ac4d3c2b4ebb3d5a694852cdeee8 Mon Sep 17 00:00:00 2001 From: Nelo <41271523+NeloBlivion@users.noreply.github.com> Date: Tue, 10 Feb 2026 19:27:02 -0500 Subject: [PATCH 36/52] enum docs --- docs/api/enums.rst | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/docs/api/enums.rst b/docs/api/enums.rst index 428e04e790..3607ceeda5 100644 --- a/docs/api/enums.rst +++ b/docs/api/enums.rst @@ -535,6 +535,15 @@ of :class:`enum.Enum`. .. attribute:: file_upload Represents a file upload component. + .. attribute:: radio_group + + Represents a radio group component. + .. attribute:: checkbox_group + + Represents a checkbox group component. + .. attribute:: checkbox + + Represents a checkbox component. .. class:: ButtonStyle From ebcd3bb2e7b063e5cc2b3212c9eb8264a621ad6f Mon Sep 17 00:00:00 2001 From: Nelo <41271523+NeloBlivion@users.noreply.github.com> Date: Tue, 10 Feb 2026 19:46:03 -0500 Subject: [PATCH 37/52] docs --- discord/ui/item.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/discord/ui/item.py b/discord/ui/item.py index ed4ea569d3..85132558bd 100644 --- a/discord/ui/item.py +++ b/discord/ui/item.py @@ -232,7 +232,8 @@ class ModalItem(Item[M]): - :class:`discord.ui.Label` - :class:`discord.ui.TextDisplay` - And :class:`discord.ui.Label` should be used in :class:`discord.ui.DesignerModal` to support the following items: + And :class:`discord.ui.Label` should be used in :class:`discord.ui.DesignerModal` to add the following items: + - :class:`discord.ui.InputText` - :class:`discord.ui.Select` - :class:`discord.ui.FileUpload` From 1c31317015822ef61f845b6d028377228238abdc Mon Sep 17 00:00:00 2001 From: Nelo <41271523+NeloBlivion@users.noreply.github.com> Date: Tue, 10 Feb 2026 20:21:46 -0500 Subject: [PATCH 38/52] fix group underlying --- discord/ui/checkbox_group.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/discord/ui/checkbox_group.py b/discord/ui/checkbox_group.py index 688859bb6f..d92fc874f5 100644 --- a/discord/ui/checkbox_group.py +++ b/discord/ui/checkbox_group.py @@ -129,8 +129,8 @@ def _generate_underlying( type=ComponentType.checkbox_group, custom_id=custom_id or self.custom_id, options=options if options is not None else self.options, - min_values=min_values or self.min_values, - max_values=max_values or self.max_values, + min_values=min_values if min_values is not None else self.min_values, + max_values=max_values if max_values is not None else self.max_values, required=required if required is not None else self.required, id=id or self.id, ) From 417a61ab7e546f80d05b465a532fb2f302efeb9b Mon Sep 17 00:00:00 2001 From: Nelo <41271523+NeloBlivion@users.noreply.github.com> Date: Tue, 10 Feb 2026 20:37:03 -0500 Subject: [PATCH 39/52] update types.components --- discord/types/components.py | 31 +++++++++++++++++++------------ 1 file changed, 19 insertions(+), 12 deletions(-) diff --git a/discord/types/components.py b/discord/types/components.py index 24470481e4..968e4b6f70 100644 --- a/discord/types/components.py +++ b/discord/types/components.py @@ -49,7 +49,7 @@ class BaseComponent(TypedDict): class ActionRow(BaseComponent): type: Literal[1] - components: list[ButtonComponent | InputText | SelectMenu] + components: list[AllowedActionRowComponents] class ButtonComponent(BaseComponent): @@ -108,8 +108,8 @@ class TextDisplayComponent(BaseComponent): class SectionComponent(BaseComponent): type: Literal[9] - components: list[TextDisplayComponent] - accessory: NotRequired[ThumbnailComponent | ButtonComponent] + components: list[AllowedSectionComponents] + accessory: NotRequired[AllowedSectionAccessories] class UnfurledMediaItem(TypedDict): @@ -165,14 +165,7 @@ class LabelComponent(BaseComponent): type: Literal[18] label: str description: NotRequired[str] - component: ( - SelectMenu - | InputText - | FileUploadComponent - | RadioGroupComponent - | CheckboxComponent - | CheckboxGroupComponent - ) + component: AllowedLabelComponents class FileUploadComponent(BaseComponent): @@ -220,9 +213,14 @@ class CheckboxComponent(BaseComponent): Component = Union[ - ActionRow, ButtonComponent, SelectMenu, InputText, FileUploadComponent + ActionRow, ButtonComponent, SelectMenu, InputText, TextDisplayComponent, SectionComponent, ThumbnailComponent, MediaGalleryComponent, FileComponent, SeparatorComponent, ContainerComponent, LabelComponent, FileUploadComponent, RadioGroupComponent, CheckboxGroupComponent, CheckboxComponent ] +AllowedActionRowComponents = Union[ButtonComponent, InputText, SelectMenu] + +AllowedSectionAccessories = Union[ThumbnailComponent, ButtonComponent] + +AllowedSectionComponents = Union[TextDisplayComponent] AllowedContainerComponents = Union[ ActionRow, @@ -232,3 +230,12 @@ class CheckboxComponent(BaseComponent): SeparatorComponent, SectionComponent, ] + +AllowedLabelComponents = Union[ + SelectMenu, + InputText, + FileUploadComponent, + RadioGroupComponent, + CheckboxComponent, + CheckboxGroupComponent, +] \ No newline at end of file From 817595d26f9dc740df9a3cf854092c0ad2f3e6fe Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 11 Feb 2026 01:37:32 +0000 Subject: [PATCH 40/52] style(pre-commit): auto fixes from pre-commit.com hooks --- discord/types/components.py | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/discord/types/components.py b/discord/types/components.py index 968e4b6f70..2f217adce7 100644 --- a/discord/types/components.py +++ b/discord/types/components.py @@ -213,7 +213,22 @@ class CheckboxComponent(BaseComponent): Component = Union[ - ActionRow, ButtonComponent, SelectMenu, InputText, TextDisplayComponent, SectionComponent, ThumbnailComponent, MediaGalleryComponent, FileComponent, SeparatorComponent, ContainerComponent, LabelComponent, FileUploadComponent, RadioGroupComponent, CheckboxGroupComponent, CheckboxComponent + ActionRow, + ButtonComponent, + SelectMenu, + InputText, + TextDisplayComponent, + SectionComponent, + ThumbnailComponent, + MediaGalleryComponent, + FileComponent, + SeparatorComponent, + ContainerComponent, + LabelComponent, + FileUploadComponent, + RadioGroupComponent, + CheckboxGroupComponent, + CheckboxComponent, ] AllowedActionRowComponents = Union[ButtonComponent, InputText, SelectMenu] @@ -238,4 +253,4 @@ class CheckboxComponent(BaseComponent): RadioGroupComponent, CheckboxComponent, CheckboxGroupComponent, -] \ No newline at end of file +] From e4fb423e10680fa01d650a26b96ae43f151ec873 Mon Sep 17 00:00:00 2001 From: Nelo <41271523+NeloBlivion@users.noreply.github.com> Date: Fri, 13 Feb 2026 16:03:35 -0500 Subject: [PATCH 41/52] change `values` to None if not received through interaction --- discord/ui/checkbox.py | 2 +- discord/ui/checkbox_group.py | 6 +++--- discord/ui/file_upload.py | 6 +++--- discord/ui/select.py | 4 ++-- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/discord/ui/checkbox.py b/discord/ui/checkbox.py index 3977e2bcc8..585d903969 100644 --- a/discord/ui/checkbox.py +++ b/discord/ui/checkbox.py @@ -130,7 +130,7 @@ def default(self, value: bool): @property def value(self) -> bool | None: - """Whether this checkbox was selected or not by the user.""" + """Whether this checkbox was selected or not by the user. This will be ``None`` if the checkbox has not been submitted via a modal yet.""" return self._value def to_component_dict(self) -> CheckboxComponentPayload: diff --git a/discord/ui/checkbox_group.py b/discord/ui/checkbox_group.py index d92fc874f5..13f29e0214 100644 --- a/discord/ui/checkbox_group.py +++ b/discord/ui/checkbox_group.py @@ -98,7 +98,7 @@ def __init__( if not isinstance(required, bool): raise TypeError(f"required must be bool not {required.__class__.__name__}") # type: ignore custom_id = os.urandom(16).hex() if custom_id is None else custom_id - self._selected_values: list[str] = [] + self._selected_values: list[str] | None = None self._underlying: CheckboxGroupComponent = self._generate_underlying( custom_id=custom_id, @@ -188,8 +188,8 @@ def required(self, value: bool): self.underlying.required = bool(value) @property - def values(self) -> list[str]: - """The values selected by the user.""" + def values(self) -> list[str] | None: + """The values selected by the user. This will be ``None`` if the checkbox group has not been submitted in a modal yet.""" return self._selected_values @property diff --git a/discord/ui/file_upload.py b/discord/ui/file_upload.py index 1bb10a01f6..c7268d2525 100644 --- a/discord/ui/file_upload.py +++ b/discord/ui/file_upload.py @@ -89,7 +89,7 @@ def __init__( if not isinstance(required, bool): raise TypeError(f"required must be bool not {required.__class__.__name__}") # type: ignore custom_id = os.urandom(16).hex() if custom_id is None else custom_id - self._attachments: list[Attachment] = [] + self._attachments: list[Attachment] | None = None self._underlying: FileUploadComponent = self._generate_underlying( custom_id=custom_id, @@ -176,8 +176,8 @@ def required(self, value: bool): self.underlying.required = bool(value) @property - def values(self) -> list[Attachment]: - """The files that were uploaded to the field.""" + def values(self) -> list[Attachment] | None: + """The files that were uploaded to the field. This will be ``None`` if the file upload has not been submitted via a modal yet.""" return self._attachments def to_component_dict(self) -> FileUploadComponentPayload: diff --git a/discord/ui/select.py b/discord/ui/select.py index 841ed6318b..0a31f60010 100644 --- a/discord/ui/select.py +++ b/discord/ui/select.py @@ -661,14 +661,14 @@ def append_option(self, option: SelectOption) -> Self: return self @property - def values(self) -> list[ST]: + def values(self) -> list[ST] | None: """List[:class:`str`] | List[:class:`discord.Member` | :class:`discord.User`]] | List[:class:`discord.Role`]] | List[:class:`discord.Member` | :class:`discord.User` | :class:`discord.Role`]] | List[:class:`discord.abc.GuildChannel`] | None: A list of values that have been selected by the user. This will be ``None`` if the select has not been interacted with yet. """ if self._interaction is None or self._interaction.data is None: # The select has not been interacted with yet - return [] + return None select_type = self.underlying.type if select_type is ComponentType.string_select: return self._selected_values # type: ignore # ST is str From d739cc9d865d023f39c149d84600c4abfd4d3e5f Mon Sep 17 00:00:00 2001 From: Nelo <41271523+NeloBlivion@users.noreply.github.com> Date: Sun, 22 Feb 2026 22:39:57 -0500 Subject: [PATCH 42/52] adjustments --- discord/components.py | 2 +- discord/ui/checkbox.py | 6 ------ discord/ui/checkbox_group.py | 6 ------ discord/ui/file_upload.py | 6 ------ discord/ui/input_text.py | 6 ------ discord/ui/radio_group.py | 6 ------ 6 files changed, 1 insertion(+), 31 deletions(-) diff --git a/discord/components.py b/discord/components.py index 71e9884622..fd0be52b80 100644 --- a/discord/components.py +++ b/discord/components.py @@ -1748,7 +1748,7 @@ class Checkbox(Component): ---------- custom_id: Optional[:class:`str`] The custom ID of the checkbox group that gets received during an interaction. - required: Optional[:class:`bool`] + default: Optional[:class:`bool`] Whether this checkbox is selected by default. id: Optional[:class:`int`] The checkbox group's ID. diff --git a/discord/ui/checkbox.py b/discord/ui/checkbox.py index 585d903969..a21e801747 100644 --- a/discord/ui/checkbox.py +++ b/discord/ui/checkbox.py @@ -82,12 +82,6 @@ def __init__( id=id, ) - def __repr__(self) -> str: - attrs = " ".join( - f"{key}={getattr(self, key)!r}" for key in self.__item_repr_attributes__ - ) - return f"<{self.__class__.__name__} {attrs}>" - def _generate_underlying( self, custom_id: str | None = None, diff --git a/discord/ui/checkbox_group.py b/discord/ui/checkbox_group.py index 13f29e0214..0fea657759 100644 --- a/discord/ui/checkbox_group.py +++ b/discord/ui/checkbox_group.py @@ -109,12 +109,6 @@ def __init__( id=id, ) - def __repr__(self) -> str: - attrs = " ".join( - f"{key}={getattr(self, key)!r}" for key in self.__item_repr_attributes__ - ) - return f"<{self.__class__.__name__} {attrs}>" - def _generate_underlying( self, custom_id: str | None = None, diff --git a/discord/ui/file_upload.py b/discord/ui/file_upload.py index c7268d2525..359111a112 100644 --- a/discord/ui/file_upload.py +++ b/discord/ui/file_upload.py @@ -99,12 +99,6 @@ def __init__( id=id, ) - def __repr__(self) -> str: - attrs = " ".join( - f"{key}={getattr(self, key)!r}" for key in self.__item_repr_attributes__ - ) - return f"<{self.__class__.__name__} {attrs}>" - def _generate_underlying( self, custom_id: str | None = None, diff --git a/discord/ui/input_text.py b/discord/ui/input_text.py index 087834f36a..fdc8f980b3 100644 --- a/discord/ui/input_text.py +++ b/discord/ui/input_text.py @@ -132,12 +132,6 @@ def __init__( id=id, ) - def __repr__(self) -> str: - attrs = " ".join( - f"{key}={getattr(self, key)!r}" for key in self.__item_repr_attributes__ - ) - return f"<{self.__class__.__name__} {attrs}>" - def _generate_underlying( self, style: InputTextStyle | None = None, diff --git a/discord/ui/radio_group.py b/discord/ui/radio_group.py index 20edf4888c..771c81ed99 100644 --- a/discord/ui/radio_group.py +++ b/discord/ui/radio_group.py @@ -91,12 +91,6 @@ def __init__( id=id, ) - def __repr__(self) -> str: - attrs = " ".join( - f"{key}={getattr(self, key)!r}" for key in self.__item_repr_attributes__ - ) - return f"<{self.__class__.__name__} {attrs}>" - def _generate_underlying( self, custom_id: str | None = None, From 3c08faacfe7fac8abd7167179ae970855cfe3301 Mon Sep 17 00:00:00 2001 From: Nelo <41271523+NeloBlivion@users.noreply.github.com> Date: Sun, 22 Feb 2026 22:58:17 -0500 Subject: [PATCH 43/52] more adjustments --- discord/components.py | 6 ++++-- discord/ui/checkbox_group.py | 7 +++++++ discord/ui/radio_group.py | 19 +++++++++++++++---- 3 files changed, 26 insertions(+), 6 deletions(-) diff --git a/discord/components.py b/discord/components.py index fd0be52b80..add9d8292c 100644 --- a/discord/components.py +++ b/discord/components.py @@ -1447,7 +1447,7 @@ class RadioGroup(Component): custom_id: Optional[:class:`str`] The custom ID of the radio group that gets received during an interaction. options: List[:class:`RadioGroupOption`] - A list of options that can be selected in this group. + A list of options that can be selected in this group, between 2 and 10. required: Optional[:class:`bool`] Whether the radio group requires a selection or not. Defaults to ``True``. id: Optional[:class:`int`] @@ -1509,7 +1509,7 @@ class RadioGroupOption: An additional description of the option, if any. Can only be up to 100 characters. default: :class:`bool` - Whether this option is selected by default. + Whether this option is selected by default. Only 1 option should be set to default within a :class:`discord.RadioGroup`. """ __slots__: tuple[str, ...] = ( @@ -1594,8 +1594,10 @@ class CheckboxGroup(Component): A list of options that can be selected in this group. min_values: Optional[:class:`int`] The minimum number of options that must be selected. + Defaults to 1 and must be between 0 and 25. If set to 0, :attr:`required` must be ``False``. max_values: Optional[:class:`int`] The maximum number of options that can be selected. + Must be between 1 and 10. required: Optional[:class:`bool`] Whether the checkbox group requires a selection or not. Defaults to ``True``. id: Optional[:class:`int`] diff --git a/discord/ui/checkbox_group.py b/discord/ui/checkbox_group.py index 0fea657759..38f4f40a36 100644 --- a/discord/ui/checkbox_group.py +++ b/discord/ui/checkbox_group.py @@ -264,6 +264,13 @@ def append_option(self, option: CheckboxGroupOption) -> Self: self.underlying.options.append(option) return self + def clear_options(self) -> Self: + """Remove all options from the checkbox group. + """ + + self.underlying.options.clear() + return self + def to_component_dict(self) -> CheckboxGroupComponentPayload: return self.underlying.to_dict() diff --git a/discord/ui/radio_group.py b/discord/ui/radio_group.py index 771c81ed99..4543aef788 100644 --- a/discord/ui/radio_group.py +++ b/discord/ui/radio_group.py @@ -52,7 +52,7 @@ class RadioGroup(ModalItem): custom_id: Optional[:class:`str`] The ID of the radio group that gets received during an interaction. options: List[:class:`discord.RadioGroupOption`] - A list of options that can be selected from this group. + A list of options that can be selected from this group. Must provide between 2 and 10 options. required: Optional[:class:`bool`] Whether an option selection is required or not. Defaults to ``True``. id: Optional[:class:`int`] @@ -86,10 +86,12 @@ def __init__( self._underlying: RadioGroupComponent = self._generate_underlying( custom_id=custom_id, - options=options or [], + options=[], required=required, id=id, ) + if options is not None: + self.options = options def _generate_underlying( self, @@ -147,10 +149,12 @@ def options(self) -> list[RadioGroupOption]: def options(self, value: list[RadioGroupOption]): if not isinstance(value, list): raise TypeError("options must be a list of RadioGroupOption") - if len(value) > 10: - raise ValueError("you may only provide up to 10 options.") + if not (2 >= len(value) >= 10) and len(value) != 0: + raise ValueError("you must provide between 2 and 10 options.") if not all(isinstance(obj, RadioGroupOption) for obj in value): raise TypeError("all list items must subclass RadioGroupOption") + if len([o for o in value if o.default]) > 1: + raise ValueError("only 1 option can be set as default.") self.underlying.options = value @@ -216,6 +220,13 @@ def append_option(self, option: RadioGroupOption) -> Self: self.underlying.options.append(option) return self + def clear_options(self) -> Self: + """Remove all options from the radio group. + """ + + self.underlying.options.clear() + return self + def to_component_dict(self) -> RadioGroupComponentPayload: return self.underlying.to_dict() From 6f2fd4c4abff3ba79879fc4ac6ef558d4fce6e1e Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 23 Feb 2026 03:58:43 +0000 Subject: [PATCH 44/52] style(pre-commit): auto fixes from pre-commit.com hooks --- discord/ui/checkbox_group.py | 3 +-- discord/ui/radio_group.py | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/discord/ui/checkbox_group.py b/discord/ui/checkbox_group.py index 38f4f40a36..56a4018209 100644 --- a/discord/ui/checkbox_group.py +++ b/discord/ui/checkbox_group.py @@ -265,8 +265,7 @@ def append_option(self, option: CheckboxGroupOption) -> Self: return self def clear_options(self) -> Self: - """Remove all options from the checkbox group. - """ + """Remove all options from the checkbox group.""" self.underlying.options.clear() return self diff --git a/discord/ui/radio_group.py b/discord/ui/radio_group.py index 4543aef788..f5ecf8208a 100644 --- a/discord/ui/radio_group.py +++ b/discord/ui/radio_group.py @@ -221,8 +221,7 @@ def append_option(self, option: RadioGroupOption) -> Self: return self def clear_options(self) -> Self: - """Remove all options from the radio group. - """ + """Remove all options from the radio group.""" self.underlying.options.clear() return self From e954037280ca290c86abf631f5c9e1f41ed53822 Mon Sep 17 00:00:00 2001 From: Nelo <41271523+NeloBlivion@users.noreply.github.com> Date: Sun, 22 Feb 2026 23:01:25 -0500 Subject: [PATCH 45/52] versions --- CHANGELOG.md | 4 ++-- discord/components.py | 10 +++++----- discord/ui/checkbox.py | 2 +- discord/ui/checkbox_group.py | 2 +- discord/ui/radio_group.py | 2 +- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1d6ff203b8..ea79282048 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,8 @@ These changes are available on the `master` branch, but have not yet been releas - Added `Member.colours` and `Member.colors` properties. ([#3063](https://github.com/Pycord-Development/pycord/pull/3063)) +- Added `RadioGroup`, `CheckboxGroup`, and `Checkbox` for modals. + ([#3073](https://github.com/Pycord-Development/pycord/pull/3073)) ### Changed @@ -37,8 +39,6 @@ These changes are available on the `master` branch, but have not yet been releas - Added `.extension` attribute to the `AppEmoji` and `GuildEmoji` classes. ([#3055](https://github.com/Pycord-Development/pycord/pull/3055)) -- Added `RadioGroup`, `CheckboxGroup`, and `Checkbox` for modals. - ([#3073](https://github.com/Pycord-Development/pycord/pull/3073)) - Added the ability to compare instances of `PrimaryGuild`. ([#3077](https://github.com/Pycord-Development/pycord/pull/3077)) diff --git a/discord/components.py b/discord/components.py index add9d8292c..227cee7156 100644 --- a/discord/components.py +++ b/discord/components.py @@ -1440,7 +1440,7 @@ class RadioGroup(Component): This class is not useable by end-users; see :class:`discord.ui.RadioGroup` instead. - .. versionadded:: 2.7.1 + .. versionadded:: 2.8 Attributes ---------- @@ -1494,7 +1494,7 @@ class RadioGroupOption: These can be created by users. - .. versionadded:: 2.7.1 + .. versionadded:: 2.8 Attributes ---------- @@ -1584,7 +1584,7 @@ class CheckboxGroup(Component): This class is not useable by end-users; see :class:`discord.ui.CheckboxGroup` instead. - .. versionadded:: 2.7.1 + .. versionadded:: 2.8 Attributes ---------- @@ -1654,7 +1654,7 @@ class CheckboxGroupOption: These can be created by users. - .. versionadded:: 2.7.1 + .. versionadded:: 2.8 Attributes ---------- @@ -1744,7 +1744,7 @@ class Checkbox(Component): This class is not useable by end-users; see :class:`discord.ui.Checkbox` instead. - .. versionadded:: 2.7.1 + .. versionadded:: 2.8 Attributes ---------- diff --git a/discord/ui/checkbox.py b/discord/ui/checkbox.py index a21e801747..aea74af0c3 100644 --- a/discord/ui/checkbox.py +++ b/discord/ui/checkbox.py @@ -41,7 +41,7 @@ class Checkbox(ModalItem): """Represents a UI Checkbox component. - .. versionadded:: 2.7.1 + .. versionadded:: 2.8 Parameters ---------- diff --git a/discord/ui/checkbox_group.py b/discord/ui/checkbox_group.py index 56a4018209..695ba4e371 100644 --- a/discord/ui/checkbox_group.py +++ b/discord/ui/checkbox_group.py @@ -47,7 +47,7 @@ class CheckboxGroup(ModalItem): """Represents a UI Checkbox Group component. - .. versionadded:: 2.7.1 + .. versionadded:: 2.8 Parameters ---------- diff --git a/discord/ui/radio_group.py b/discord/ui/radio_group.py index f5ecf8208a..986b9afd8f 100644 --- a/discord/ui/radio_group.py +++ b/discord/ui/radio_group.py @@ -45,7 +45,7 @@ class RadioGroup(ModalItem): """Represents a UI Radio Group component. - .. versionadded:: 2.7.1 + .. versionadded:: 2.8 Attributes ---------- From 73a356228495a1ee0aa2f1b2a3bfda170cf08ac2 Mon Sep 17 00:00:00 2001 From: Nelo <41271523+NeloBlivion@users.noreply.github.com> Date: Sun, 22 Feb 2026 23:05:42 -0500 Subject: [PATCH 46/52] option setter --- discord/ui/checkbox_group.py | 4 +++- discord/ui/radio_group.py | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/discord/ui/checkbox_group.py b/discord/ui/checkbox_group.py index 695ba4e371..6f12b9aa35 100644 --- a/discord/ui/checkbox_group.py +++ b/discord/ui/checkbox_group.py @@ -102,12 +102,14 @@ def __init__( self._underlying: CheckboxGroupComponent = self._generate_underlying( custom_id=custom_id, - options=options or [], + options=[], min_values=min_values, max_values=max_values, required=required, id=id, ) + if options: + self.options = option def _generate_underlying( self, diff --git a/discord/ui/radio_group.py b/discord/ui/radio_group.py index 986b9afd8f..bcc9a2692d 100644 --- a/discord/ui/radio_group.py +++ b/discord/ui/radio_group.py @@ -90,7 +90,7 @@ def __init__( required=required, id=id, ) - if options is not None: + if options: self.options = options def _generate_underlying( From 46e8bb0314beb22e4e84af9c47ce6e01073edb44 Mon Sep 17 00:00:00 2001 From: Nelo <41271523+NeloBlivion@users.noreply.github.com> Date: Sun, 22 Feb 2026 23:06:53 -0500 Subject: [PATCH 47/52] s --- discord/ui/checkbox_group.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/discord/ui/checkbox_group.py b/discord/ui/checkbox_group.py index 6f12b9aa35..b20468abbd 100644 --- a/discord/ui/checkbox_group.py +++ b/discord/ui/checkbox_group.py @@ -109,7 +109,7 @@ def __init__( id=id, ) if options: - self.options = option + self.options = options def _generate_underlying( self, From dff1280395e34c05ada68a633b605ae9b4ac02d0 Mon Sep 17 00:00:00 2001 From: Nelo <41271523+NeloBlivion@users.noreply.github.com> Date: Wed, 25 Feb 2026 20:15:20 -0500 Subject: [PATCH 48/52] correct option valiadation --- discord/ui/radio_group.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/discord/ui/radio_group.py b/discord/ui/radio_group.py index bcc9a2692d..20fea39903 100644 --- a/discord/ui/radio_group.py +++ b/discord/ui/radio_group.py @@ -149,7 +149,7 @@ def options(self) -> list[RadioGroupOption]: def options(self, value: list[RadioGroupOption]): if not isinstance(value, list): raise TypeError("options must be a list of RadioGroupOption") - if not (2 >= len(value) >= 10) and len(value) != 0: + if not (2 <= len(value) <= 10) and len(value) != 0: raise ValueError("you must provide between 2 and 10 options.") if not all(isinstance(obj, RadioGroupOption) for obj in value): raise TypeError("all list items must subclass RadioGroupOption") From 637a304584218009482078fb45e3e032928e685c Mon Sep 17 00:00:00 2001 From: Nelo <41271523+NeloBlivion@users.noreply.github.com> Date: Wed, 25 Feb 2026 20:18:02 -0500 Subject: [PATCH 49/52] remove type ignores --- discord/ui/checkbox.py | 4 ++-- discord/ui/checkbox_group.py | 8 ++++---- discord/ui/radio_group.py | 4 ++-- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/discord/ui/checkbox.py b/discord/ui/checkbox.py index aea74af0c3..dced3c574a 100644 --- a/discord/ui/checkbox.py +++ b/discord/ui/checkbox.py @@ -72,7 +72,7 @@ def __init__( f"expected custom_id to be str, not {custom_id.__class__.__name__}" ) if not isinstance(default, bool): - raise TypeError(f"default must be bool, not {default.__class__.__name__}") # type: ignore + raise TypeError(f"default must be bool, not {default.__class__.__name__}") custom_id = os.urandom(16).hex() if custom_id is None else custom_id self._value: bool | None = None @@ -119,7 +119,7 @@ def default(self) -> bool: @default.setter def default(self, value: bool): if not isinstance(value, bool): - raise TypeError(f"default must be bool, not {value.__class__.__name__}") # type: ignore + raise TypeError(f"default must be bool, not {value.__class__.__name__}") self.underlying.default = bool(value) @property diff --git a/discord/ui/checkbox_group.py b/discord/ui/checkbox_group.py index b20468abbd..6025f774c6 100644 --- a/discord/ui/checkbox_group.py +++ b/discord/ui/checkbox_group.py @@ -96,7 +96,7 @@ def __init__( f"expected custom_id to be str, not {custom_id.__class__.__name__}" ) if not isinstance(required, bool): - raise TypeError(f"required must be bool not {required.__class__.__name__}") # type: ignore + raise TypeError(f"required must be bool not {required.__class__.__name__}") custom_id = os.urandom(16).hex() if custom_id is None else custom_id self._selected_values: list[str] | None = None @@ -154,7 +154,7 @@ def min_values(self) -> int | None: @min_values.setter def min_values(self, value: int | None): if value and not isinstance(value, int): - raise TypeError(f"min_values must be None or int not {value.__class__.__name__}") # type: ignore + raise TypeError(f"min_values must be None or int not {value.__class__.__name__}") if value and (value < 0 or value > 10): raise ValueError("min_values must be between 0 and 10") self.underlying.min_values = value @@ -167,7 +167,7 @@ def max_values(self) -> int | None: @max_values.setter def max_values(self, value: int | None): if value and not isinstance(value, int): - raise TypeError(f"max_values must be None or int not {value.__class__.__name__}") # type: ignore + raise TypeError(f"max_values must be None or int not {value.__class__.__name__}") if value and (value < 1 or value > 10): raise ValueError("max_values must be between 1 and 10") self.underlying.max_values = value @@ -180,7 +180,7 @@ def required(self) -> bool: @required.setter def required(self, value: bool): if not isinstance(value, bool): - raise TypeError(f"required must be bool, not {value.__class__.__name__}") # type: ignore + raise TypeError(f"required must be bool, not {value.__class__.__name__}") self.underlying.required = bool(value) @property diff --git a/discord/ui/radio_group.py b/discord/ui/radio_group.py index 20fea39903..2bd7c99e05 100644 --- a/discord/ui/radio_group.py +++ b/discord/ui/radio_group.py @@ -80,7 +80,7 @@ def __init__( f"expected custom_id to be str, not {custom_id.__class__.__name__}" ) if not isinstance(required, bool): - raise TypeError(f"required must be bool not {required.__class__.__name__}") # type: ignore + raise TypeError(f"required must be bool not {required.__class__.__name__}") custom_id = os.urandom(16).hex() if custom_id is None else custom_id self._selected_value: str | None = None @@ -132,7 +132,7 @@ def required(self) -> bool: @required.setter def required(self, value: bool): if not isinstance(value, bool): - raise TypeError(f"required must be bool, not {value.__class__.__name__}") # type: ignore + raise TypeError(f"required must be bool, not {value.__class__.__name__}") self.underlying.required = bool(value) @property From a53a72e96cc53fb07587006003e208630b779d66 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 26 Feb 2026 01:18:29 +0000 Subject: [PATCH 50/52] style(pre-commit): auto fixes from pre-commit.com hooks --- discord/ui/checkbox_group.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/discord/ui/checkbox_group.py b/discord/ui/checkbox_group.py index 6025f774c6..0fbbe7d654 100644 --- a/discord/ui/checkbox_group.py +++ b/discord/ui/checkbox_group.py @@ -154,7 +154,9 @@ def min_values(self) -> int | None: @min_values.setter def min_values(self, value: int | None): if value and not isinstance(value, int): - raise TypeError(f"min_values must be None or int not {value.__class__.__name__}") + raise TypeError( + f"min_values must be None or int not {value.__class__.__name__}" + ) if value and (value < 0 or value > 10): raise ValueError("min_values must be between 0 and 10") self.underlying.min_values = value @@ -167,7 +169,9 @@ def max_values(self) -> int | None: @max_values.setter def max_values(self, value: int | None): if value and not isinstance(value, int): - raise TypeError(f"max_values must be None or int not {value.__class__.__name__}") + raise TypeError( + f"max_values must be None or int not {value.__class__.__name__}" + ) if value and (value < 1 or value > 10): raise ValueError("max_values must be between 1 and 10") self.underlying.max_values = value From ce7147f000d85a3026a451fa19cd8f83b2d064df Mon Sep 17 00:00:00 2001 From: Nelo <41271523+NeloBlivion@users.noreply.github.com> Date: Wed, 25 Feb 2026 20:25:27 -0500 Subject: [PATCH 51/52] adjust custom_id check --- discord/ui/checkbox.py | 4 ++-- discord/ui/checkbox_group.py | 4 ++-- discord/ui/file_upload.py | 4 ++-- discord/ui/input_text.py | 4 ++-- discord/ui/modal.py | 2 +- discord/ui/radio_group.py | 4 ++-- discord/ui/select.py | 2 +- 7 files changed, 12 insertions(+), 12 deletions(-) diff --git a/discord/ui/checkbox.py b/discord/ui/checkbox.py index dced3c574a..c682b30700 100644 --- a/discord/ui/checkbox.py +++ b/discord/ui/checkbox.py @@ -105,9 +105,9 @@ def custom_id(self) -> str: def custom_id(self, value: str): if not isinstance(value, str): raise TypeError( - f"custom_id must be None or str not {value.__class__.__name__}" + f"custom_id must be str not {value.__class__.__name__}" ) - if value and len(value) > 100: + if len(value) > 100: raise ValueError("custom_id must be 100 characters or fewer") self.underlying.custom_id = value diff --git a/discord/ui/checkbox_group.py b/discord/ui/checkbox_group.py index 0fbbe7d654..4c9383b379 100644 --- a/discord/ui/checkbox_group.py +++ b/discord/ui/checkbox_group.py @@ -140,9 +140,9 @@ def custom_id(self) -> str: def custom_id(self, value: str): if not isinstance(value, str): raise TypeError( - f"custom_id must be None or str not {value.__class__.__name__}" + f"custom_id must be str not {value.__class__.__name__}" ) - if value and len(value) > 100: + if len(value) > 100: raise ValueError("custom_id must be 100 characters or fewer") self.underlying.custom_id = value diff --git a/discord/ui/file_upload.py b/discord/ui/file_upload.py index 3f49c8a1b8..64a1405735 100644 --- a/discord/ui/file_upload.py +++ b/discord/ui/file_upload.py @@ -126,9 +126,9 @@ def custom_id(self) -> str: def custom_id(self, value: str): if not isinstance(value, str): raise TypeError( - f"custom_id must be None or str not {value.__class__.__name__}" + f"custom_id must be str not {value.__class__.__name__}" ) - if value and len(value) > 100: + if len(value) > 100: raise ValueError("custom_id must be 100 characters or fewer") self.underlying.custom_id = value diff --git a/discord/ui/input_text.py b/discord/ui/input_text.py index fbdbdc934a..42f53b449d 100644 --- a/discord/ui/input_text.py +++ b/discord/ui/input_text.py @@ -180,9 +180,9 @@ def custom_id(self) -> str: def custom_id(self, value: str): if not isinstance(value, str): raise TypeError( - f"custom_id must be None or str not {value.__class__.__name__}" + f"custom_id must be str not {value.__class__.__name__}" ) - if value and len(value) > 100: + if len(value) > 100: raise ValueError("custom_id must be 100 characters or fewer") self.underlying.custom_id = value diff --git a/discord/ui/modal.py b/discord/ui/modal.py index 4f75929d18..6bb3a21d6a 100644 --- a/discord/ui/modal.py +++ b/discord/ui/modal.py @@ -157,7 +157,7 @@ def custom_id(self, value: str): raise TypeError( f"expected custom_id to be str, not {value.__class__.__name__}" ) - if value and len(value) > 100: + if len(value) > 100: raise ValueError("custom_id must be 100 characters or fewer") self._custom_id = value diff --git a/discord/ui/radio_group.py b/discord/ui/radio_group.py index 2bd7c99e05..c7db275edc 100644 --- a/discord/ui/radio_group.py +++ b/discord/ui/radio_group.py @@ -118,9 +118,9 @@ def custom_id(self) -> str: def custom_id(self, value: str): if not isinstance(value, str): raise TypeError( - f"custom_id must be None or str not {value.__class__.__name__}" + f"custom_id must be str not {value.__class__.__name__}" ) - if value and len(value) > 100: + if len(value) > 100: raise ValueError("custom_id must be 100 characters or fewer") self.underlying.custom_id = value diff --git a/discord/ui/select.py b/discord/ui/select.py index 0a31f60010..d6f5a105ca 100644 --- a/discord/ui/select.py +++ b/discord/ui/select.py @@ -377,7 +377,7 @@ def custom_id(self) -> str: def custom_id(self, value: str): if not isinstance(value, str): raise TypeError("custom_id must be None or str") - if value and len(value) > 100: + if len(value) > 100: raise ValueError("custom_id must be 100 characters or fewer") self.underlying.custom_id = value self._provided_custom_id = value is not None From a9e95300b0510c739a91e3ce61ad0263d27261f8 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 26 Feb 2026 01:25:54 +0000 Subject: [PATCH 52/52] style(pre-commit): auto fixes from pre-commit.com hooks --- discord/ui/checkbox.py | 4 +--- discord/ui/checkbox_group.py | 4 +--- discord/ui/file_upload.py | 4 +--- discord/ui/input_text.py | 4 +--- discord/ui/radio_group.py | 4 +--- 5 files changed, 5 insertions(+), 15 deletions(-) diff --git a/discord/ui/checkbox.py b/discord/ui/checkbox.py index c682b30700..25d11ef644 100644 --- a/discord/ui/checkbox.py +++ b/discord/ui/checkbox.py @@ -104,9 +104,7 @@ def custom_id(self) -> str: @custom_id.setter def custom_id(self, value: str): if not isinstance(value, str): - raise TypeError( - f"custom_id must be str not {value.__class__.__name__}" - ) + raise TypeError(f"custom_id must be str not {value.__class__.__name__}") if len(value) > 100: raise ValueError("custom_id must be 100 characters or fewer") self.underlying.custom_id = value diff --git a/discord/ui/checkbox_group.py b/discord/ui/checkbox_group.py index 4c9383b379..ed155898ab 100644 --- a/discord/ui/checkbox_group.py +++ b/discord/ui/checkbox_group.py @@ -139,9 +139,7 @@ def custom_id(self) -> str: @custom_id.setter def custom_id(self, value: str): if not isinstance(value, str): - raise TypeError( - f"custom_id must be str not {value.__class__.__name__}" - ) + raise TypeError(f"custom_id must be str not {value.__class__.__name__}") if len(value) > 100: raise ValueError("custom_id must be 100 characters or fewer") self.underlying.custom_id = value diff --git a/discord/ui/file_upload.py b/discord/ui/file_upload.py index 64a1405735..784b8f6733 100644 --- a/discord/ui/file_upload.py +++ b/discord/ui/file_upload.py @@ -125,9 +125,7 @@ def custom_id(self) -> str: @custom_id.setter def custom_id(self, value: str): if not isinstance(value, str): - raise TypeError( - f"custom_id must be str not {value.__class__.__name__}" - ) + raise TypeError(f"custom_id must be str not {value.__class__.__name__}") if len(value) > 100: raise ValueError("custom_id must be 100 characters or fewer") self.underlying.custom_id = value diff --git a/discord/ui/input_text.py b/discord/ui/input_text.py index 42f53b449d..5b632e933c 100644 --- a/discord/ui/input_text.py +++ b/discord/ui/input_text.py @@ -179,9 +179,7 @@ def custom_id(self) -> str: @custom_id.setter def custom_id(self, value: str): if not isinstance(value, str): - raise TypeError( - f"custom_id must be str not {value.__class__.__name__}" - ) + raise TypeError(f"custom_id must be str not {value.__class__.__name__}") if len(value) > 100: raise ValueError("custom_id must be 100 characters or fewer") self.underlying.custom_id = value diff --git a/discord/ui/radio_group.py b/discord/ui/radio_group.py index c7db275edc..7a01501776 100644 --- a/discord/ui/radio_group.py +++ b/discord/ui/radio_group.py @@ -117,9 +117,7 @@ def custom_id(self) -> str: @custom_id.setter def custom_id(self, value: str): if not isinstance(value, str): - raise TypeError( - f"custom_id must be str not {value.__class__.__name__}" - ) + raise TypeError(f"custom_id must be str not {value.__class__.__name__}") if len(value) > 100: raise ValueError("custom_id must be 100 characters or fewer") self.underlying.custom_id = value