telegram-keyboard-design

📁 ils15/copilot-global-config 📅 11 days ago
2
总安装量
2
周安装量
#70739
全站排名
安装命令
npx skills add https://github.com/ils15/copilot-global-config --skill telegram-keyboard-design

Agent 安装分布

replit 1
trae 1
github-copilot 1
antigravity 1
gemini-cli 1

Skill 文档

Telegram Keyboard Design Skill

When to Use

Use this skill when:

  • Designing Telegram bot menus and navigation
  • Creating inline buttons for product browsing
  • Building conversational flows with progressive disclosure
  • Implementing callback handlers for button interactions
  • Designing mobile-first keyboard layouts
  • Managing state in bot conversations
  • Creating rich interaction patterns

Design Principles

1. Progressive Disclosure

Only show relevant buttons at each step, reducing cognitive load:

Bad: Show 20 buttons at once
Good: Show 3-5 buttons, then reveal more based on choice

2. Mobile-First Design

  • Buttons are small touch targets
  • Stack vertically when possible (2-column max)
  • Avoid too many rows (5 max per screen)

3. Consistent Navigation

  • Always include a “Back” button for multi-step flows
  • “Main Menu” to return home
  • Clear CTA buttons

Code Patterns

1. Reply Keyboard (Main Menu)

from telegram import ReplyKeyboardMarkup, KeyboardButton

def get_main_keyboard():
    """Main menu with 2-column layout"""
    keyboard = [
        [
            KeyboardButton("🛍️ Buscar Ofertas"),
            KeyboardButton("💰 Meus Cupons")
        ],
        [
            KeyboardButton("❓ Ajuda"),
            KeyboardButton("⚙️ Configurações")
        ]
    ]
    
    return ReplyKeyboardMarkup(
        keyboard,
        resize_keyboard=True,      # Auto-size buttons
        one_time_keyboard=False    # Keep keyboard visible
    )

2. Inline Keyboard (Button Actions)

from telegram import InlineKeyboardButton, InlineKeyboardMarkup

def get_product_keyboard(product_id: int, page: int = 1):
    """Product view with action buttons"""
    keyboard = [
        [
            InlineKeyboardButton("📋 Detalhes", callback_data=f"product_details_{product_id}"),
            InlineKeyboardButton("🔗 Link", url=f"https://ofertachina.com/p/{product_id}")
        ],
        [
            InlineKeyboardButton("❤️ Salvar", callback_data=f"save_product_{product_id}"),
            InlineKeyboardButton("📤 Compartilhar", callback_data=f"share_product_{product_id}")
        ],
        [
            InlineKeyboardButton("⬅️ Voltar", callback_data="back_to_products")
        ]
    ]
    
    return InlineKeyboardMarkup(keyboard)

def get_pagination_keyboard(page: int, total_pages: int):
    """Pagination buttons"""
    keyboard = []
    
    buttons = []
    if page > 1:
        buttons.append(InlineKeyboardButton("⬅️ Anterior", callback_data=f"page_{page-1}"))
    
    buttons.append(InlineKeyboardButton(f"{page}/{total_pages}", callback_data="noop"))
    
    if page < total_pages:
        buttons.append(InlineKeyboardButton("Próxima ➡️", callback_data=f"page_{page+1}"))
    
    keyboard.append(buttons)
    return InlineKeyboardMarkup(keyboard)

3. Category Selection Flow

# Progressive disclosure: First show categories, then products

def get_category_keyboard():
    """First step: Choose category"""
    keyboard = [
        [InlineKeyboardButton("🖥️ Eletrônicos", callback_data="cat_electronics")],
        [InlineKeyboardButton("👗 Moda", callback_data="cat_fashion")],
        [InlineKeyboardButton("🏠 Casa", callback_data="cat_home")],
        [InlineKeyboardButton("🎮 Games", callback_data="cat_games")],
        [InlineKeyboardButton("📚 Livros", callback_data="cat_books")],
        [InlineKeyboardButton("🔄 Voltar", callback_data="main_menu")]
    ]
    
    return InlineKeyboardMarkup(keyboard)

def get_subcategory_keyboard(category: str):
    """Second step: Choose subcategory within category"""
    subcategories = {
        "electronics": [
            ("📱 Smartphones", "subcat_phones"),
            ("💻 Laptops", "subcat_laptops"),
            ("⌚ Wearables", "subcat_wearables"),
        ],
        "fashion": [
            ("👔 Homem", "subcat_mens"),
            ("👗 Mulher", "subcat_womens"),
            ("👶 Infantil", "subcat_kids"),
        ]
    }
    
    keyboard = []
    for label, callback in subcategories.get(category, []):
        keyboard.append([InlineKeyboardButton(label, callback_data=callback)])
    
    keyboard.append([InlineKeyboardButton("⬅️ Voltar", callback_data="categories")])
    
    return InlineKeyboardMarkup(keyboard)

