npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@rustigram/tma-solid

v0.10.0

Published

Solid.js reactive bindings and components for Telegram Mini Apps

Readme

@rustigram/tma-solid

Solid.js reactive bindings for Telegram Mini Apps. Built on @rustigram/tma-core — provides TmaProvider, fine-grained signals, declarative UI components, and hooks for storage, sensors, and biometrics.

pnpm add @rustigram/tma-solid @rustigram/tma-core solid-js zod

Setup (SolidStart v2)

1. src/entry-server.tsx — inject CDN script:

import { StartServer, createHandler } from "@solidjs/start/server";

export default createHandler(() => (
  <StartServer
    document={({ assets, children, scripts }) => (
      <html lang="en">
        <head>
          {/* Must load synchronously before Solid mounts */}
          <script src="https://telegram.org/js/telegram-web-app.js" />
          {assets}
        </head>
        <body>
          {children}
          {scripts}
        </body>
      </html>
    )}
  />
));

2. src/app.tsx — wrap with TmaProvider:

import { Router } from "@solidjs/router";
import { FileRoutes } from "@solidjs/start/router";
import { Suspense } from "solid-js";
import { TmaProvider } from "@rustigram/tma-solid";

export default function App() {
  return (
    <Router
      root={(props) => (
        <TmaProvider fallback={<div>Open in Telegram</div>}>
          <Suspense>{props.children}</Suspense>
        </TmaProvider>
      )}
    >
      <FileRoutes />
    </Router>
  );
}

3. vite.config.ts:

import { defineConfig } from "vite";
import { solidStart } from "@solidjs/start/vite";

export default defineConfig({
  plugins: [solidStart({ ssr: false })],
});

TmaProvider

<TmaProvider
  options={{ skipReady: false }} // BridgeOptions — optional
  fallback={<NotInTelegram />} // shown when window.Telegram.WebApp is absent
>
  {props.children}
</TmaProvider>

Injects all --tg-theme-* and --tg-safe-area-inset-* CSS variables automatically. Calls webApp.ready() on mount and appState.destroy() on unmount.

useTma()

const { bridge, appState } = useTma();
// bridge.webApp — raw TelegramWebApp
// bridge.launchContext — { version, platform, colorScheme, themeParams, initDataUnsafe }
// bridge.isVersionAtLeast("8.0")

Signals

import {
  useColorScheme, // Accessor<"light" | "dark">
  useThemeParams, // Accessor<ThemeParams>
  useIsActive, // Accessor<boolean>
  useIsExpanded, // Accessor<boolean>
  useIsFullscreen, // Accessor<boolean>
  useIsOrientationLocked, // Accessor<boolean>
  useViewportHeight, // Accessor<number>
  useViewportStableHeight, // Accessor<number>
  useSafeAreaInset, // Accessor<SafeAreaInset>
  useContentSafeAreaInset, // Accessor<ContentSafeAreaInset>
  useHeaderColor, // Accessor<string>
  useBackgroundColor, // Accessor<string>
  useBottomBarColor, // Accessor<string>
  useInitData, // Accessor<WebAppInitData>
  useIsVersionAtLeast, // (version: string) => Accessor<boolean>
} from "@rustigram/tma-solid";

Theme

const { colorScheme, themeParams, isDark } = useTmaTheme();
const style = useSafeAreaStyle(); // Accessor<JSX.CSSProperties> — for fullscreen layouts

CSS variables are injected automatically — use them directly:

background: var(--tg-theme-bg-color);
color: var(--tg-theme-text-color);

Components

Side-effect-only components — render null, interact with the Telegram WebApp:

import { BackButton, MainButton, SecondaryButton, SettingsButton } from "@rustigram/tma-solid";

<BackButton onBack={() => navigate(-1)} />

<MainButton
  text="Pay"
  loading={isPending()}
  disabled={false}
  onClick={handlePay}
/>

<SecondaryButton text="Cancel" position="left" onClick={handleCancel} />  {/* Bot API 7.10+ */}

<SettingsButton onSettings={() => navigate("/settings")} />

Error Boundary

import { TmaErrorBoundary } from "@rustigram/tma-solid";

<TmaErrorBoundary fallback={(err) => <div>Error: {err.message}</div>}>
  <MyPage />
</TmaErrorBoundary>;

Version Gate

import { useVersionGate } from "@rustigram/tma-solid";

const gate = useVersionGate("8.0");
// gate.supported — Accessor<boolean>
// gate.Guard     — component that renders children only when version ≥ requirement
// gate.assert()  — throws TmaBridgeError if not supported

<gate.Guard fallback={<p>Requires Bot API 8.0+</p>}>
  <SensorDashboard />
</gate.Guard>;

Storage Hooks

const cloud = useCloudStorage(); // + useDeviceStorage(), useSecureStorage()

await cloud.setItem("key", "value");
const val = await cloud.getItem("key"); // string | null
// cloud.loading — Accessor<boolean>

Sensor Hooks

const acc = useAccelerometer(); // + useGyroscope(), useDeviceOrientation()
await acc.start({ refresh_rate: 100 });
// acc.data()      — Vector3D | null
// acc.isRunning() — boolean

const bm = useBiometric();
await bm.init();
const granted = await bm.requestAccess({ reason: "Verify" });
const { success, token } = await bm.authenticate({ reason: "Confirm" });
// bm.status() — BiometricStatus

const lm = useLocation();
await lm.init();
const data = await lm.getLocation(); // LocationData | null
// lm.status() — LocationManagerStatus

Testing

import { createTmaMock } from "@rustigram/tma-core/mock";
import { render } from "@solidjs/testing-library";
import { TmaProvider } from "@rustigram/tma-solid";

const mock = createTmaMock({ colorScheme: "dark" });

render(() => (
  <TmaProvider options={{ mockWebApp: mock.webApp, skipReady: true }}>
    <MyComponent />
  </TmaProvider>
));

mock.setState({ colorScheme: "light" });
mock.emit("themeChanged", undefined);