0
# Keyboards and Markup
1
2
Comprehensive keyboard system for creating interactive interfaces including reply keyboards, inline keyboards, and specialized button types for web apps, contact/location requests, and callback data.
3
4
## Capabilities
5
6
### Reply Keyboards
7
8
Custom keyboards that replace the standard keyboard in the user's Telegram client.
9
10
```python { .api }
11
class ReplyKeyboardMarkup:
12
def __init__(
13
self,
14
keyboard: list[list[KeyboardButton]],
15
is_persistent: bool = None,
16
resize_keyboard: bool = None,
17
one_time_keyboard: bool = None,
18
input_field_placeholder: str = None,
19
selective: bool = None
20
): ...
21
22
keyboard: list[list[KeyboardButton]]
23
is_persistent: bool | None
24
resize_keyboard: bool | None
25
one_time_keyboard: bool | None
26
input_field_placeholder: str | None
27
selective: bool | None
28
29
@classmethod
30
def from_button(cls, button: KeyboardButton, **kwargs) -> 'ReplyKeyboardMarkup': ...
31
@classmethod
32
def from_row(cls, *args: KeyboardButton, **kwargs) -> 'ReplyKeyboardMarkup': ...
33
@classmethod
34
def from_column(cls, *args: KeyboardButton, **kwargs) -> 'ReplyKeyboardMarkup': ...
35
36
class KeyboardButton:
37
def __init__(
38
self,
39
text: str,
40
request_users: KeyboardButtonRequestUsers = None,
41
request_chat: KeyboardButtonRequestChat = None,
42
request_contact: bool = None,
43
request_location: bool = None,
44
request_poll: KeyboardButtonPollType = None,
45
web_app: WebAppInfo = None
46
): ...
47
48
text: str
49
request_users: KeyboardButtonRequestUsers | None
50
request_chat: KeyboardButtonRequestChat | None
51
request_contact: bool | None
52
request_location: bool | None
53
request_poll: KeyboardButtonPollType | None
54
web_app: WebAppInfo | None
55
56
class ReplyKeyboardRemove:
57
def __init__(self, selective: bool = None): ...
58
59
remove_keyboard: bool = True
60
selective: bool | None
61
```
62
63
Usage examples:
64
65
```python
66
from telegram import ReplyKeyboardMarkup, KeyboardButton
67
68
# Simple text buttons
69
keyboard = [
70
["Option 1", "Option 2"],
71
["Option 3", "Option 4"]
72
]
73
reply_markup = ReplyKeyboardMarkup(keyboard, resize_keyboard=True)
74
75
# Using KeyboardButton objects
76
buttons = [
77
[KeyboardButton("Share Contact", request_contact=True)],
78
[KeyboardButton("Share Location", request_location=True)],
79
[KeyboardButton("Web App", web_app=WebAppInfo(url="https://example.com"))]
80
]
81
reply_markup = ReplyKeyboardMarkup(buttons, one_time_keyboard=True)
82
83
# Send message with keyboard
84
await bot.send_message(
85
chat_id=chat_id,
86
text="Choose an option:",
87
reply_markup=reply_markup
88
)
89
90
# Remove keyboard
91
from telegram import ReplyKeyboardRemove
92
await bot.send_message(
93
chat_id=chat_id,
94
text="Keyboard removed",
95
reply_markup=ReplyKeyboardRemove()
96
)
97
```
98
99
### Inline Keyboards
100
101
Keyboards with buttons that appear directly below messages, supporting callbacks, URLs, and other actions.
102
103
```python { .api }
104
class InlineKeyboardMarkup:
105
def __init__(self, inline_keyboard: list[list[InlineKeyboardButton]]): ...
106
107
inline_keyboard: list[list[InlineKeyboardButton]]
108
109
@classmethod
110
def from_button(cls, button: InlineKeyboardButton) -> 'InlineKeyboardMarkup': ...
111
@classmethod
112
def from_row(cls, *args: InlineKeyboardButton) -> 'InlineKeyboardMarkup': ...
113
@classmethod
114
def from_column(cls, *args: InlineKeyboardButton) -> 'InlineKeyboardMarkup': ...
115
116
class InlineKeyboardButton:
117
def __init__(
118
self,
119
text: str,
120
url: str = None,
121
callback_data: str = None,
122
web_app: WebAppInfo = None,
123
login_url: LoginUrl = None,
124
switch_inline_query: str = None,
125
switch_inline_query_current_chat: str = None,
126
switch_inline_query_chosen_chat: SwitchInlineQueryChosenChat = None,
127
copy_text: CopyTextButton = None,
128
callback_game: CallbackGame = None,
129
pay: bool = None
130
): ...
131
132
text: str
133
url: str | None
134
callback_data: str | None
135
web_app: WebAppInfo | None
136
login_url: LoginUrl | None
137
switch_inline_query: str | None
138
switch_inline_query_current_chat: str | None
139
switch_inline_query_chosen_chat: SwitchInlineQueryChosenChat | None
140
copy_text: CopyTextButton | None
141
callback_game: CallbackGame | None
142
pay: bool | None
143
```
144
145
Usage examples:
146
147
```python
148
from telegram import InlineKeyboardMarkup, InlineKeyboardButton
149
150
# Callback data buttons
151
keyboard = [
152
[
153
InlineKeyboardButton("Option A", callback_data="opt_a"),
154
InlineKeyboardButton("Option B", callback_data="opt_b")
155
],
156
[InlineKeyboardButton("Help", callback_data="help")]
157
]
158
reply_markup = InlineKeyboardMarkup(keyboard)
159
160
# URL buttons
161
keyboard = [
162
[InlineKeyboardButton("Visit Website", url="https://example.com")],
163
[InlineKeyboardButton("Telegram Channel", url="https://t.me/channel")]
164
]
165
reply_markup = InlineKeyboardMarkup(keyboard)
166
167
# Mixed button types
168
keyboard = [
169
[InlineKeyboardButton("Callback", callback_data="cb_data")],
170
[InlineKeyboardButton("URL", url="https://example.com")],
171
[InlineKeyboardButton("Switch Inline", switch_inline_query="query")],
172
[InlineKeyboardButton("Web App", web_app=WebAppInfo(url="https://webapp.com"))]
173
]
174
reply_markup = InlineKeyboardMarkup(keyboard)
175
176
await bot.send_message(
177
chat_id=chat_id,
178
text="Choose an action:",
179
reply_markup=reply_markup
180
)
181
```
182
183
### Force Reply
184
185
Force users to reply to a specific message.
186
187
```python { .api }
188
class ForceReply:
189
def __init__(
190
self,
191
input_field_placeholder: str = None,
192
selective: bool = None
193
): ...
194
195
force_reply: bool = True
196
input_field_placeholder: str | None
197
selective: bool | None
198
```
199
200
Usage example:
201
202
```python
203
from telegram import ForceReply
204
205
await bot.send_message(
206
chat_id=chat_id,
207
text="Please tell me your name:",
208
reply_markup=ForceReply(input_field_placeholder="Enter your name...")
209
)
210
```
211
212
### Specialized Button Types
213
214
Advanced button configurations for specific use cases.
215
216
```python { .api }
217
class KeyboardButtonRequestUsers:
218
def __init__(
219
self,
220
request_id: int,
221
user_is_bot: bool = None,
222
user_is_premium: bool = None,
223
max_quantity: int = None,
224
request_name: bool = None,
225
request_username: bool = None,
226
request_photo: bool = None
227
): ...
228
229
request_id: int
230
user_is_bot: bool | None
231
user_is_premium: bool | None
232
max_quantity: int | None
233
request_name: bool | None
234
request_username: bool | None
235
request_photo: bool | None
236
237
class KeyboardButtonRequestChat:
238
def __init__(
239
self,
240
request_id: int,
241
chat_is_channel: bool,
242
chat_is_forum: bool = None,
243
chat_has_username: bool = None,
244
chat_is_created: bool = None,
245
user_administrator_rights: ChatAdministratorRights = None,
246
bot_administrator_rights: ChatAdministratorRights = None,
247
bot_is_member: bool = None,
248
request_title: bool = None,
249
request_username: bool = None,
250
request_photo: bool = None
251
): ...
252
253
request_id: int
254
chat_is_channel: bool
255
chat_is_forum: bool | None
256
chat_has_username: bool | None
257
chat_is_created: bool | None
258
user_administrator_rights: ChatAdministratorRights | None
259
bot_administrator_rights: ChatAdministratorRights | None
260
bot_is_member: bool | None
261
request_title: bool | None
262
request_username: bool | None
263
request_photo: bool | None
264
265
class KeyboardButtonPollType:
266
def __init__(self, type: str = None): ...
267
268
type: str | None
269
270
class WebAppInfo:
271
def __init__(self, url: str): ...
272
273
url: str
274
275
class LoginUrl:
276
def __init__(
277
self,
278
url: str,
279
forward_text: str = None,
280
bot_username: str = None,
281
request_write_access: bool = None
282
): ...
283
284
url: str
285
forward_text: str | None
286
bot_username: str | None
287
request_write_access: bool | None
288
289
class SwitchInlineQueryChosenChat:
290
def __init__(
291
self,
292
query: str = None,
293
allow_user_chats: bool = None,
294
allow_bot_chats: bool = None,
295
allow_group_chats: bool = None,
296
allow_channel_chats: bool = None
297
): ...
298
299
query: str | None
300
allow_user_chats: bool | None
301
allow_bot_chats: bool | None
302
allow_group_chats: bool | None
303
allow_channel_chats: bool | None
304
305
class CopyTextButton:
306
def __init__(self, text: str): ...
307
308
text: str
309
```
310
311
Usage examples:
312
313
```python
314
# Request user selection
315
user_request = KeyboardButtonRequestUsers(
316
request_id=1,
317
user_is_bot=False,
318
max_quantity=5,
319
request_name=True,
320
request_username=True
321
)
322
button = KeyboardButton("Select Users", request_users=user_request)
323
324
# Request chat selection
325
chat_request = KeyboardButtonRequestChat(
326
request_id=2,
327
chat_is_channel=False,
328
chat_has_username=True,
329
request_title=True
330
)
331
button = KeyboardButton("Select Chat", request_chat=chat_request)
332
333
# Poll type button
334
poll_button = KeyboardButton(
335
"Create Poll",
336
request_poll=KeyboardButtonPollType(type="quiz")
337
)
338
339
# Web app button
340
webapp_button = InlineKeyboardButton(
341
"Open Web App",
342
web_app=WebAppInfo(url="https://your-webapp.com")
343
)
344
345
# Login URL button
346
login_button = InlineKeyboardButton(
347
"Login",
348
login_url=LoginUrl(
349
url="https://your-site.com/auth",
350
request_write_access=True
351
)
352
)
353
354
# Switch inline query button
355
switch_button = InlineKeyboardButton(
356
"Share",
357
switch_inline_query_chosen_chat=SwitchInlineQueryChosenChat(
358
query="shared content",
359
allow_user_chats=True,
360
allow_group_chats=True
361
)
362
)
363
```
364
365
### Dynamic Keyboard Building
366
367
Utility methods for building keyboards programmatically.
368
369
```python
370
# Build keyboard from lists
371
def build_menu(buttons, n_cols, header_buttons=None, footer_buttons=None):
372
"""Build a menu keyboard with specified column layout."""
373
menu = [buttons[i:i + n_cols] for i in range(0, len(buttons), n_cols)]
374
if header_buttons:
375
menu.insert(0, header_buttons)
376
if footer_buttons:
377
menu.append(footer_buttons)
378
return menu
379
380
# Example usage
381
buttons = [
382
InlineKeyboardButton(f"Option {i}", callback_data=f"opt_{i}")
383
for i in range(1, 10)
384
]
385
386
keyboard = build_menu(
387
buttons,
388
n_cols=3,
389
header_buttons=[InlineKeyboardButton("Header", callback_data="header")],
390
footer_buttons=[InlineKeyboardButton("Cancel", callback_data="cancel")]
391
)
392
393
reply_markup = InlineKeyboardMarkup(keyboard)
394
```
395
396
### Handling Keyboard Responses
397
398
Processing user interactions with keyboards.
399
400
```python
401
from telegram.ext import CallbackQueryHandler, MessageHandler, filters
402
403
# Handle callback query responses
404
async def button_handler(update, context):
405
query = update.callback_query
406
await query.answer() # Always acknowledge callback queries
407
408
data = query.data
409
if data == "opt_a":
410
await query.edit_message_text("You chose Option A")
411
elif data == "opt_b":
412
await query.edit_message_text("You chose Option B")
413
414
app.add_handler(CallbackQueryHandler(button_handler))
415
416
# Handle shared users/chats
417
async def handle_users_shared(update, context):
418
users_shared = update.message.users_shared
419
request_id = users_shared.request_id
420
users = users_shared.users
421
422
user_names = [f"{user.first_name}" for user in users if user.first_name]
423
await update.message.reply_text(f"You shared users: {', '.join(user_names)}")
424
425
async def handle_chat_shared(update, context):
426
chat_shared = update.message.chat_shared
427
request_id = chat_shared.request_id
428
chat_id = chat_shared.chat_id
429
430
await update.message.reply_text(f"You shared chat ID: {chat_id}")
431
432
# Filter for shared content
433
app.add_handler(MessageHandler(filters.StatusUpdate.USERS_SHARED, handle_users_shared))
434
app.add_handler(MessageHandler(filters.StatusUpdate.CHAT_SHARED, handle_chat_shared))
435
```
436
437
## Common Patterns
438
439
### Pagination Keyboards
440
441
```python
442
def create_pagination_keyboard(items, page, per_page=5):
443
"""Create paginated inline keyboard."""
444
start = page * per_page
445
end = start + per_page
446
current_items = items[start:end]
447
448
keyboard = []
449
450
# Item buttons
451
for item in current_items:
452
keyboard.append([InlineKeyboardButton(item['name'], callback_data=f"item_{item['id']}")])
453
454
# Navigation buttons
455
nav_buttons = []
456
if page > 0:
457
nav_buttons.append(InlineKeyboardButton("◀️ Previous", callback_data=f"page_{page-1}"))
458
if end < len(items):
459
nav_buttons.append(InlineKeyboardButton("Next ▶️", callback_data=f"page_{page+1}"))
460
461
if nav_buttons:
462
keyboard.append(nav_buttons)
463
464
return InlineKeyboardMarkup(keyboard)
465
```
466
467
### Confirmation Dialogs
468
469
```python
470
def create_confirmation_keyboard(action_data):
471
"""Create Yes/No confirmation keyboard."""
472
keyboard = [
473
[
474
InlineKeyboardButton("✅ Yes", callback_data=f"confirm_{action_data}"),
475
InlineKeyboardButton("❌ No", callback_data="cancel")
476
]
477
]
478
return InlineKeyboardMarkup(keyboard)
479
```
480
481
### Settings Menus
482
483
```python
484
def create_settings_keyboard(user_settings):
485
"""Create settings toggle keyboard."""
486
keyboard = []
487
488
for setting, value in user_settings.items():
489
status = "✅" if value else "❌"
490
keyboard.append([
491
InlineKeyboardButton(
492
f"{status} {setting.title()}",
493
callback_data=f"toggle_{setting}"
494
)
495
])
496
497
keyboard.append([InlineKeyboardButton("💾 Save", callback_data="save_settings")])
498
return InlineKeyboardMarkup(keyboard)
499
```