Legan Studio
Все статьи
~ 16 мин чтения

UX/UI Telegram Mini Apps: лучшие практики

Как делать Mini App, который выглядит как часть Telegram: themeParams, MainButton, BackButton, навигация, формы, медиа и поведение клавиатуры.

  • Telegram
  • Mini Apps
  • UX

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 и возвращается в чат.
  • Фиксированный viewportheaderHeight и 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-shaking1–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 overscrolloverscroll-behavior: contain на скроллируемых контейнерах внутри Mini App, чтобы скролл списка не триггерил сворачивание Mini App.
  • Swipe back — на iOS работает по краю экрана и делает BackButton.click(). Синхронизируйте с роутером.
  • ЗакрытиеTelegram.WebApp.enableClosingConfirmation() если есть несохранённые изменения.

Состояния: skeleton, empty, error, loading

Эти состояния обязательно проектировать — не «потом, если останется время».

СостояниеЧто показывать
LoadingSkeleton/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-reactReact-хуки для 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 Performance90+
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, планшет.