~/wiki / telegram-boty / navigatsiya-telegram-bot-knopki-menyu

How to Navigate in a Telegram Bot: Inline buttons, reply menus, commands and Mini App

Main chat

A chat for vibe coders: news, guides, live cases, marketplace, and finding executors.

$ cd section/ $ join vibe dev
How to Navigate in a Telegram Bot: Inline buttons, reply menus, commands and Mini App - обложка

Navigation in a Telegram bot is a set of interface elements with which the user moves between the bot sections, launches the desired functions and receives answers without having to print something manually.

Good navigation in a bot works on one principle: the user always sees what can be done next, and gets to the desired action in no more than two or three clicks.

Telegram has several types of navigation elements:

Элемент Где отображается Главная особенность
Inline-кнопки Под конкретным сообщением Привязаны к сообщению, меняются динамически
Reply-клавиатура Над полем ввода Постоянно видны, заменяют клавиатуру
Команды Меню «/» у поля ввода Работают всегда, из любого состояния
Кнопка Menu Слева от поля ввода Открывает список команд или Mini App
Mini App Полноэкранный веб-интерфейс Полноценный сайт внутри Telegram

Inline buttons: the most flexible type of navigation

What it is and when to use it

Inline buttons are attached to a particular message or media file and stay close to it. This is the most popular type of navigation in modern bots – they do not overlap the screen, appear where necessary, and can change in response to user actions.

Use the inline buttons when:

  • you need to offer answers at a specific point in the dialogue
  • pagination is required (list of products, articles, results)
  • confirm/Cancel buttons for dangerous activities
  • you want to update the content of the message without sending a new one

Inline buttons in Python (aiogram 3)

python
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 buttons on Node.js (grammY)

javascript
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();
});

An inline button can open not just an action, but an external link or site:

python
InlineKeyboardButton(
    text="🌐 Открыть сайт",
    url="https://your-site.ru"
)

Reply keyboard: permanent menu under the input field

What it is and when to use it

Reply buttons look like template pre-prepared answers and are fixed instead of the main keyboard on the device screen. Usually used in chatbots as the main menu.

They always remain under the input field, are clearly visible and occupy part of the screen, but they have a significant disadvantage: they can easily get lost and disappear. Users sometimes accidentally remove the keyboard swipe and do not understand where it went.

Use the reply keyboard when:

  • bot simple with a fixed set of actions
  • the audience is not technically savvy, you need constantly visible buttons
  • navigation does not change depending on the context
python
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()
    )

Commands: Navigation through /start, /help, /menu

What is it and why

To use the menu, you need to click on the icon to the left of the message input field or write a slash - the bot will tell the available commands. Such commands have the highest priority: they will be executed even if the user was in another chain.

Commands are the safety net of navigation. Even if the user gets confused in the buttons or loses the menu, /start will always return it to the beginning.

Set up commands through BotFather

plaintext
/setcommands
Choose a bot
Enter the list:

start - Getting started
menu - Main menu
catalog - Product catalog
Cart - My basket
help
settings - Settings

After that, the user sees command prompts when entering /.

Processing commands in code

python
from aiogram.filters import command

@dp.message(Command("menu")
async def show menu(message):
wait message.answer("Main menu:", reply markup=main menu())

@dp.message(Command("help"))
async def help command(message):
await message.answer.
"Available commands:\n"
"/start is the beginning of work\n"
"/menu is the main menu\n"
"/catalog - directory\n"
"/cart - basket\n"
"/help is this reference"
)

The Menu button is the button to the left of the message input field (the grid icon or arrow icon). It opens a command list or Mini App. Configured via BotFather by the /setmenubutton command.

Two modes:

  • List of commands - opens the same command menu with a slash
  • Mini App - opens the web application directly from the Menu button
python
# Программная установка кнопки 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: a full-fledged interface inside Telegram

Mini App is a web application (HTML/CSS/JS) that opens inside Telegram as a full-screen interface. Suitable when logic is too complex for buttons: a catalog with filters, an order form, a personal account.

html
<!-- Минимальная структура 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>

How to build the right navigation structure

