Mini App — это веб-приложение внутри Telegram, и пользователь подсознательно ожидает, что оно будет ощущаться как часть мессенджера. Если сделать «обычный сайт в WebView», получится чужеродно: своя цветовая палитра спорит с темой клиента, кастомная кнопка «назад» дублирует системную, верхний хедер съедает половину экрана. Конверсия в таких Mini App на 30–50% ниже, чем в тех, которые соблюдают платформенные конвенции.
Разберём практики, которые делают Mini App интегрированным с интерфейсом Telegram — от темы и нативных кнопок до жестов, форм и производительности. Цель — чтобы пользователь не чувствовал перехода из чата в «другое приложение», а воспринимал Mini App как продолжение разговора с ботом.
Чем Mini App отличается от обычного веба
Веб-приложение в браузере и Mini App внутри Telegram — это разные продукты, даже если технологически вы используете один и тот же Next.js или Vite. Различия:
- Запуск из чата — пользователь приходит из контекста разговора с ботом, у него уже есть ожидание задачи. Не нужен онбординг «что это вообще», нужно сразу решение.
- Быстрая загрузка обязательна — нет адресной строки, нет вкладок, нет «подождать пока загрузится». Если первый экран не появился за 1.5 секунды, пользователь закрывает Mini App и возвращается в чат.
- Фиксированный viewport —
headerHeightиviewportHeightзадаются клиентом Telegram, не браузером. Меняются при появлении клавиатуры и при свайпе вниз для сворачивания. - Своя тема — пользователь видит интерфейс в выбранной им теме клиента (светлая, тёмная, кастомная). Mini App обязан её уважать.
- Нет URL-строки — нельзя «скопировать ссылку», нельзя «открыть в новой вкладке», нельзя «поделиться текущим экраном». Навигация — внутренняя.
Из этих ограничений рождаются дизайн-правила: минимальный chrome, нативные кнопки клиента, тема из themeParams, лёгкий бандл.
Telegram WebApp API: themeParams
Telegram передаёт в Mini App объект themeParams — палитру текущей темы пользователя. Используйте эти значения через CSS-переменные:
| Параметр | Назначение |
|---|---|
bg_color | Основной фон |
secondary_bg_color | Вторичный фон (карточки, секции) |
text_color | Основной текст |
hint_color | Подсказки, второстепенный текст |
link_color | Ссылки |
button_color | Цвет основной кнопки |
button_text_color | Текст на основной кнопке |
accent_text_color | Акцентный текст |
destructive_text_color | Красный для деструктивных действий |
header_bg_color | Фон системного хедера |
Базовое подключение через CSS-переменные выглядит так:
:root {
--tg-bg: var(--tg-theme-bg-color, #ffffff);
--tg-text: var(--tg-theme-text-color, #000000);
--tg-hint: var(--tg-theme-hint-color, #999999);
--tg-link: var(--tg-theme-link-color, #2481cc);
--tg-button: var(--tg-theme-button-color, #2481cc);
--tg-button-text: var(--tg-theme-button-text-color, #ffffff);
--tg-secondary-bg: var(--tg-theme-secondary-bg-color, #f4f4f5);
}
body {
background: var(--tg-bg);
color: var(--tg-text);
}
Telegram сам инжектит переменные --tg-theme-* в documentElement, так что fallback-значения нужны только на случай открытия страницы вне клиента (для отладки).
SDK кидает событие themeChanged при переключении темы пользователем — подпишитесь, чтобы Mini App обновлялся на лету без перезагрузки.
colorScheme и адаптация под нативный вид
Помимо themeParams, доступно поле colorScheme: "light" | "dark". Это полезно, когда нужно переключить иконки, иллюстрации или логотипы под тему. Не пытайтесь восстановить тёмную/светлую тему по фону — используйте этот флаг напрямую.
Главное правило адаптации: не приносите свой брендинг в нативные элементы. Кнопка «Купить» с фирменным градиентом выглядит дёшево рядом с системным хедером Telegram. Используйте MainButton — она автоматически окрасится в button_color пользовательской темы.
BackButton: синхронизация с навигацией
BackButton — стрелка назад в верхнем баре Telegram. Включается через Telegram.WebApp.BackButton.show(), скрывается через hide(). Без неё пользователь не может вернуться в многоуровневой навигации — обычной браузерной кнопки в Mini App нет.
Правила:
- На корневом экране — скрывать.
- На любом дочернем — показывать.
- При нажатии —
pop()стека илиrouter.back(). - Синхронизировать с
history.pushState/routerфреймворка.
Минимальная синхронизация в Next.js App Router:
useEffect(() => {
const tg = window.Telegram?.WebApp;
if (!tg) return;
const isHome = pathname === "/";
if (isHome) tg.BackButton.hide();
else tg.BackButton.show();
const onBack = () => router.back();
tg.BackButton.onClick(onBack);
return () => tg.BackButton.offClick(onBack);
}, [pathname, router]);
MainButton: основное действие экрана
MainButton — нативная кнопка снизу экрана. Используйте её для главного действия страницы: «Оплатить», «Подтвердить», «Далее». Преимущества:
- появляется поверх клавиатуры;
- не мешает скроллу контента;
- стандартный вид, привычный пользователю;
- автоматически окрашивается в цвет темы.
Базовое управление:
const tg = window.Telegram.WebApp;
tg.MainButton.setText("Оплатить 1 990 ₽");
tg.MainButton.show();
tg.MainButton.enable();
tg.MainButton.onClick(handlePay);
// при загрузке
tg.MainButton.showProgress(false);
// при ошибке валидации
tg.MainButton.disable();
| Метод | Когда применять |
|---|---|
show() / hide() | Появление/исчезновение по экрану |
enable() / disable() | По валидности формы |
showProgress(leaveActive) | Во время асинхронного запроса |
hideProgress() | После ответа |
setText(text) | Динамический текст с суммой/счётчиком |
setParams({ color, text_color }) | Деструктивные действия (red) |
Антипаттерн — две главные цели на одном экране, одна на MainButton, одна своя в верстке. Пользователь теряется. Если действий два, вторичное вешайте на SecondaryButton (TG ≥ 7.10).
HapticFeedback: тактильный отклик
Telegram даёт API для виброотклика. Применяйте на действиях, где пользователь ожидает физического подтверждения:
| Метод | Кейс |
|---|---|
impactOccurred("light") | Лёгкое нажатие, переключатель |
impactOccurred("medium") | Подтверждение действия |
impactOccurred("heavy") | Критическое подтверждение |
notificationOccurred("success") | Успешная отправка, оплата |
notificationOccurred("warning") | Валидация формы не прошла |
notificationOccurred("error") | Платёж отклонён |
selectionChanged() | Прокрутка выбора, переключение таба |
Не злоупотребляйте — частая вибрация раздражает. Не вешайте selectionChanged на каждое движение пальцем по списку.
Bottom Sheet vs Full Screen
Telegram 7.7+ поддерживает мини-режим — Mini App открывается «полу-шторкой» (bottom sheet), а не полноэкранно. Пользователь может потянуть её вниз и свернуть, оставив доступ к чату.
Когда какой режим уместен:
- Bottom sheet — короткие сценарии: подтверждение, выбор из списка, быстрая оплата. До 3 экранов, до 30 секунд.
- Full screen — каталог, личный кабинет, многоэкранные формы, медиа-просмотр.
Включить полноэкранный режим: tg.expand() в useEffect после монтирования. Не вызывайте безусловно — пользователь мог сознательно открыть в свернутом виде.
Анимации и motion
Mini App открывается на разных устройствах, в том числе на бюджетных Android с Snapdragon 4xx. Тяжёлая framer-motion с layout-анимациями там даёт 15 fps вместо 60 — UX рушится.
Правила:
- Используйте CSS-transition вместо JS-анимаций где возможно.
framer-motion— точечно, на 1–2 ключевых элементах.- Уважайте
prefers-reduced-motion— отключайте всю motion для пользователей, которые её отключили. - Целевой FPS — 60, на старых Android допустимо 45.
- Не анимируйте
width/height— толькоtransformиopacity.
Шрифты
Не подгружайте Inter, Roboto, SF Pro и прочее — это +50–100 КБ на каждое начертание плюс задержка рендера. Используйте системный шрифт Telegram через CSS:
body {
font-family: -apple-system, BlinkMacSystemFont, "SF Pro", "Segoe UI",
Roboto, "Helvetica Neue", Arial, sans-serif;
}
Системный шрифт уже есть на устройстве, FOUT не возникает, рендер мгновенный, и Mini App визуально не выпадает из клиента.
Иконки
Три варианта, каждый со своими bundle-trade-off:
| Подход | Размер | Когда подходит |
|---|---|---|
lucide-react (полный импорт) | 200+ КБ | Никогда в Mini App |
lucide-react через named imports + tree-shaking | 1–3 КБ на иконку | Дефолт |
| Свой SVG-спрайт | 5–20 КБ на 30 иконок | Кастомные иконки |
| Системные эмодзи | 0 КБ | Простые случаи: 💳 ✅ ⚠️ |
Эмодзи отлично работают в TG-контексте — это родной язык мессенджера.
Цветовая палитра
Уважайте тему пользователя. Проектируйте интерфейс сразу в двух режимах — светлом и тёмном — и проверяйте контраст в обоих. Используйте CSS-переменные из themeParams, не хардкодьте #5B8DFF или #229ED9.
Свои акцентные цвета допустимы только для иллюстраций и иконок состояний (success/warning/error). Кнопки, фоны, текст — из темы.
Touch targets
Минимальный размер интерактивной зоны:
- iOS HIG — 44×44 pt
- Material Design — 48×48 dp
- WCAG 2.2 (Target Size) — 24×24 CSS px минимум, 44×44 рекомендовано
Между соседними кнопками — минимум 8 px пустого пространства. Кнопки в строку шириной 320 px помещают максимум 3 элемента, а не 5.
Формы в Mini App
Формы — самая частая боль. Что улучшает UX:
- Минимум полей. Просите только то, что критично для следующего шага.
- Используйте подходящий
inputmodeиtype:telдля номера,emailдля почты,numericдля кодов. autocompleteатрибуты —name,tel,email,street-address,postal-code,cc-number.- Валидация по мере ввода, без modal-ошибок.
- Скролл к активному полю при появлении клавиатуры.
- Telegram MainButton как «отправить» — не делайте свою кнопку внизу.
Keyboard avoiding на iOS требует дополнительной работы:
useEffect(() => {
const onResize = () => {
const vh = window.visualViewport?.height ?? window.innerHeight;
document.documentElement.style.setProperty("--vvh", `${vh}px`);
};
window.visualViewport?.addEventListener("resize", onResize);
return () => window.visualViewport?.removeEventListener("resize", onResize);
}, []);
Затем использовать --vvh вместо 100vh для контейнеров формы.
Жесты
Telegram сам обрабатывает свайп вниз для сворачивания и pull-to-refresh. Не блокируйте их — не ставьте touch-action: none на корневой контейнер.
Что управляется явно:
- Prevent overscroll —
overscroll-behavior: containна скроллируемых контейнерах внутри Mini App, чтобы скролл списка не триггерил сворачивание Mini App. - Swipe back — на iOS работает по краю экрана и делает
BackButton.click(). Синхронизируйте с роутером. - Закрытие —
Telegram.WebApp.enableClosingConfirmation()если есть несохранённые изменения.
Состояния: skeleton, empty, error, loading
Эти состояния обязательно проектировать — не «потом, если останется время».
| Состояние | Что показывать |
|---|---|
| Loading | Skeleton/shimmer вместо спиннера — perceived performance выше |
| Empty (нет данных) | Иллюстрация + пояснение + CTA |
| Error (запрос упал) | Текст ошибки + кнопка «Повторить» |
| Offline | Баннер вверху, отключение MainButton |
| Success (после действия) | Подтверждение + Haptic success + следующий шаг |
Skeleton делайте формы, повторяющей будущий контент: карточки = карточки-плейсхолдеры, не один прямоугольник на весь экран.
Onboarding
Если Mini App требует объяснений — 1–3 экрана максимум, опция «Пропустить» с самого первого. Идеально — onboarding встроен в первое использование (пустое состояние с подсказкой), а не отдельной шторкой.
В большинстве случаев онбординг вообще не нужен: пользователь пришёл из бота с конкретной задачей.
Доступность
Минимум:
- Контраст текст/фон не ниже 4.5:1 (WCAG AA), для крупного текста — 3:1.
aria-labelна иконочных кнопках.- Видимый focus indicator (
outline), неoutline: none. - Семантические теги (
button,nav,main,section). - Поддержка скрин-ридера VoiceOver/TalkBack — проверять руками.
- Размер шрифта от 14 px, основной текст 16 px.
Тестирование на реальных устройствах
Симуляторы и DevTools не покажут реальное поведение клавиатуры, жестов, viewport. Минимальный набор:
- iPhone SE (375 px ширина) — узкий экран, проверка переноса
- iPhone 15 Pro Max — широкий, large text
- Android low-end (Redmi 9, бюджетные Samsung A) — производительность
- Android flagship (Pixel, Galaxy S) — нормальный baseline
- Планшет — Mini App открывается и здесь, проверьте растяжку
На каждом устройстве пройдите ключевой сценарий: запуск из чата, основной flow, оплата (если есть), закрытие.
Дизайн-системы и UI-киты
Готовые киты экономят недели:
| Решение | Что даёт |
|---|---|
@telegram-apps/telegram-ui | Официальный TG UI Kit, native look-and-feel |
@telegram-apps/sdk-react | React-хуки для WebApp API, типы |
tgui (community) | Альтернативный TG-style kit |
| shadcn/ui + Tailwind | Гибкая база, требует адаптации под TG-тему |
| Radix UI | Низкоуровневые примитивы, доступность из коробки |
Рекомендация: @telegram-apps/telegram-ui для типовых Mini App, shadcn + Tailwind для нестандартного дизайна.
Антипаттерны
Чего не делать:
- Дизайн «под iOS-приложение» — большие закруглённые карточки, segmented controls, blur-эффекты — выпадает из контекста TG.
- Большой кастомный хедер — у Mini App уже есть системный хедер с
BackButtonи закрытием. Свой хедер сверху съедает 50–60 px полезной площади. - Полноэкранный кастомный back — ломает swipe-back, конфликтует с
BackButton. - Своя кнопка снизу вместо MainButton — теряется нативный вид и появление поверх клавиатуры.
- Хардкод палитры — игнорирует тему пользователя, в тёмном режиме читается белым по серому.
- Тяжёлые web-fonts — задержка первого рендера, FOUT.
- Toast снизу — конфликтует с
MainButtonи системной полосой. - Modal поверх modal — на маленьком экране это ад.
Производительность
Целевые показатели:
| Метрика | Цель |
|---|---|
| Lighthouse Performance | 90+ |
| LCP (Largest Contentful Paint) | менее 2.5 с |
| INP (Interaction to Next Paint) | менее 200 мс |
| CLS (Cumulative Layout Shift) | менее 0.1 |
| JS bundle (initial) | менее 200 КБ gzip |
| TTFB | менее 300 мс |
Что помогает:
- SSR/SSG (Next.js, Astro, Remix) — отдавайте готовый HTML.
- Code splitting по роутам.
- Lazy-load изображений ниже первого экрана,
loading="lazy". - WebP/AVIF вместо PNG, фиксированные размеры через
aspect-ratio. - WebSocket вместо polling для реал-тайм-данных.
- CDN с edge-кэшем (Cloudflare, Vercel).
Каждые 100 КБ JS — это +1 секунда загрузки на 3G. Mini App, который грузится 4 секунды, теряет 60% пользователей до первого взаимодействия.
Закрытие и подтверждения
Если у пользователя есть несохранённые изменения, активируйте Telegram.WebApp.enableClosingConfirmation() — Telegram спросит подтверждение перед закрытием. После сохранения — disableClosingConfirmation().
Это особенно важно для длинных форм и многошаговых сценариев: пользователь смахнёт Mini App вниз случайно, а заполненные данные не пропадут.
Итого
Хороший Mini App ощущается как нативная часть Telegram: использует themeParams и colorScheme, нативные MainButton, BackButton, SecondaryButton, виброотклик и платёжные экраны. Навигация — стек или bottom nav, формы — минимальные с правильными inputmode и autocomplete, медиа — нативные плееры, шрифт — системный, иконки — лёгкие. Производительность: LCP менее 2.5 с, бандл менее 200 КБ gzip. Состояния loading/empty/error спроектированы заранее, доступность WCAG AA, тестирование на реальных устройствах от iPhone SE до бюджетного Android.
Чем меньше пользователь чувствует, что вышел из мессенджера, тем выше конверсия и удержание.
Частые вопросы
Как использовать тему Telegram в Mini App?
Telegram передаёт в Mini App объект themeParams — палитру текущей темы пользователя: bg_color и secondary_bg_color (фон); text_color и hint_color (текст и подсказки); button_color и button_text_color (основная кнопка); link_color и accent_text_color (ссылки и акценты); destructive_text_color (красный для деструктивных действий); header_bg_color (фон системного хедера). Используйте эти переменные через CSS-variables, не хардкодьте свою палитру. Telegram сам инжектит CSS-переменные с префиксом tg-theme в documentElement. У пользователя может быть тёмная тема, светлая, кастомная — Mini App должен подхватывать её автоматически. SDK кидает событие themeChanged при переключении темы, на него можно подписаться и обновлять интерфейс на лету без перезагрузки.
Какие нативные кнопки Telegram доступны в Mini App?
Три ключевые. MainButton — нативная кнопка снизу экрана для главного действия страницы: Оплатить, Подтвердить, Далее. Появляется поверх клавиатуры, не мешает скроллу, автоматически окрашивается в цвет темы пользователя. Поддерживает showProgress для индикации загрузки и setParams для деструктивных действий. BackButton — стрелка назад в верхнем баре Telegram, включается через BackButton.show. Без неё пользователь не может вернуться в многоуровневой навигации, потому что обычной браузерной кнопки в Mini App нет. Синхронизируйте её с роутером фреймворка. SecondaryButton (с TG 7.10) — вторая кнопка рядом с главной для альтернативного действия: Пропустить, В корзину. Эти три кнопки лучше любых кастомных по UX.
Как организовать навигацию в Telegram Mini App?
В Mini App нет URL-навигации в классическом смысле — нет адресной строки, нет вкладок, нельзя поделиться ссылкой на конкретный экран. Реалистичные паттерны. Стек экранов — push/pop, как в мобильных приложениях; BackButton делает pop. Bottom nav — таб-бар внизу для 3–5 разделов, хорошо подходит для каталогов и личных кабинетов. Drawer — выезжающее меню сбоку, реже уместно, потому что отнимает площадь. Если экранов больше 10, без stack-навигации не обойтись. Используйте history-state и синхронизируйте с BackButton, чтобы свайп назад по краю экрана работал предсказуемо на iOS. Скрывайте BackButton на корневом экране, показывайте на любом дочернем.
Как делать формы в Telegram Mini App?
Минимум полей — просите только то, что критично для следующего шага. Используйте подходящий type и inputmode: tel для номера, email для почты, numeric для кодов. Атрибуты autocomplete (name, tel, email, postal-code, cc-number) ускоряют заполнение. Валидация по мере ввода, без modal-ошибок. Большие кнопки выбора вместо длинных селектов. Telegram MainButton как Отправить — не делайте свою кнопку внизу. Keyboard avoiding на iOS требует работы с visualViewport: подпишитесь на resize и обновляйте CSS-переменную с реальной высотой viewport вместо 100vh. Скролл к активному полю при появлении клавиатуры обязателен. Включите enableClosingConfirmation, если есть несохранённые изменения, чтобы случайный свайп вниз не уничтожил данные.
Какие требования к производительности Mini App?
Mini App открывается поверх чата — пользователь ожидает мгновенной реакции. Целевые показатели: Lighthouse Performance 90+, LCP менее 2.5 с, INP менее 200 мс, CLS менее 0.1, JS bundle менее 200 КБ gzip, TTFB менее 300 мс. Что помогает. SSR/SSG (Next.js, Astro, Remix) — отдавайте готовый HTML, не ждите гидратации. Code splitting по роутам. Минимум сторонних шрифтов — используйте системный шрифт через font-family. Lazy-load изображений ниже первого экрана. WebP/AVIF вместо PNG с фиксированными aspect-ratio. WebSocket вместо polling для реал-тайм-данных. Каждые 100 КБ JS — это плюс секунда загрузки на 3G; Mini App, который грузится 4 секунды, теряет 60% пользователей до первого взаимодействия. Для иконок не подключайте lucide-react целиком — используйте named imports с tree-shaking или собственный SVG-спрайт.
Как использовать Haptic Feedback в Mini App?
Telegram даёт три семейства методов виброотклика. impactOccurred (light/medium/heavy) — для тактильного отклика на нажатия и подтверждения. notificationOccurred (success/warning/error) — для результатов асинхронных действий: успешная оплата, валидация формы не прошла, платёж отклонён. selectionChanged — для прокрутки выбора и переключения табов. Не злоупотребляйте — частая вибрация раздражает; не вешайте selectionChanged на каждое движение пальцем по списку. Хорошее правило: haptic только там, где пользователь физически ожидает подтверждения — главное действие экрана, успех/ошибка после ответа сервера, переключение важных состояний. На бюджетных Android haptic API может молчать, не делайте логику зависимой от него.
Какие антипаттерны UX в Telegram Mini App?
Дизайн под iOS-приложение — крупные закруглённые карточки, segmented controls, blur-эффекты — выпадает из TG-контекста. Большой кастомный хедер сверху — у Mini App уже есть системный хедер с BackButton, ваш съедает 50–60 px полезной площади. Полноэкранный кастомный back — ломает swipe-back на iOS и конфликтует с системным BackButton. Своя кнопка снизу вместо MainButton — теряется нативный вид и появление поверх клавиатуры. Хардкод палитры — игнорирует тему пользователя, в тёмном режиме читается белым по серому. Тяжёлые web-fonts с Google Fonts — задержка первого рендера и FOUT, используйте системный шрифт. Toast снизу — конфликтует с MainButton и системной полосой. Modal поверх modal — на маленьком экране это ад. Тестируйте на реальных устройствах: iPhone SE, iPhone 15 Pro Max, бюджетный Android, планшет.