telegram-mini-apps-react
npx skills add https://github.com/nailorsh/agents_utils --skill telegram-mini-apps-react
Agent 安装分布
Skill 文档
Telegram Mini Apps with React
This skill provides comprehensive guidance for building Telegram Mini Apps using React and the @tma.js/sdk-react package.
Overview
Telegram Mini Apps are web applications displayed inside Telegram’s WebView. They integrate with Telegram’s native UI components (Back Button, Main Button) and have access to user data, theme parameters, and platform-specific features.
Key concepts:
- Mini Apps are add-ons for Telegram Bots
- They run inside Telegram’s WebView
- They communicate with Telegram client via events and methods
- They have access to launch parameters, init data, and theme settings
Quick Start
1. Installation
# For React projects, install the React-specific package
pnpm i @tma.js/sdk-react
# DO NOT install both @tma.js/sdk and @tma.js/sdk-react - this causes bugs!
Important: The
@tma.js/sdk-reactpackage fully re-exports@tma.js/sdk, so you don’t need to install them separately.
2. Create New Project (Optional)
pnpm dlx @tma.js/create-mini-app@latest
# or
npx @tma.js/create-mini-app@latest
This CLI scaffolds a complete project with proper configuration.
Project Structure
A typical Telegram Mini App React project structure:
src/
âââ main.tsx # Entry point - SDK initialization
âââ init.ts # SDK configuration and component mounting
âââ mockEnv.ts # Development environment mocking
âââ App.tsx # Main React app with routing
âââ components/
â âââ Page.tsx # Page wrapper with back button handling
â âââ EnvUnsupported.tsx # Fallback for non-TG environments
âââ hooks/
â âââ useDeeplink.ts # Deep linking handler
âââ services/
âââ analytics.ts # Analytics with user data
Core Concepts
SDK Initialization
The SDK must be initialized before using any features. See references/init.md for detailed implementation.
import {
init as initSDK,
setDebug,
themeParams,
miniApp,
viewport,
backButton,
swipeBehavior,
initData
} from '@tma.js/sdk-react';
export async function init(options: {
debug: boolean;
eruda: boolean;
mockForMacOS: boolean;
}): Promise<void> {
// Enable debug mode for development
setDebug(options.debug);
// Initialize the SDK (REQUIRED before using any features)
initSDK();
// Mount components you'll use in the app
backButton.mount.ifAvailable();
initData.restore();
// Configure swipe behavior
if (swipeBehavior.isSupported()) {
swipeBehavior.mount();
swipeBehavior.disableVertical();
}
// Setup Mini App theming
if (miniApp.mount.isAvailable()) {
themeParams.mount();
miniApp.mount();
themeParams.bindCssVars(); // Binds theme to CSS variables
}
// Configure viewport
if (viewport.mount.isAvailable()) {
viewport.mount().then(() => {
viewport.bindCssVars();
viewport.requestFullscreen();
});
}
}
Entry Point (main.tsx)
import { StrictMode } from "react";
import ReactDOM from "react-dom/client";
import { retrieveLaunchParams } from '@tma.js/sdk-react';
import { init } from './init';
import App from "./App";
import { EnvUnsupported } from "./components/EnvUnsupported";
// Mock environment for local development
import './mockEnv';
const root = ReactDOM.createRoot(document.getElementById('root')!);
try {
const launchParams = retrieveLaunchParams();
const { tgWebAppPlatform: platform } = launchParams;
const debug = (launchParams.tgWebAppStartParam || '').includes('debug')
|| import.meta.env.DEV;
await init({
debug,
eruda: debug && ['ios', 'android'].includes(platform),
mockForMacOS: platform === 'macos',
}).then(() => {
root.render(
<StrictMode>
<App/>
</StrictMode>,
);
});
} catch (e) {
// Show fallback UI when not in Telegram
root.render(<EnvUnsupported/>);
}
Using Back Button
The Back Button is a native Telegram UI element that appears in the header.
import { useEffect } from 'react';
import { useNavigate, useLocation } from 'react-router-dom';
import { backButton, miniApp } from '@tma.js/sdk-react';
export function Page({ children, back = true }) {
const navigate = useNavigate();
const location = useLocation();
useEffect(() => {
if (back) {
backButton.show();
// onClick returns a cleanup function
return backButton.onClick(() => {
const isDeeplink = location.state?.fromDeeplink;
const isFirstPage = !window.history.state || window.history.state.idx === 0;
if (isDeeplink || isFirstPage) {
miniApp.close(); // Close the Mini App
} else {
navigate(-1); // Go back in history
}
});
}
backButton.hide();
}, [back, navigate, location]);
return <>{children}</>;
}
Using Signals (useSignal Hook)
Signals are reactive values that update automatically. Use useSignal to subscribe to them in React:
import { useEffect } from 'react';
import { backButton, useSignal } from '@tma.js/sdk-react';
function BackButtonStatus() {
const isVisible = useSignal(backButton.isVisible);
useEffect(() => {
console.log('Back button is', isVisible ? 'visible' : 'hidden');
}, [isVisible]);
return null;
}
Getting Init Data (User Info)
Init data contains user information and can be used for authentication:
import { initData } from '@tma.js/sdk-react';
function getUserId(): number | undefined {
try {
const user = initData.user();
return user?.id;
} catch (e) {
return undefined;
}
}
// Get start parameter (for deep linking)
const startParam = initData.startParam();
Launch Parameters
Launch parameters contain platform info, theme, and app data:
import { retrieveLaunchParams, useLaunchParams } from '@tma.js/sdk-react';
// In component
function Component() {
const launchParams = useLaunchParams();
// launchParams.tgWebAppPlatform - 'ios', 'android', 'macos', 'tdesktop', 'web', 'weba'
// launchParams.tgWebAppVersion - SDK version supported by client
// launchParams.tgWebAppData - init data
// launchParams.tgWebAppThemeParams - theme colors
// launchParams.tgWebAppStartParam - custom start parameter
}
// Outside component
const launchParams = retrieveLaunchParams();
Theming
Theme parameters are automatically provided by Telegram. Bind them to CSS variables:
import { themeParams, miniApp } from '@tma.js/sdk-react';
// During initialization
if (miniApp.mount.isAvailable()) {
themeParams.mount();
miniApp.mount();
themeParams.bindCssVars(); // Creates CSS variables like --tg-theme-bg-color
}
Available CSS variables after binding:
--tg-theme-bg-color--tg-theme-text-color--tg-theme-hint-color--tg-theme-link-color--tg-theme-button-color--tg-theme-button-text-color--tg-theme-secondary-bg-color--tg-theme-header-bg-color--tg-theme-accent-text-color--tg-theme-section-bg-color--tg-theme-section-header-text-color--tg-theme-subtitle-text-color--tg-theme-destructive-text-color
Viewport and Safe Areas
Handle viewport and safe areas for proper layout:
import { viewport } from '@tma.js/sdk-react';
if (viewport.mount.isAvailable()) {
viewport.mount().then(() => {
viewport.bindCssVars(); // Binds viewport dimensions to CSS
viewport.requestFullscreen(); // Request fullscreen mode
});
}
Available CSS variables:
/* Safe area insets */
padding-top: var(--tg-viewport-safe-area-inset-top, 0);
padding-bottom: var(--tg-viewport-safe-area-inset-bottom, 0);
/* Content safe area (for notch, etc.) */
padding-top: var(--tg-viewport-content-safe-area-inset-top, 0);
/* Viewport dimensions */
height: var(--tg-viewport-height);
width: var(--tg-viewport-width);
Usage in CSS:
.header {
padding-top: max(2rem, calc(var(--tg-viewport-content-safe-area-inset-top, 0) + var(--tg-viewport-safe-area-inset-top, 0)));
}
.footer {
padding-bottom: calc(1rem + var(--tg-viewport-safe-area-inset-bottom, 0));
}
Development Environment Mocking
For local development outside Telegram, mock the environment. See references/mock-env.md.
import { emitEvent, isTMA, mockTelegramEnv } from '@tma.js/sdk-react';
if (import.meta.env.DEV) {
if (!await isTMA('complete')) {
const themeParams = {
accent_text_color: '#6ab2f2',
bg_color: '#17212b',
button_color: '#5288c1',
button_text_color: '#ffffff',
destructive_text_color: '#ec3942',
header_bg_color: '#17212b',
hint_color: '#708499',
link_color: '#6ab3f3',
secondary_bg_color: '#232e3c',
section_bg_color: '#17212b',
section_header_text_color: '#6ab3f3',
subtitle_text_color: '#708499',
text_color: '#f5f5f5',
};
mockTelegramEnv({
onEvent(e) {
if (e.name === 'web_app_request_theme') {
return emitEvent('theme_changed', { theme_params: themeParams });
}
if (e.name === 'web_app_request_viewport') {
return emitEvent('viewport_changed', {
height: window.innerHeight,
width: window.innerWidth,
is_expanded: true,
is_state_stable: true,
});
}
if (e.name === 'web_app_request_safe_area') {
return emitEvent('safe_area_changed', { left: 0, top: 0, right: 0, bottom: 0 });
}
},
launchParams: new URLSearchParams([
['tgWebAppThemeParams', JSON.stringify(themeParams)],
['tgWebAppData', new URLSearchParams([
['auth_date', (Date.now() / 1000 | 0).toString()],
['hash', 'mock-hash'],
['signature', 'mock-signature'],
['user', JSON.stringify({ id: 1, first_name: 'Developer' })],
]).toString()],
['tgWebAppVersion', '8.4'],
['tgWebAppPlatform', 'tdesktop'],
]),
});
console.info('â ï¸ Running in mocked Telegram environment');
}
}
Deep Linking
Handle start parameters for deep linking. See references/deeplink.md.
import { useEffect, useRef } from "react";
import { useNavigate } from "react-router-dom";
import { initData } from "@tma.js/sdk-react";
export function useDeeplink() {
const navigate = useNavigate();
const processedRef = useRef(false);
useEffect(() => {
if (processedRef.current) return;
const startParam = initData.startParam();
if (!startParam) return;
processedRef.current = true;
try {
// startParam is base64url encoded
const base64 = startParam.replace(/-/g, '+').replace(/_/g, '/');
const decoded = atob(base64);
const params = new URLSearchParams(decoded);
const route = params.get('route');
if (route) {
navigate(route, { replace: true, state: { fromDeeplink: true } });
}
} catch (e) {
console.error("Failed to parse startParam:", e);
}
}, [navigate]);
}
Best Practices
1. Always Check Availability
Before using any method, check if it’s available:
import { backButton } from '@tma.js/sdk-react';
// Option 1: Check before calling
if (backButton.show.isAvailable()) {
backButton.show();
}
// Option 2: Call only if available (safer, no-op if unavailable)
backButton.show.ifAvailable();
// Option 3: Mount only if available
backButton.mount.ifAvailable();
2. Mount Components Before Use
Components must be mounted before their methods can be used:
// â Wrong - will throw error
backButton.show();
// â
Correct
backButton.mount();
backButton.show();
3. Handle macOS Bugs
Telegram for macOS has known issues:
if (platform === 'macos') {
mockTelegramEnv({
onEvent(event, next) {
if (event.name === 'web_app_request_theme') {
const tp = themeParams.state() || retrieveLaunchParams().tgWebAppThemeParams;
return emitEvent('theme_changed', { theme_params: tp });
}
if (event.name === 'web_app_request_safe_area') {
return emitEvent('safe_area_changed', { left: 0, top: 0, right: 0, bottom: 0 });
}
next();
},
});
}
4. Don’t Install Duplicate SDKs
Never install both @tma.js/sdk and @tma.js/sdk-react:
// â Wrong - causes bugs
{
"dependencies": {
"@tma.js/sdk": "^3.0.0",
"@tma.js/sdk-react": "^3.0.8"
}
}
// â
Correct - only the React package
{
"dependencies": {
"@tma.js/sdk-react": "^3.0.8"
}
}
5. Disable Swipe When Needed
Prevent accidental navigation:
if (swipeBehavior.isSupported()) {
swipeBehavior.mount();
swipeBehavior.disableVertical(); // Prevents swipe-to-close
}
Sending Init Data to Server
For authentication, send init data to your server:
import { retrieveRawInitData } from '@tma.js/sdk-react';
const initDataRaw = retrieveRawInitData();
fetch('https://api.example.com/auth', {
method: 'POST',
headers: {
Authorization: `tma ${initDataRaw}`,
},
});
Server-side validation:
- Use
@tma.js/init-data-nodefor Node.js - Validate the hash using your bot token
- Never trust init data without validation
Supported Platforms
Mini Apps work on:
- android – Telegram for Android
- ios – Telegram for iOS
- macos – Telegram for macOS (has some bugs)
- tdesktop – Telegram Desktop
- weba – Telegram Web A
- web – Telegram Web K
Common Issues
SDK not initialized error
Make sure to call init() before using any SDK features.
Component not mounted error
Mount the component before calling its methods:
backButton.mount();
backButton.show();
Method not available error
Check availability before calling:
if (backButton.show.isAvailable()) {
backButton.show();
}
App crashes outside Telegram
Use environment mocking during development and provide a fallback UI.
Additional Resources
- Official Documentation
- @tma.js/sdk-react Package
- @tma.js/sdk Package
- Platform Overview
- Init Data Validation
- Theming
See Also
- references/init.md – Full initialization example
- references/mock-env.md – Environment mocking
- references/deeplink.md – Deep linking implementation
- examples/page-component.tsx – Page wrapper with back button