Good navigation in the Telegram bot is based on three rules.

**Depth rule. ** No more than three levels of nesting: main menu → section → action. If deeper, the user is lost and does not return.

**Back button rule. ** Each level has a return button deeper than the first. The user must not poke /start to get out of the subsection.

Rule /start. /start always goes back to the beginning. This is an absolute reset that must work from any state.

python
# Универсальная структура с кнопкой Назад
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")],
    ])

Comparison: what to choose for the task

Задача Рекомендуемый тип
Простой бот с 3–5 разделами Reply-клавиатура
Многоуровневая навигация Inline-кнопки
Выбор из списка / пагинация Inline-кнопки
Подтверждение действия Inline-кнопки
Кнопки с ссылками на сайт Inline-кнопки с URL
Быстрый доступ из любого места Команды через BotFather
Сложный интерфейс с формами Mini App
Корзина и оплата Mini App

Frequent errors in the navigation of Telegram bots

Too many buttons in a row. Telegram displays inline buttons adaptively, but more than 3 buttons in one line is ugly on mobile. Optimally 1-2 buttons in a line.

No back button. The user has logged into the subsection and does not know how to return. Conversion losses are guaranteed.

Reply keyboard is hidden by accident. Add the command /menu which always shows the keyboard again. Users who accidentally hid it will find a way to get it back.

**Callback data is not unique. If two buttons are assigned the same callback_data, they will do the same thing. Use the action:id pattern: cat:12, page:3.

python
# Good: Unique identifiers with context
InlineKeyboardButton(text="Next →", callback data="page:2")
InlineKeyboardButton(text="Product 15", callback data="item:15")

# Bad: Same for different actions
InlineKeyboardButton(text="Next →", callback data="next")
InlineKeyboardButton(text="Back", callback data="back")
If you use several levels, the back will work unpredictably

No answerCallbackQuery. If you do not call callback.answer() after processing, the user will turn the "clock" on the button for several seconds. Always respond to a callback, even with a blank answer.


plaintext
Structure:
● No more than 3 levels of nesting
● Each level (except the first) has a back button.
/start always returns to the main menu
/help shows a list of available commands

Inline buttons:
● No more than 2-3 buttons in one line
Callback data is unique and has an action:id pattern.
Each handler calls callback.answer()
Edit message is used instead of sending a new message where possible.

Teams:
● The list of commands is given through BotFather (/setcommands).
Commands are documented briefly and clearly

Reply keyboard:
Resize keyboard=True for compact display
● There is a way to restore the keyboard if the user has hidden it

Outcome

Navigation in the Telegram-bot is built from four tools: inline-buttons for dynamic scripts, reply-keyboard for permanent menu, commands as an emergency exit from any state, Mini App for complex interfaces.

For most projects, inline buttons plus three or four commands via BotFather are enough. Reply keyboard is suitable for simple bots with a permanent structure. Mini App – when logic outgrown the capabilities of the button interface.

The main principle is that the user should never get stuck: the next step is always visible and there is always a way back.


FAQ

**Is it possible to use inline buttons and reply keyboards at the same time? ** Yeah. When using inline buttons, the user sees not only them, but also the main keyboard. This is normal practice: reply-keyboard for the main menu, inline-buttons for contextual actions within the dialogue.

**How many buttons can the maximum be set? ** Technically, up to 100 inline buttons per message. Practically – no more than 8-10: no longer fits on the screen and confuses the user.

**How to make a button that opens the site? ** Inline button with url instead of callback_data. Clicking opens an external link in the browser or Mini App if web_app is used.

**Is it possible to change buttons after sending? ** Yes, via edit_message_reply_markup (keyboard only) or edit_message_text (text and keyboard). This is the key advantage of the inline buttons over the reply keyboard.

**What happens if the user loses the reply keyboard? ** Add a /menu command that sends a new message from reply_markup=main_reply_keyboard(). You can also add an inline “Show menu” button in the welcome message.


*Relevant to aiogram 3.x, grammY 1.x, Bot API 9.x. June 2026. *

$ cd ../ ← back to Telegram bots