← All posts

Designing for Telegram — the safe-area / theme / haptic cheat sheet

5 min readMiniapper team

Building inside Telegram looks like building for the web — but the box has different edges. Top notches eat your header. Bottom keyboards crush your footer. The user can flip themes mid-scroll. Here's the cheat sheet your Miniapper-generated app already follows, in case you want to hand-edit the output.

Safe areas

  • Use TG's `safeAreaInset` (top, bottom, left, right). Don't stop at iOS's `env(safe-area-inset-*)` — TG's value already includes phone notch + Telegram chrome.
  • Anchor primary buttons to `MainButton` instead of a custom footer — TG handles keyboard avoidance for you.
  • Test in Telegram Desktop too — different chrome offsets.

Theme variables

  • Read `themeParams` once at boot, then subscribe to `themeChanged`. Users *do* flip themes inside an active session.
  • Map TG vars → CSS custom properties (`--tg-bg`, `--tg-text`, `--tg-button-bg`, etc.) so your stylesheet just references them.
  • Don't hard-code colors. Even brand colors should be tinted variants of the theme bg in dark mode.

Haptic feedback

  • Use `light` for taps on selectable items, `medium` for primary CTAs, `heavy` for irreversible actions.
  • Use `notification.success/warning/error` exactly once per action — repeat haptic feels like nagging.
  • Skip haptic on Desktop — `WebApp.platform` tells you.

BackButton, MainButton

  • Drive both from a top-level navigation state — never bind handlers ad-hoc inside child components.
  • On `BackButton`, *always* fall back to closing the WebApp if you're at the root — users tap it to leave the app.
  • Loading states on `MainButton` go through `showProgress()` / `hideProgress()`. Don't rebuild your own spinner.

Keyboard handling

When the soft keyboard is open, `viewportStableHeight` is what you actually have. Anchor scrollable content to that, not `100vh` or `dvh`. Your modal sheets, especially, must scroll instead of getting clipped.

RTL / i18n

TG passes `language_code`. If it starts with ar/he/fa, flip your layout direction. Miniapper auto-applies `dir=rtl` for those locales — your CSS should use logical properties (`margin-inline-start`) instead of `margin-left`.

What Miniapper handles for you

All of the above is wired into our codegen output by default. The generated app reads safeArea, subscribes to theme/viewport, fires haptic on the right interactions, drives MainButton/BackButton from a single nav state, and supports 15 locales auto-detected at runtime. You only think about the cheat sheet if you fork the output and start hand-editing.

Want to ship your own mini app?Start free