4. Callback Handler

from telegram import Update
from telegram.ext import ContextTypes, CallbackQueryHandler

async def handle_product_action(update: Update, context: ContextTypes.DEFAULT_TYPE):
    """Handle button callbacks"""
    query = update.callback_query
    await query.answer()  # Remove loading spinner
    
    callback_data = query.data
    
    if callback_data.startswith("product_details_"):
        product_id = int(callback_data.split("_")[-1])
        await show_product_details(query, product_id, context)
    
    elif callback_data.startswith("save_product_"):
        product_id = int(callback_data.split("_")[-1])
        await save_to_favorites(query, product_id, context)
    
    elif callback_data.startswith("page_"):
        page = int(callback_data.split("_")[-1])
        await show_page(query, page, context)
    
    elif callback_data == "main_menu":
        await query.edit_message_text(
            text="🏠 Menu Principal",
            reply_markup=get_main_keyboard()
        )

async def show_product_details(query, product_id: int, context: ContextTypes.DEFAULT_TYPE):
    """Show detailed product view"""
    # Fetch product from database
    product = await get_product(product_id)
    
    text = f"""
📦 *{product['title']}*

💰 Preço: R$ {product['price']:.2f}
⭐ Avaliação: {product['rating']}/5
📦 Envio: Frete grátis

{product['description']}

🔗 [Ver na loja]({product['url']})
    """.strip()
    
    await query.edit_message_text(
        text=text,
        reply_markup=get_product_keyboard(product_id),
        parse_mode="Markdown"
    )

async def save_to_favorites(query, product_id: int, context: ContextTypes.DEFAULT_TYPE):
    """Toggle favorite status"""
    user_id = query.from_user.id
    
    # Save to database
    saved = await toggle_favorite(user_id, product_id)
    
    status = "✅ Salvo!" if saved else "❌ Removido"
    await query.answer(status, show_alert=False)

5. Search with Buttons

from telegram import Update
from telegram.ext import ContextTypes, MessageHandler, filters

async def handle_search(update: Update, context: ContextTypes.DEFAULT_TYPE):
    """Handle search input"""
    search_query = update.message.text
    
    # Search products
    products = await search_products(search_query)
    
    if not products:
        await update.message.reply_text("❌ Nenhum produto encontrado")
        return
    
    # Show first 5 products with next page button
    text = "🔍 Resultados da busca:\n\n"
    
    keyboard = []
    for i, product in enumerate(products[:5], 1):
        text += f"{i}. {product['title']}\n💰 R$ {product['price']}\n\n"
        
        keyboard.append([
            InlineKeyboardButton(
                f"#{i} Ver",
                callback_data=f"product_details_{product['id']}"
            )
        ])
    
    # Pagination if more results
    if len(products) > 5:
        keyboard.append([
            InlineKeyboardButton("Próxima página ➡️", callback_data="search_next_page")
        ])
    
    keyboard.append([InlineKeyboardButton("🔄 Nova busca", callback_data="main_menu")])
    
    await update.message.reply_text(
        text=text,
        reply_markup=InlineKeyboardMarkup(keyboard),
        parse_mode="HTML"
    )

6. Confirmation Dialog

def get_confirm_keyboard(action_id: str):
    """Confirmation buttons"""
    keyboard = [
        [
            InlineKeyboardButton("✅ Confirmar", callback_data=f"confirm_{action_id}"),
            InlineKeyboardButton("❌ Cancelar", callback_data="cancel")
        ]
    ]
    
    return InlineKeyboardMarkup(keyboard)

async def handle_share(query, product_id: int, context: ContextTypes.DEFAULT_TYPE):
    """Share product confirmation"""
    product = await get_product(product_id)
    
    text = f"""
Compartilhar este produto?

📦 {product['title']}
💰 R$ {product['price']}
    """.strip()
    
    await query.edit_message_text(
        text=text,
        reply_markup=get_confirm_keyboard(f"share_{product_id}")
    )

Mobile UX Checklist

✅ Buttons are 30-40px tall (easy to tap)
✅ Max 2 buttons per row horizontally
✅ Max 5 rows visible without scrolling
✅ Back buttons always available
✅ Loading state with query.answer()
✅ Edit, not reply for follow-up messages
✅ Emoji for visual clarity
✅ Short labels (max 20 chars)

Anti-Patterns ❌

❌ Show 20 buttons at once
❌ Nested buttons without back
❌ Tiny buttons (<25px)
❌ No loading feedback (frozen UI)
❌ Same text with different actions
❌ Deep button hierarchies (>3 levels)

Related Files

References