Как сделать навигацию в Telegram-боте: inline-кнопки, reply-меню, команды и Mini App — полное руководство
Основной чат
Чат для вайбкодеров: новости, гайды, поиск исполнителей, маркетплейс и разбор реальных кейсов.
Навигация в Telegram-боте — это набор интерфейсных элементов, с помощью которых пользователь перемещается между разделами бота, запускает нужные функции и получает ответы без необходимости что-то печатать вручную.
Хорошая навигация в боте работает по одному принципу: пользователь всегда видит, что можно сделать дальше, и добирается до нужного действия не более чем за два-три нажатия.
В Telegram есть несколько видов навигационных элементов:
| Элемент | Где отображается | Главная особенность |
|---|---|---|
| Inline-кнопки | Под конкретным сообщением | Привязаны к сообщению, меняются динамически |
| Reply-клавиатура | Над полем ввода | Постоянно видны, заменяют клавиатуру |
| Команды | Меню «/» у поля ввода | Работают всегда, из любого состояния |
| Кнопка Menu | Слева от поля ввода | Открывает список команд или Mini App |
| Mini App | Полноэкранный веб-интерфейс | Полноценный сайт внутри Telegram |
Inline-кнопки: самый гибкий вид навигации
Что это и когда использовать
Inline-кнопки прикрепляются к конкретному сообщению или медиафайлу и остаются рядом с ним. Это самый популярный вид навигации в современных ботах — они не перекрывают экран, появляются там где нужны, и могут меняться в ответ на действия пользователя.
Используйте inline-кнопки когда:
- нужно предложить варианты ответа в конкретный момент диалога
- требуется пагинация (список товаров, статей, результатов)
- нужны кнопки «Подтвердить / Отменить» для опасных действий
- хотите обновлять содержимое сообщения без отправки нового
Inline-кнопки на Python (aiogram 3)
from aiogram import Bot, Dispatcher, F
from aiogram.types import InlineKeyboardMarkup, InlineKeyboardButton, CallbackQuery
from aiogram.filters import CommandStart
bot = Bot(token="YOUR_BOT_TOKEN")
dp = Dispatcher()
# Главное меню
def main_menu() -> InlineKeyboardMarkup:
return InlineKeyboardMarkup(inline_keyboard=[
[
InlineKeyboardButton(text="📦 Каталог", callback_data="catalog"),
InlineKeyboardButton(text="🛒 Корзина", callback_data="cart"),
],
[
InlineKeyboardButton(text="📞 Поддержка", callback_data="support"),
InlineKeyboardButton(text="⚙️ Настройки", callback_data="settings"),
],
])
@dp.message(CommandStart())
async def start(message):
await message.answer(
"Привет! Выберите раздел:",
reply_markup=main_menu()
)
# Обработка нажатий
@dp.callback_query(F.data == "catalog")
async def show_catalog(callback: CallbackQuery):
# Обновляем сообщение вместо отправки нового
await callback.message.edit_text(
"📦 Каталог товаров:",
reply_markup=InlineKeyboardMarkup(inline_keyboard=[
[InlineKeyboardButton(text="← Назад", callback_data="back_main")],
])
)
await callback.answer() # убираем "часики" на кнопке
@dp.callback_query(F.data == "back_main")
async def back_to_main(callback: CallbackQuery):
await callback.message.edit_text(
"Главное меню:",
reply_markup=main_menu()
)
await callback.answer()
Inline-кнопки на Node.js (grammY)
const { Bot, InlineKeyboard } = require('grammy');
const bot = new Bot('YOUR_BOT_TOKEN');
// Функция создания главного меню
function mainMenu() {
return new InlineKeyboard()
.text('📦 Каталог', 'catalog').text('🛒 Корзина', 'cart').row()
.text('📞 Поддержка', 'support').text('⚙️ Настройки', 'settings');
}
bot.command('start', async (ctx) => {
await ctx.reply('Привет! Выберите раздел:', {
reply_markup: mainMenu(),
});
});
bot.callbackQuery('catalog', async (ctx) => {
await ctx.editMessageText('📦 Каталог товаров:', {
reply_markup: new InlineKeyboard().text('← Назад', 'back_main'),
});
await ctx.answerCallbackQuery();
});
bot.callbackQuery('back_main', async (ctx) => {
await ctx.editMessageText('Главное меню:', {
reply_markup: mainMenu(),
});
await ctx.answerCallbackQuery();
});
Кнопка-ссылка в inline-меню
Inline-кнопка может открывать не просто действие, а внешнюю ссылку или сайт:
InlineKeyboardButton(
text="🌐 Открыть сайт",
url="https://your-site.ru"
)
Reply-клавиатура: постоянное меню под полем ввода
Что это и когда использовать
Reply-кнопки выглядят как шаблонные заранее заготовленные ответы и закрепляются вместо основной клавиатуры на экране устройства. Обычно используются в чат-ботах в качестве основного меню.
Они всегда остаются под полем ввода, хорошо видны и занимают часть экрана, но у них есть существенный минус: они могут легко потеряться и скрыться. Пользователи иногда случайно убирают клавиатуру свайпом и не понимают куда она делась.
Используйте reply-клавиатуру когда:
- бот простой с фиксированным набором действий
- аудитория не технически подкованная, нужны постоянно видимые кнопки
- навигация не меняется в зависимости от контекста
from aiogram.types import ReplyKeyboardMarkup, KeyboardButton, ReplyKeyboardRemove
def main_reply_keyboard() -> ReplyKeyboardMarkup:
return ReplyKeyboardMarkup(
keyboard=[
[KeyboardButton(text="📦 Каталог"), KeyboardButton(text="🛒 Корзина")],
[KeyboardButton(text="📞 Поддержка"), KeyboardButton(text="ℹ️ О нас")],
],
resize_keyboard=True, # компактный размер
one_time_keyboard=False # не скрывать после нажатия
)
@dp.message(CommandStart())
async def start(message):
await message.answer(
"Выберите раздел:",
reply_markup=main_reply_keyboard()
)
# Обработка нажатия по тексту кнопки
@dp.message(F.text == "📦 Каталог")
async def catalog(message):
await message.answer("Открываю каталог...")
# Убрать клавиатуру когда не нужна
@dp.message(F.text == "Скрыть меню")
async def hide_keyboard(message):
await message.answer(
"Клавиатура скрыта",
reply_markup=ReplyKeyboardRemove()
)
Команды: навигация через /start, /help, /menu
Что это и зачем
Чтобы воспользоваться меню, нужно нажать на иконку слева от поля ввода сообщения или написать слеш — бот подскажет доступные команды. У таких команд высший приоритет: они будут выполнены, даже если пользователь находился в другой цепочке.
Команды — это страховочная сеть навигации. Даже если пользователь запутался в кнопках или потерял меню, /start всегда вернёт его в начало.
Настройка команд через BotFather
/setcommands
→ выбрать бота
→ ввести список:
start - Начало работы
menu - Главное меню
catalog - Каталог товаров
cart - Моя корзина
help - Помощь
settings - Настройки
После этого пользователь видит подсказки команд при вводе /.
Обработка команд в коде
from aiogram.filters import Command
@dp.message(Command("menu"))
async def show_menu(message):
await message.answer("Главное меню:", reply_markup=main_menu())
@dp.message(Command("help"))
async def help_command(message):
await message.answer(
"Доступные команды:\n"
"/start — начало работы\n"
"/menu — главное меню\n"
"/catalog — каталог\n"
"/cart — корзина\n"
"/help — эта справка"
)
Кнопка Menu: фиксированный вход в бота
Кнопка Menu — это кнопка слева от поля ввода сообщения (иконка сетки или стрелка). Она открывает список команд или Mini App. Настраивается через BotFather командой /setmenubutton.
Два режима:
- Список команд — открывает то же меню команд со слешем
- Mini App — открывает веб-приложение прямо из кнопки Menu
# Программная установка кнопки Menu через Bot API
from aiogram.types import MenuButtonWebApp, WebAppInfo
await bot.set_chat_menu_button(
menu_button=MenuButtonWebApp(
text="Открыть приложение",
web_app=WebAppInfo(url="https://your-site.ru/app")
)
)
Mini App: полноценный интерфейс внутри Telegram
Mini App — это веб-приложение (HTML/CSS/JS), которое открывается внутри Telegram как полноэкранный интерфейс. Подходит когда логика слишком сложна для кнопок: каталог с фильтрами, форма заказа, личный кабинет.
<!-- Минимальная структура Mini App -->
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script src="https://telegram.org/js/telegram-web-app.js"></script>
</head>
<body>
<script>
const tg = window.Telegram.WebApp;
tg.ready(); // инициализация
// Цвет интерфейса из темы Telegram
document.body.style.background = tg.themeParams.bg_color;
// Главная кнопка внизу экрана
tg.MainButton.setText('Оформить заказ');
tg.MainButton.show();
tg.MainButton.onClick(() => {
// Отправляем данные в бота
tg.sendData(JSON.stringify({ action: 'order', items: [] }));
});
</script>
</body>
</html>
Как выстроить правильную структуру навигации
Хорошая навигация в Telegram-боте строится по трём правилам.
Правило глубины. Не больше трёх уровней вложенности: главное меню → раздел → действие. Если глубже — пользователь теряется и не возвращается.
Правило кнопки «Назад». На каждом уровне глубже первого должна быть кнопка возврата. Пользователь не должен тыкать /start чтобы выбраться из подраздела.
Правило /start. Команда /start всегда возвращает в самое начало. Это абсолютный сброс, который должен работать из любого состояния.
# Универсальная структура с кнопкой Назад
def catalog_menu() -> InlineKeyboardMarkup:
return InlineKeyboardMarkup(inline_keyboard=[
[InlineKeyboardButton(text="👕 Одежда", callback_data="cat_clothes")],
[InlineKeyboardButton(text="👟 Обувь", callback_data="cat_shoes")],
[InlineKeyboardButton(text="🎒 Аксессуары", callback_data="cat_accessories")],
[InlineKeyboardButton(text="← Главное меню", callback_data="back_main")],
])
Сравнение: что выбрать под задачу
| Задача | Рекомендуемый тип |
|---|---|
| Простой бот с 3–5 разделами | Reply-клавиатура |
| Многоуровневая навигация | Inline-кнопки |
| Выбор из списка / пагинация | Inline-кнопки |
| Подтверждение действия | Inline-кнопки |
| Кнопки с ссылками на сайт | Inline-кнопки с URL |
| Быстрый доступ из любого места | Команды через BotFather |
| Сложный интерфейс с формами | Mini App |
| Корзина и оплата | Mini App |
Частые ошибки в навигации Telegram-ботов
Слишком много кнопок в ряду. Telegram отображает inline-кнопки адаптивно, но больше 3 кнопок в одной строке — это некрасиво на мобильном. Оптимально 1–2 кнопки в строке.
Нет кнопки «Назад». Пользователь зашёл в подраздел и не знает как вернуться. Потери конверсии гарантированы.
Reply-клавиатура скрывается случайно. Добавьте команду /menu которая всегда показывает клавиатуру заново. Пользователи, случайно скрывшие её, найдут способ вернуть.
Callback_data не уникальны. Если двум кнопкам присвоить одинаковый callback_data — они будут делать одно и то же. Используйте паттерн action:id: cat:12, page:3.
# Хорошо: уникальные идентификаторы с контекстом
InlineKeyboardButton(text="Следующая →", callback_data="page:2")
InlineKeyboardButton(text="Товар 15", callback_data="item:15")
# Плохо: одинаковые для разных действий
InlineKeyboardButton(text="Следующая →", callback_data="next")
InlineKeyboardButton(text="Назад", callback_data="back")
# если используете несколько уровней — 'back' сработает непредсказуемо
Отсутствует answerCallbackQuery. Если не вызвать callback.answer() после обработки — у пользователя на кнопке будет крутиться «часики» несколько секунд. Всегда отвечайте на callback, даже пустым ответом.
Чеклист навигации в Telegram-боте
Структура:
☐ Не более 3 уровней вложенности
☐ На каждом уровне (кроме первого) есть кнопка «Назад»
☐ /start всегда возвращает в главное меню
☐ /help показывает список доступных команд
Inline-кнопки:
☐ Не более 2–3 кнопок в одной строке
☐ callback_data уникальны и имеют паттерн action:id
☐ Каждый обработчик вызывает callback.answer()
☐ edit_message используется вместо отправки нового сообщения где возможно
Команды:
☐ Список команд задан через BotFather (/setcommands)
☐ Команды задокументированы кратко и понятно
Reply-клавиатура:
☐ resize_keyboard=True для компактного отображения
☐ Есть способ восстановить клавиатуру если пользователь её скрыл
Итог
Навигация в Telegram-боте строится из четырёх инструментов: inline-кнопки для динамических сценариев, reply-клавиатура для постоянного меню, команды как аварийный выход из любого состояния, Mini App для сложных интерфейсов.
Для большинства проектов достаточно inline-кнопок плюс три-четыре команды через BotFather. Reply-клавиатура подходит для простых ботов с постоянной структурой. Mini App — когда логика переросла возможности кнопочного интерфейса.
Главный принцип — пользователь никогда не должен «застрять»: всегда виден следующий шаг и всегда есть путь назад.
FAQ
Можно ли использовать inline-кнопки и reply-клавиатуру одновременно? Да. При использовании inline-кнопок пользователь видит не только их, но и основную клавиатуру. Это нормальная практика: reply-клавиатура для основного меню, inline-кнопки для контекстных действий внутри диалога.
Сколько кнопок максимум можно поставить? Технически — до 100 inline-кнопок на сообщение. Практически — не более 8–10: больше не помещается на экране и запутывает пользователя.
Как сделать кнопку которая открывает сайт?
Inline-кнопка с параметром url вместо callback_data. Нажатие открывает внешнюю ссылку в браузере или Mini App если используется web_app.
Можно ли менять кнопки после отправки?
Да — через edit_message_reply_markup (только клавиатура) или edit_message_text (текст и клавиатура). Это ключевое преимущество inline-кнопок перед reply-клавиатурой.
Что делать если пользователь потерял reply-клавиатуру?
Добавить команду /menu которая отправляет новое сообщение с reply_markup=main_reply_keyboard(). Также можно добавить inline-кнопку «Показать меню» в приветственном сообщении.
Актуально для aiogram 3.x, grammY 1.x, Bot API 9.x. Июнь 2026.