@appboxo/ui-kit
v0.3.2
Published
A themeable, host-agnostic React UI kit for Boxo and partner mini-apps.
Readme
@appboxo/ui-kit
A themeable, host-agnostic React UI kit for Boxo and partner mini-apps.
@appboxo/ui-kit is the design system distilled out of the Boxo mini-apps
monorepo: typography, layout primitives, form controls and surfaces — all
themeable through CSS custom properties.
It is intentionally host-agnostic: the kit doesn't know whether it runs inside a Telegram WebApp, a Boxo native shell, or a plain browser. Anything host-specific (haptic feedback, native share, …) is plugged in by the consuming app at startup.
Status
Currently at 0.2. API is stabilizing. We are dogfooding the kit by
white-labeling internal apps before opening it up to external teams.
Install
pnpm add @appboxo/ui-kit
# Peer dependencies you need in your app
pnpm add react react-dom react-i18next i18next \
@arco-design/mobile-react @arco-design/mobile-utils \
@tanstack/react-query
# Next.js apps additionally need:
pnpm add next next-i18nextQuick start
// _app.tsx (Next) or main.tsx (Vite)
import "@arco-design/mobile-react/esm/style";
// Kit's component-local stylesheets (Tip icon sizing, Card padding,
// Input borders, etc.). Bundled at build time; import once at entry.
import "@appboxo/ui-kit/styles.css";
// Brand theme. Pre-compiled CSS, no Less pipeline required.
import "@appboxo/ui-kit/themes/freedom/theme.css";
import {
PrimaryButton,
Card,
Body1,
Flex,
setHapticHandler,
} from "@appboxo/ui-kit";
// Optional: wire up host capabilities once at startup
setHapticHandler((style) => navigator.vibrate?.(style === "heavy" ? 20 : 10));
export default function Home() {
return (
<Flex vertical gap={16}>
<Card>
<Body1>Welcome</Body1>
</Card>
<PrimaryButton text="Continue" onClick={() => console.log("clicked")} />
</Flex>
);
}A full runnable example lives in examples/basic-app.
Providers (real apps)
The snippet above is enough for static screens, but a real app usually wants
React Query and a place to compose context providers. The kit ships
MultiProvider and QueryProvider for that:
import { MultiProvider, QueryProvider } from "@appboxo/ui-kit";
import { QueryClient } from "@tanstack/react-query";
const queryClient = new QueryClient();
const providers = [
<QueryProvider queryClient={queryClient} />,
// <YourAuthProvider />, <YourTrackingProvider />, ...
];
export default function App({ children }: { children: React.ReactNode }) {
return <MultiProvider providers={providers}>{children}</MultiProvider>;
}For a complete wiring (Arco ContextProvider, dark-mode resolution, status
bar colors, …) see
examples/pass-freedom/src/lib/with-providers.tsx.
Theming
Theming is done entirely through CSS custom properties. The kit ships 37 themes:
themes/default/— brand-neutral baseline. Declares every required token with safe defaults (iOS system-blue primary).themes/freedom/,themes/snoonu/,themes/telegram/, … — 36 partner brands carried over from the historicalboxo-miniapps/packages/esim-theme/viascripts/migrate-esim-theme.mjs. Each one extendsdefault/and re-declares only the tokens that differ.
Toggle between brands at runtime with the Brand dropdown in Storybook
or in examples/kitchen-sink (a single-page
gallery that renders every component under whichever brand you pick),
or import the one you want directly.
Two integration patterns, depending on how much you want to control:
// 1. "I am Freedom" -- use the bundled brand.
import "@appboxo/ui-kit/themes/freedom/theme.css";
// 2. "I am another brand, light overlay" -- recommended for partners.
// Pull in the neutral baseline, then override only what differs.
import "@appboxo/ui-kit/themes/default/theme.css";
import "@/styles/my-brand.css"; // declares e.g. `#root { --primary-6: #ff7a00; }`pnpm test:tokens validates that the kit's components only read tokens that
the default theme actually declares — useful as a regression check when you
add new components.
See themes/README.md for the full token contract,
dark-mode notes, and the "adding a new brand" recipe.
Host integration
Some components want host capabilities the kit can't provide on its own. Plug them in once at startup:
import {
setHapticHandler,
setShareFileHandler,
} from "@appboxo/ui-kit";
setHapticHandler((style) => myHost.haptic(style));
setShareFileHandler(async (url, name) => myHost.share({ url, name }));Defaults are no-ops, so the kit works without any wiring.
What's included
| Group | Components |
|---|---|
| Typography | LargeTitle, Title1–Title3, Headline, Body1, Body2, Callout, Footnote1, Footnote2, Caption1–Caption3 |
| Layout | Layout, ResponsiveLayout, Flex, Footer, LayoutLoading |
| Forms | Input, Textarea, Checkbox, Radio, SearchBar, PickerInput, DatePicker, TimePicker, DialCodeSelector |
| Surfaces | Card, Drawer, PopupSwiper, Tip, Toast, Placeholder |
| Display | Tabs, TouchCell, SummaryTable, Markdown, Copyable |
| Buttons | PrimaryButton, SecondaryButton, TertiaryButton, QuaternaryButton |
| Providers | MultiProvider, QueryProvider |
| Hooks | useDevice, useBreakpoint, useDesktopDetection, useSafeArea, useScrollTop, useDarkMode, … |
Storybook is deployed (Boxo-themed): https://ui-kit-ofey.vercel.app/?path=/docs/introduction--docs
To run it locally:
pnpm storybookExamples
examples/
├── basic-app/ # 70-line "hello world" -- absolute minimum kit consumer,
│ # via workspace `file:../..` (workspace-style).
├── with-npm-quickstart/ # Same shape as basic-app, but pulls the kit
│ # from the public npm registry instead of the
│ # workspace. Use this one to validate the
│ # *publish pipeline*, not the design.
├── freedom-theme/ # Paired example A: identical app rendered with the
│ # bundled `themes/freedom/theme.css`.
├── custom-theme/ # Paired example B: same App.tsx, swapped for the
│ # neutral `default/theme.css` + a tiny `my-brand.css`
│ # overlay (fictional Citrus brand). `diff -r` against
│ # `freedom-theme/` shows EXACTLY what BYO theming costs.
├── kitchen-sink/ # Single-page kit gallery: every component on one
│ # page with a brand toolbar (37 themes) + light/dark
│ # toggle. Faster than Storybook for "what does
│ # brand X look like across the whole UI?" reviews.
└── pass-freedom/ # A full real-world mini-app (forked from production
# pass-freedom). 11 pages, all backend / host calls
# replaced with mocks. Useful as a soak test for the kit.To compare bundled vs. BYO theme side-by-side (recommended for partner onboarding):
pnpm install && pnpm build # build the kit dist/
cd examples/freedom-theme && pnpm install && pnpm dev # http://localhost:5173
# in another terminal
cd examples/custom-theme && pnpm install && pnpm dev # http://localhost:5174
diff -r examples/freedom-theme examples/custom-theme # see the BYO touchpointsTo run the kitchen sink (recommended for design / brand review):
pnpm install && pnpm build
cd examples/kitchen-sink
pnpm install
pnpm dev # http://localhost:5173To run the full pass-freedom demo:
pnpm install && pnpm build
cd examples/pass-freedom
pnpm install
pnpm dev # http://localhost:3000Repository layout
src/ The kit source — components, hooks, theming entry points
themes/ Brand themes. `default/` is the brand-neutral baseline,
`freedom/` is the example brand built on top of it.
Each ships both .less (source) and .css (pre-compiled,
generated by `pnpm build:themes`). Copy `freedom/` to
scaffold a new brand.
scripts/ Build-time helpers: `build-themes.mjs` (.less → .css) and
`check-token-coverage.mjs` (theme contract test).
.storybook/ Storybook config
examples/ Runnable consumer apps (start with examples/basic-app)Development
pnpm install
pnpm storybook # local Storybook on :6008
pnpm build # build:themes → tsup → dist/
pnpm build:themes # compile themes/*/theme.less → theme.css
pnpm test:tokens # verify default theme covers every required token
pnpm typecheck # tsc --noEmitWhy this kit exists
Boxo runs more than forty white-labeled mini-apps. Most share the same fundamentals — typography, spacing, form patterns, eSIM-like flows — but diverge wildly on brand visuals.
The kit is the small, opinionated layer those apps agree on: a token system that any brand can override, a typography scale that survives the redesign cycle, and a handful of primitives (Flex, Card, Input, …) that are useful without being prescriptive.
It is not a replacement for Arco Mobile — it sits on top of it.
License
Internal — Boxo / Appboxo.
