🎨 ADVANCED UI COMPONENTS CHEAT SHEET #
Ultra-Condensed Enlightenment Guide - Red 3.5+ & Discord.py 2.3+ Compliant
🚀 DISCORD.UI ARCHITECTURE MASTERY (Red 3.5+) #
Modern UI Component Hierarchy #
┌─────────────────────────────────────────────────────────────┐
│ BOT COMMAND LAYER │
│ • Hybrid Commands • App Commands • Context Menus │
├─────────────────────────────────────────────────────────────┤
│ VIEW CONTAINER LAYER │
│ • discord.ui.View • Red SimpleMenu • Custom Views │
├─────────────────────────────────────────────────────────────┤
│ COMPONENT LAYER │
│ • Buttons • Select Menus • Modals │
│ • Text Inputs • User Selects • Role Selects │
├─────────────────────────────────────────────────────────────┤
│ INTERACTION LAYER │
│ • interaction.response • Followups • Deferrals │
├─────────────────────────────────────────────────────────────┤
│ DISCORD API LAYER │
│ • Gateway Events • HTTP Responses • Rate Limits │
└─────────────────────────────────────────────────────────────┘
Core UI Framework (Red 3.5+ Compliant) #
# Modern Red 3.5+ UI Implementation
from redbot.core import commands, Config
from redbot.core.utils.views import ConfirmView, SimpleMenu
import discord
import asyncio
import logging
log = logging.getLogger("red.mycog.ui")
# GDPR Compliance for UI interactions
__red_end_user_data_statement__ = (
"This cog stores interaction data temporarily for UI state management. "
"Data is automatically cleaned up after view timeouts."
)
class ModernUICog(commands.Cog):
"""Red 3.5+ compliant UI cog with all modern patterns"""
def __init__(self, bot):
self.bot = bot
self.config = Config.get_conf(self, identifier=1234567890, force_registration=True)
self.active_views = {} # Track views for cleanup
# UI-specific defaults
self.config.register_guild(
ui_theme="default",
ui_timeout=300,
ephemeral_responses=True
)
async def cog_load(self):
"""Initialize UI system on cog load"""
log.info("UI system loading...")
await self.setup_persistent_views()
async def cog_unload(self):
"""Clean up all active views on unload"""
log.info("UI system unloading...")
for view in list(self.active_views.values()):
view.stop()
self.active_views.clear()
async def red_delete_data_for_user(self, kwargs):
"""GDPR compliance - remove user UI data"""
user_id = kwargs.get("user_id")
if user_id:
for view_id, view in self.active_views.items():
if hasattr(view, 'author') and view.author.id == user_id:
view.stop()
# REQUIRED: Async setup function (Red 3.5+)
async def setup(bot):
"""Load the cog - MUST BE ASYNC in Red 3.5+"""
cog = ModernUICog(bot)
await bot.add_cog(cog)
# Component Limits (Discord.py 2.3+)
COMPONENT_LIMITS = {
"buttons_per_row": 5,
"select_menus_per_row": 1,
"rows_per_view": 5,
"total_components": 25,
"select_options": 25,
"modal_text_inputs": 5,
"embed_fields": 25,
"embed_characters": 6000
}
- 🎯 Architecture: Layered UI system from commands to Discord API
- 🔧 Red Integration: Native Red utility classes and patterns
- 📊 State Management: Persistent view tracking and cleanup
- 🎨 Modern Components: Latest discord.py 2.3+ component types
- 🔄 Lifecycle: Proper view lifecycle with Red 3.5+ patterns
- 🛡️ GDPR Compliance: User data cleanup and privacy protection
- 📦 Resource Management: View tracking and automatic cleanup
- 🎯 Error Boundaries: Comprehensive error handling throughout
🔲 MODERN BUTTON SYSTEM MASTERY #
Advanced Button Implementation (Discord.py 2.3+) #
class ProfessionalButtonView(discord.ui.View):
"""Showcase all modern button patterns and styles"""
def __init__(self, author, *, timeout=180):
super().__init__(timeout=timeout)
self.author = author
self.result = None
@discord.ui.button(
label="Primary Action",
style=discord.ButtonStyle.primary,
emoji="🚀",
row=0
)
async def primary_button(self, interaction: discord.Interaction, button: discord.ui.Button):
"""Primary action button - main call-to-action"""
await interaction.response.send_message(
"🚀 Primary action executed!",
ephemeral=True
)
@discord.ui.button(
label="Secondary Info",
style=discord.ButtonStyle.secondary,
emoji="ℹ️",
row=0
)
async def secondary_button(self, interaction: discord.Interaction, button: discord.ui.Button):
"""Secondary button - informational actions"""
embed = discord.Embed(
title="ℹ️ Information",
description="This is additional information about the action.",
color=0x5865F2
)
await interaction.response.send_message(embed=embed, ephemeral=True)
@discord.ui.button(
label="Confirm",
style=discord.ButtonStyle.success,
emoji="✅",
row=1
)
async def success_button(self, interaction: discord.Interaction, button: discord.ui.Button):
"""Success/confirmation button"""
self.result = True
self.stop()
await interaction.response.edit_message(
content="✅ Action confirmed! View closed.",
view=None
)
@discord.ui.button(
label="Danger Zone",
style=discord.ButtonStyle.danger,
emoji="⚠️",
row=1
)
async def danger_button(self, interaction: discord.Interaction, button: discord.ui.Button):
"""Destructive action with double confirmation"""
confirm = ConfirmView(interaction.user, timeout=30)
await interaction.response.send_message(
"⚠️ DANGER: This action cannot be undone. Are you sure?",
view=confirm,
ephemeral=True
)
await confirm.wait()
if confirm.result:
await interaction.followup.send("💥 Destructive action completed!", ephemeral=True)
else:
await interaction.followup.send("❌ Action cancelled.", ephemeral=True)
@discord.ui.button(
label="🔗 Documentation",
style=discord.ButtonStyle.link,
url="https://docs.pycord.dev/en/stable/",
row=2
)
async def link_button(self, interaction: discord.Interaction, button: discord.ui.Button):
"""Link button - no callback needed, opens URL directly"""
pass # Link buttons don't trigger callbacks
@discord.ui.button(
label="Toggle",
style=discord.ButtonStyle.secondary,
emoji="🔄",
row=2
)
async def toggle_button(self, interaction: discord.Interaction, button: discord.ui.Button):
"""Toggle button with state management"""
if button.style == discord.ButtonStyle.secondary:
button.style = discord.ButtonStyle.success
button.label = "Enabled"
button.emoji = "✅"
message = "Feature enabled!"
else:
button.style = discord.ButtonStyle.secondary
button.label = "Toggle"
button.emoji = "🔄"
message = "Feature disabled!"
await interaction.response.edit_message(
content=f"🔄 {message}",
view=self
)
async def interaction_check(self, interaction: discord.Interaction) -> bool:
"""Enhanced security check with Red patterns"""
if interaction.user != self.author:
await interaction.response.send_message(
"❌ Only the command author can use these buttons.",
ephemeral=True
)
return False
return True
async def on_timeout(self):
"""Red-style timeout handling"""
try:
for child in self.children:
child.disabled = True
if hasattr(self, 'message') and self.message:
await self.message.edit(
content="⏰ This interaction has expired.",
view=self
)
except discord.HTTPException:
pass
# Dynamic Button Management
class DynamicButtonView(discord.ui.View):
"""Advanced dynamic button management with runtime modification"""
def __init__(self, author, max_buttons=20):
super().__init__(timeout=300)
self.author = author
self.max_buttons = min(max_buttons, 20) # Safety limit
self.button_count = 0
def add_dynamic_button(self, label, emoji=None, style=discord.ButtonStyle.secondary):
"""Add button at runtime with automatic row management"""
if self.button_count >= self.max_buttons:
return False
button = discord.ui.Button(
label=label,
emoji=emoji,
style=style,
custom_id=f"dynamic_{self.button_count}",
row=self.button_count // 5 # Auto-row calculation
)
async def button_callback(interaction):
await interaction.response.send_message(
f"You clicked: {label}",
ephemeral=True
)
button.callback = button_callback
self.add_item(button)
self.button_count += 1
return True
def remove_button_by_label(self, label):
"""Remove button by label and rebalance rows"""
for item in self.children.copy():
if isinstance(item, discord.ui.Button) and item.label == label:
self.remove_item(item)
self.button_count -= 1
self._rebalance_rows()
return True
return False
def _rebalance_rows(self):
"""Rebalance button rows after removal"""
buttons = [item for item in self.children if isinstance(item, discord.ui.Button)]
for i, button in enumerate(buttons):
button.row = i // 5
# Professional Navigation View
class NavigationView(discord.ui.View):
"""Red-style navigation with comprehensive controls"""
def __init__(self, pages, *, author, timeout=300):
super().__init__(timeout=timeout)
self.pages = pages
self.author = author
self.current_page = 0
self.max_pages = len(pages)
if self.max_pages > 1:
self._add_navigation_buttons()
self._update_button_states()
def _add_navigation_buttons(self):
"""Add all navigation buttons"""
# First page
self.first_btn = discord.ui.Button(emoji="⏮️", style=discord.ButtonStyle.secondary, row=0)
# Previous page
self.prev_btn = discord.ui.Button(emoji="◀️", style=discord.ButtonStyle.primary, row=0)
# Stop view
self.stop_btn = discord.ui.Button(emoji="🛑", style=discord.ButtonStyle.danger, row=0)
# Next page
self.next_btn = discord.ui.Button(emoji="▶️", style=discord.ButtonStyle.primary, row=0)
# Last page
self.last_btn = discord.ui.Button(emoji="⏭️", style=discord.ButtonStyle.secondary, row=0)
# Assign callbacks
self.first_btn.callback = self._first_page
self.prev_btn.callback = self._previous_page
self.stop_btn.callback = self._stop_view
self.next_btn.callback = self._next_page
self.last_btn.callback = self._last_page
# Add to view
self.add_item(self.first_btn)
self.add_item(self.prev_btn)
self.add_item(self.stop_btn)
self.add_item(self.next_btn)
self.add_item(self.last_btn)
async def _first_page(self, interaction: discord.Interaction):
"""Jump to first page"""
if self.current_page == 0:
return await interaction.response.defer()
self.current_page = 0
self._update_button_states()
await self._update_page(interaction)
async def _previous_page(self, interaction: discord.Interaction):
"""Go to previous page"""
if self.current_page == 0:
return await interaction.response.defer()
self.current_page -= 1
self._update_button_states()
await self._update_page(interaction)
async def _stop_view(self, interaction: discord.Interaction):
"""Stop the view"""
self.stop()
await interaction.response.edit_message(
content="🛑 Navigation stopped.",
view=None
)
async def _next_page(self, interaction: discord.Interaction):
"""Go to next page"""
if self.current_page >= self.max_pages - 1:
return await interaction.response.defer()
self.current_page += 1
self._update_button_states()
await self._update_page(interaction)
async def _last_page(self, interaction: discord.Interaction):
"""Jump to last page"""
if self.current_page >= self.max_pages - 1:
return await interaction.response.defer()
self.current_page = self.max_pages - 1
self._update_button_states()
await self._update_page(interaction)
def _update_button_states(self):
"""Update button enabled/disabled states"""
if self.max_pages <= 1:
return
# Disable first/previous if on first page
self.first_btn.disabled = self.prev_btn.disabled = (self.current_page == 0)
# Disable next/last if on last page
self.next_btn.disabled = self.last_btn.disabled = (self.current_page >= self.max_pages - 1)
async def _update_page(self, interaction):
"""Update page content"""
page_content = self.pages[self.current_page]
if isinstance(page_content, discord.Embed):
page_content.set_footer(text=f"Page {self.current_page + 1}/{self.max_pages}")
await interaction.response.edit_message(embed=page_content, view=self)
else:
content = f"{page_content}\n\n📄 Page {self.current_page + 1}/{self.max_pages}"
await interaction.response.edit_message(content=content, view=self)
- 🎯 Five Styles: Primary, Secondary, Success, Danger, Link buttons
- 🔧 Dynamic Management: Runtime button addition/removal with row balancing
- 📊 State Tracking: Toggle buttons with visual state changes
- 🎨 Navigation: Professional page navigation with all controls
- 🔄 Auto-Layout: Automatic row management for up to 25 buttons
- 🛡️ Security: Author-only interaction checks with ephemeral errors
- 📦 Timeout Handling: Graceful timeout with button disabling
- 🎯 Red Integration: Compatible with Red’s view utilities
📋 NEXT-GEN SELECT MENU MASTERY #
Modern Select Menu Types (Discord.py 2.3+) #
class ComprehensiveSelectView(discord.ui.View):
"""Showcase all modern select menu types"""
def __init__(self, author, *, timeout=300):
super().__init__(timeout=timeout)
self.author = author
@discord.ui.select(
placeholder="Choose options...",
options=[
discord.SelectOption(
label="Option 1",
value="opt1",
description="First option with description",
emoji="1️⃣"
),
discord.SelectOption(
label="Option 2",
value="opt2",
description="Second option with description",
emoji="2️⃣"
),
discord.SelectOption(
label="Option 3",
value="opt3",
description="Third option (pre-selected)",
emoji="3️⃣",
default=True # Pre-selected option
)
],
min_values=1,
max_values=2, # Allow multiple selections
row=0
)
async def string_select(self, interaction: discord.Interaction, select: discord.ui.Select):
"""Standard string select menu"""
selected = ", ".join(select.values)
await interaction.response.send_message(
f"📋 You selected: {selected}",
ephemeral=True
)
@discord.ui.user_select(
placeholder="Select users...",
min_values=1,
max_values=3,
row=1
)
async def user_select(self, interaction: discord.Interaction, select: discord.ui.UserSelect):
"""User select menu (Discord.py 2.3+)"""
users = [user.display_name for user in select.values]
user_list = ", ".join(users)
await interaction.response.send_message(
f"👥 Selected users: {user_list}",
ephemeral=True
)
@discord.ui.role_select(
placeholder="Select roles...",
min_values=1,
max_values=5,
row=2
)
async def role_select(self, interaction: discord.Interaction, select: discord.ui.RoleSelect):
"""Role select menu (Discord.py 2.3+)"""
roles = [role.name for role in select.values]
role_list = ", ".join(roles)
await interaction.response.send_message(
f"🏷️ Selected roles: {role_list}",
ephemeral=True
)
@discord.ui.channel_select(
placeholder="Select channels...",
channel_types=[
discord.ChannelType.text,
discord.ChannelType.voice,
discord.ChannelType.category
],
min_values=1,
max_values=3,
row=3
)
async def channel_select(self, interaction: discord.Interaction, select: discord.ui.ChannelSelect):
"""Channel select menu with type filtering (Discord.py 2.3+)"""
channels = [f"#{channel.name}" for channel in select.values]
channel_list = ", ".join(channels)
await interaction.response.send_message(
f"📺 Selected channels: {channel_list}",
ephemeral=True
)
@discord.ui.mentionable_select(
placeholder="Select users, roles, or both...",
min_values=1,
max_values=5,
row=4
)
async def mentionable_select(self, interaction: discord.Interaction, select: discord.ui.MentionableSelect):
"""Mentionable select (users + roles) (Discord.py 2.3+)"""
mentions = []
for item in select.values:
if isinstance(item, discord.Member):
mentions.append(f"👤 {item.display_name}")
elif isinstance(item, discord.Role):
mentions.append(f"🏷️ {item.name}")
mention_list = ", ".join(mentions)
await interaction.response.send_message(
f"🔖 Selected: {mention_list}",
ephemeral=True
)
# Smart Paginated Select Menu
class PaginatedSelectView(discord.ui.View):
"""Intelligent paginated select menu for large datasets"""
def __init__(self, items, *, author, per_page=25, timeout=300):
super().__init__(timeout=timeout)
self.items = items
self.author = author
self.per_page = per_page
self.current_page = 0
self.max_pages = (len(items) + per_page - 1) // per_page
self._build_current_page()
if self.max_pages > 1:
self._add_navigation()
def _build_current_page(self):
"""Build select menu for current page"""
# Remove existing select menu
for item in self.children.copy():
if isinstance(item, discord.ui.Select) and not isinstance(
item, (discord.ui.UserSelect, discord.ui.RoleSelect, discord.ui.ChannelSelect)
):
self.remove_item(item)
# Calculate page items
start_idx = self.current_page * self.per_page
end_idx = min(start_idx + self.per_page, len(self.items))
page_items = self.items[start_idx:end_idx]
# Create options
options = []
for i, item in enumerate(page_items):
if isinstance(item, dict):
options.append(discord.SelectOption(
label=item.get("label", f"Item {start_idx + i + 1}"),
value=item.get("value", str(start_idx + i)),
description=item.get("description"),
emoji=item.get("emoji")
))
else:
options.append(discord.SelectOption(
label=str(item)[:100], # Discord limit
value=str(start_idx + i)
))
# Create select menu
select_menu = discord.ui.Select(
placeholder=f"Page {self.current_page + 1}/{self.max_pages} - Choose an item...",
options=options,
row=0
)
async def select_callback(interaction):
selected_idx = int(select_menu.values[0])
selected_item = self.items[selected_idx]
await interaction.response.send_message(
f"✅ Selected: {selected_item}",
ephemeral=True
)
select_menu.callback = select_callback
self.add_item(select_menu)
def _add_navigation(self):
"""Add navigation buttons for multiple pages"""
@discord.ui.button(emoji="◀️", style=discord.ButtonStyle.secondary, row=1)
async def prev_page(interaction, button):
if self.current_page > 0:
self.current_page -= 1
self._build_current_page()
self._update_nav_states()
await interaction.response.edit_message(view=self)
else:
await interaction.response.defer()
@discord.ui.button(emoji="▶️", style=discord.ButtonStyle.secondary, row=1)
async def next_page(interaction, button):
if self.current_page < self.max_pages - 1:
self.current_page += 1
self._build_current_page()
self._update_nav_states()
await interaction.response.edit_message(view=self)
else:
await interaction.response.defer()
prev_page.callback = prev_page
next_page.callback = next_page
self.add_item(prev_page)
self.add_item(next_page)
self._update_nav_states()
def _update_nav_states(self):
"""Update navigation button states"""
nav_buttons = [item for item in self.children if isinstance(item, discord.ui.Button)]
if len(nav_buttons) >= 2:
nav_buttons[0].disabled = (self.current_page == 0) # Previous
nav_buttons[1].disabled = (self.current_page >= self.max_pages - 1) # Next
- 🎯 Five Types: String, User, Role, Channel, Mentionable selects
- 🔧 Smart Pagination: Automatic 25-option pagination with navigation
- 📊 Multi-Selection: Configurable min/max value limits
- 🎨 Dynamic Options: Runtime option generation and filtering
- 🔄 Type Filtering: Channel type restrictions and validation
- 🛡️ Large Datasets: Efficient handling of 100+ items
- 📦 State Management: Page state preservation across interactions
- 🎯 User Experience: Intuitive navigation with clear page indicators
📝 MODERN MODAL & FORMS MASTERY #
Professional Modal System (Red 3.5+) #
class AdvancedModal(discord.ui.Modal):
"""Feature-complete modal with validation and Red integration"""
def __init__(self, title="Input Required", *, cog=None, author=None):
super().__init__(title=title, timeout=300)
self.cog = cog
self.author = author
self.result = None
# Short text input with validation
short_input = discord.ui.TextInput(
label="Username",
placeholder="Enter username (3-20 characters)...",
min_length=3,
max_length=20,
required=True,
row=0
)
# Long text input for detailed content
long_input = discord.ui.TextInput(
label="Description",
placeholder="Enter detailed description...",
style=discord.TextStyle.paragraph,
min_length=10,
max_length=1024,
required=False,
row=1
)
# Numeric input with validation
number_input = discord.ui.TextInput(
label="Amount",
placeholder="Enter amount (1-1000)...",
min_length=1,
max_length=4,
required=True,
row=2
)
# URL input with format validation
url_input = discord.ui.TextInput(
label="Website URL",
placeholder="https://example.com",
min_length=7,
max_length=200,
required=False,
row=3
)
# Configuration input with parsing
config_input = discord.ui.TextInput(
label="Configuration",
placeholder="key1=value1\nkey2=value2",
style=discord.TextStyle.paragraph,
min_length=0,
max_length=2000,
required=False,
row=4
)
async def on_submit(self, interaction: discord.Interaction):
"""Process form submission with comprehensive validation"""
try:
# Validate username
username = self.short_input.value.strip()
if not username.replace('_', '').replace('-', '').isalnum():
raise ValueError("Username can only contain letters, numbers, _ and -")
# Validate number
try:
amount = int(self.number_input.value)
if not 1 <= amount <= 1000:
raise ValueError("Amount must be between 1 and 1000")
except ValueError as e:
if "invalid literal" in str(e):
raise ValueError("Amount must be a valid number")
raise
# Validate URL if provided
url = self.url_input.value.strip()
if url and not url.startswith(('http://', 'https://')):
raise ValueError("URL must start with http:// or https://")
# Parse configuration if provided
config_data = {}
if self.config_input.value.strip():
try:
for line in self.config_input.value.strip().split('\n'):
if '=' in line:
key, value = line.split('=', 1)
config_data[key.strip()] = value.strip()
except Exception:
raise ValueError("Configuration format: key=value (one per line)")
# Build result
self.result = {
"username": username,
"description": self.long_input.value.strip(),
"amount": amount,
"url": url if url else None,
"config": config_data
}
# Success response
embed = discord.Embed(
title="✅ Form Submitted Successfully",
color=0x00ff00,
timestamp=discord.utils.utcnow()
)
embed.add_field(name="Username", value=username, inline=True)
embed.add_field(name="Amount", value=str(amount), inline=True)
if url:
embed.add_field(name="URL", value=url, inline=False)
await interaction.response.send_message(embed=embed, ephemeral=True)
# Process with cog if available
if self.cog and hasattr(self.cog, 'process_modal_data'):
await self.cog.process_modal_data(interaction, self.result)
except ValueError as e:
# User-friendly error
error_embed = discord.Embed(
title="❌ Validation Error",
description=str(e),
color=0xff0000
)
await interaction.response.send_message(embed=error_embed, ephemeral=True)
except Exception as e:
# Unexpected error
if self.cog:
log.exception(f"Modal submission error: {e}")
await interaction.response.send_message(
"❌ An unexpected error occurred. Please try again.",
ephemeral=True
)
async def on_error(self, interaction: discord.Interaction, error: Exception):
"""Handle modal errors gracefully"""
log.exception(f"Modal error: {error}")
if not interaction.response.is_done():
await interaction.response.send_message(
"❌ An error occurred while processing your submission.",
ephemeral=True
)
# Red Configuration Modal
class RedConfigModal(discord.ui.Modal):
"""Modal for Red-DiscordBot configuration management"""
def __init__(self, cog, config_key, current_value="", *, title="Configuration"):
super().__init__(title=title, timeout=300)
self.cog = cog
self.config_key = config_key
self.config_input = discord.ui.TextInput(
label=f"Set {config_key.replace('_', ' ').title()}",
placeholder=f"Current: {current_value}" if current_value else "Enter new value...",
default=str(current_value) if current_value else "",
max_length=1000,
required=True
)
self.add_item(self.config_input)
async def on_submit(self, interaction: discord.Interaction):
"""Update Red configuration with type conversion"""
try:
new_value = self.config_input.value.strip()
# Smart type conversion
if self.config_key.endswith(('_enabled', '_enable')) or self.config_key.startswith('enable_'):
new_value = new_value.lower() in ('true', '1', 'yes', 'on', 'enable')
elif self.config_key.endswith(('_count', '_limit', '_max', '_min')):
new_value = int(new_value)
elif self.config_key.endswith(('_list', '_items')):
new_value = [item.strip() for item in new_value.split(',') if item.strip()]
elif self.config_key.endswith('_time'):
new_value = float(new_value)
# Update through Red's config system
if interaction.guild:
await self.cog.config.guild(interaction.guild).set_raw(self.config_key, value=new_value)
scope = f"guild {interaction.guild.name}"
else:
await self.cog.config.set_raw(self.config_key, value=new_value)
scope = "global"
# Success response
embed = discord.Embed(
title="✅ Configuration Updated",
description=f"Setting {self.config_key} updated for {scope}",
color=0x00ff00
)
embed.add_field(name="New Value", value=f"`{new_value}`", inline=False)
await interaction.response.send_message(embed=embed, ephemeral=True)
except ValueError as e:
await interaction.response.send_message(
f"❌ Invalid Value: {e}",
ephemeral=True
)
except Exception as e:
log.exception(f"Config modal error: {e}")
await interaction.response.send_message(
"❌ Failed to update configuration. Check logs for details.",
ephemeral=True
)
- 🎯 Five Inputs: Short text, long text, numeric, URL, config validation
- 🔧 Validation: Client and server-side validation with clear errors
- 📊 Red Integration: Native config system integration with type conversion
- 🎨 Multi-Step: Progressive form workflows with state management
- 🔄 Type Conversion: Automatic data type handling for Red configs
- 🛡️ Error Handling: Comprehensive error catching and user feedback
- 📦 Data Collection: Structured data collection and processing
- 🎯 User Experience: Clear placeholders and validation messages
⚡ QUICK UI REFERENCE (Red 3.5+ Production) #
# Complete Modern UI Cog Template (Production-Ready)
from redbot.core import commands, Config
from redbot.core.utils.views import ConfirmView, SimpleMenu
import discord
import asyncio
import logging
log = logging.getLogger("red.mycog.ui")
# REQUIRED: GDPR compliance for UI data
__red_end_user_data_statement__ = (
"This cog stores interaction data temporarily for UI state management. "
"Data is automatically cleaned up after view timeouts."
)
class ProductionUICog(commands.Cog):
"""Production-ready UI cog with all modern patterns"""
def __init__(self, bot):
self.bot = bot
self.config = Config.get_conf(self, identifier=1234567890, force_registration=True)
self.active_views = {}
# UI configuration
self.config.register_guild(
ui_timeout=300,
ephemeral_by_default=True,
theme_color=0x5865F2
)
async def cog_load(self):
"""Initialize UI system"""
log.info("UI system loading...")
async def cog_unload(self):
"""Clean up UI system"""
log.info("UI system unloading...")
for view in list(self.active_views.values()):
view.stop()
self.active_views.clear()
async def red_delete_data_for_user(self, kwargs):
"""GDPR compliance - clean up user UI data"""
user_id = kwargs.get("user_id")
if user_id:
for view in list(self.active_views.values()):
if hasattr(view, 'author') and view.author.id == user_id:
view.stop()
@commands.hybrid_command()
async def ui_demo(self, ctx):
"""Demonstrate all UI components"""
view = ComprehensiveUIDemo(ctx.author)
embed = discord.Embed(
title="🎨 UI Component Demo",
description="Try all the different UI components below!",
color=await ctx.embed_color()
)
view.message = await ctx.send(embed=embed, view=view)
# REQUIRED: Async setup function (Red 3.5+)
async def setup(bot):
"""Load the cog - MUST BE ASYNC in Red 3.5+"""
cog = ProductionUICog(bot)
await bot.add_cog(cog)
# Essential Component Limits (Discord.py 2.3+)
LIMITS = {
"buttons_per_row": 5,
"select_menus_per_row": 1,
"rows_per_view": 5,
"total_components": 25,
"select_options": 25,
"modal_text_inputs": 5,
"embed_fields": 25,
"embed_total_chars": 6000,
"view_timeout_max": 900 # 15 minutes
}
# Quick UI Patterns
# 1. Simple Confirmation
confirm = ConfirmView(ctx.author, timeout=30)
confirm.message = await ctx.send("Confirm action?", view=confirm)
await confirm.wait()
if confirm.result: # True/False/None
await ctx.send("✅ Confirmed!")
# 2. Paginated Menu
pages = ["Page 1", "Page 2", "Page 3"]
menu = SimpleMenu(pages, timeout=180)
await menu.start(ctx)
# 3. Quick Modal
modal = AdvancedModal("Input Form")
await interaction.response.send_modal(modal)
# 4. Multi-Component View
view = CustomView(author=ctx.author)
view.add_item(discord.ui.Button(label="Action"))
view.add_item(discord.ui.Select(options=[...]))
await ctx.send("Interactive UI:", view=view)
# 5. Modern Button Types
@discord.ui.button(style=discord.ButtonStyle.primary) # Blue
@discord.ui.button(style=discord.ButtonStyle.secondary) # Gray
@discord.ui.button(style=discord.ButtonStyle.success) # Green
@discord.ui.button(style=discord.ButtonStyle.danger) # Red
@discord.ui.button(style=discord.ButtonStyle.link, url="...") # Link
# 6. Modern Select Types (Discord.py 2.3+)
@discord.ui.select() # String options
@discord.ui.user_select() # User picker
@discord.ui.role_select() # Role picker
@discord.ui.channel_select() # Channel picker
@discord.ui.mentionable_select() # Users + Roles
# 7. Error Handling Pattern
async def on_error(self, interaction, error, item):
log.exception(f"UI Error: {error}")
if not interaction.response.is_done():
await interaction.response.send_message(
"❌ An error occurred.", ephemeral=True
)
# 8. Security Pattern
async def interaction_check(self, interaction):
if self.author and interaction.user != self.author:
await interaction.response.send_message(
"❌ Unauthorized access.", ephemeral=True
)
return False
return True
# 9. Timeout Pattern
async def on_timeout(self):
try:
for child in self.children:
child.disabled = True
if self.message:
await self.message.edit(view=self)
except discord.HTTPException:
pass
🎨 Master Red-DiscordBot’s complete modern UI component ecosystem for professional, secure, and scalable Discord bot interfaces with Red 3.5+ compliance and latest discord.py 2.3+ features!