telegram-keyboard-design
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
- keyboard-templates.py – Ready-to-use keyboard builders
- callback-handlers.py – Callback handler examples
- flow-patterns.md – Common conversation flows
References
- python-telegram-bot: https://python-telegram-bot.readthedocs.io/
- Telegram Bot API: https://core.telegram.org/bots/api#inlinekeyboardmarkup
- UX Best Practices: https://core.telegram.org/bots/design