# Mini App Design Guideline This document defines the design rules for the Duty Teller Mini App (Next.js frontend in `webapp-next/`). It aligns with [Telegram’s official Mini App design guidelines](https://core.telegram.org/bots/webapps#designing-mini-apps) (Color Schemes and Design Guidelines) and codifies the current implementation (calendar, duty list, admin) as the reference for new screens and components. --- ## 1. Telegram design principles Telegram’s guidelines state: - **Mobile-first:** All elements must be responsive and designed for mobile first. - **Consistency:** Interactive elements should match the style, behaviour, and intent of existing Telegram UI components. - **Performance:** Animations should be smooth, ideally 60fps. - **Accessibility:** Inputs and images must have labels. - **Theme:** The app must use the dynamic theme-based colors provided by the API (Day/Night and custom themes). - **Safe areas:** The interface must respect the **safe area** and **content safe area** so content does not overlap system or Telegram UI, especially in fullscreen. - **Android:** On Android, use the extra User-Agent data (e.g. performance class) and reduce animations and effects on low-performance devices for smooth operation. **Color schemes:** Mini Apps receive the user’s current **theme** in real time. Use the `ThemeParams` object and the CSS variables Telegram exposes (e.g. `--tg-theme-bg-color`, `--tg-theme-text-color`) so the UI adapts when the user switches Day/Night or custom themes. --- ## 2. Theme and colors ### 2.1 Sources Theme is resolved in this order: 1. Hash parameters: `tgWebAppColorScheme`, `tgWebAppThemeParams` (parsed in the shared inline script from `webapp-next/src/lib/theme-bootstrap-script.ts`, used in layout and global-error). 2. At runtime: `Telegram.WebApp.colorScheme` and `Telegram.WebApp.themeParams` via **TelegramProvider** (theme sync is provider-owned in `ThemeSync` / `useTelegramTheme`), so every route (/, /admin, not-found, error) receives live theme updates. The inline script maps all Telegram theme keys to `--tg-theme-*` CSS variables on the document root. The provider sets `data-theme` (`light` / `dark`) and applies Mini App background/header colors. ### 2.2 Mapping (ThemeParams → app tokens) In `webapp-next/src/app/globals.css`, `:root` and `[data-theme="light"]` / `[data-theme="dark"]` map Telegram’s ThemeParams to internal design tokens: | Telegram ThemeParam (CSS var) | App token / usage | |--------------------------------------|-------------------| | `--tg-theme-bg-color` | `--bg` (background) | | `--tg-theme-secondary-bg-color` | `--surface` (cards, panels) | | `--tg-theme-text-color` | `--text` (primary text) | | `--tg-theme-hint-color`, `--tg-theme-subtitle-text-color` | `--muted` (secondary text) | | `--tg-theme-link-color` | `--accent` (links, secondary actions) | | `--tg-theme-header-bg-color` | `--header-bg` | | `--tg-theme-section-bg-color` | `--card` (sections) | | `--tg-theme-section-header-text-color` | `--section-header` | | `--tg-theme-section-separator-color` | `--border` | | `--tg-theme-button-color` | `--primary` | | `--tg-theme-button-text-color` | `--primary-foreground` | | `--tg-theme-destructive-text-color` | `--error` | | `--tg-theme-accent-text-color` | `--accent-text` | Tailwind/shadcn semantic tokens are wired to these: `--background` → `--bg`, `--foreground` → `--text`, `--primary`, `--secondary` → `--surface`, etc. ### 2.3 Domain colors Duty-specific semantics (fixed per theme, not from ThemeParams): - `--duty` — duty shift (e.g. green). - `--unavailable` — unavailable (e.g. amber). - `--vacation` — vacation (e.g. blue). - `--today` — “today” highlight; tied to `--tg-theme-accent-text-color` / `--tg-theme-link-color`. Use Tailwind classes such as `bg-duty`, `border-l-unavailable`, `text-today`, `bg-today`, etc. ### 2.4 Derived tokens (color-mix) Prefer these instead of ad-hoc color mixes: - `--surface-hover`, `--surface-hover-10` — hover states for surfaces. - `--surface-today-tint`, `--surface-muted-tint` — subtle tints. - `--today-hover`, `--today-border`, `--today-border-selected`, `--today-gradient-end` — today state. - `--muted-fade`, `--shadow-card`, etc. Defined in `globals.css`; use via `var(--surface-hover)` in classes (e.g. `hover:bg-[var(--surface-hover)]`). ### 2.5 Rule for new work Use **only** these tokens and Tailwind/shadcn aliases (`bg-background`, `text-muted`, `bg-surface`, `text-accent`, `border-l-duty`, `bg-today`, etc.). Do not hardcode hex or RGB in new components or screens. --- ## 3. Layout and safe areas ### 3.1 Width - **Token:** `--max-width-app: 420px` (in `@theme` in `globals.css`). - **Usage:** Page wrapper uses `max-w-[var(--max-width-app)]` (e.g. in `page.tsx`, `CalendarPage.tsx`, `admin/page.tsx`). Content is centred with `mx-auto`. ### 3.2 Height - **Viewport:** Prefer `min-h-[var(--tg-viewport-stable-height,100vh)]` for the main content area so the Mini App fills the visible height correctly when expanded/collapsed. Fallback `100vh` when not in Telegram. - **Body:** In `globals.css`, `body` already has `min-height: var(--tg-viewport-stable-height, 100vh)` and `background: var(--bg)`. ### 3.3 Safe area and content safe area - **CSS custom properties** (in `globals.css`): `--app-safe-top`, `--app-safe-bottom`, `--app-safe-left`, `--app-safe-right` use Telegram viewport content-safe-area insets with `env(safe-area-inset-*)` fallbacks. Use these for sticky positioning and padding so layout works on notched and landscape devices. - **Class `.content-safe`**: Applies padding on all four sides using the above tokens so content does not sit under Telegram header, bottom bar, or side chrome (Bot API 8.0+). Use `.content-safe` on the **root container of each page** and on full-screen fallback screens (not-found, error, access denied). - **Sticky headers:** Use `top-[var(--app-safe-top)]` (not `top-0`) for sticky elements (e.g. calendar header, admin header) so they sit below the Telegram UI instead of overlapping it. - Lists that extend to the bottom should also account for bottom inset (e.g. `padding-bottom: var(--tg-viewport-content-safe-area-inset-bottom, 12px)` in `.container-app`). Official terminology (Telegram docs) uses `safeAreaInset` and `contentSafeAreaInset` plus events `safeAreaChanged` and `contentSafeAreaChanged`. In our code, these values are exposed through SDK CSS bindings and consumed via app aliases (`--app-safe-*`). When updating safe-area code, preserve this mapping and avoid mixing raw `env(...)` and Telegram content-safe insets within the same component. ### 3.4 Sheets and modals Bottom sheets and modals that sit at the bottom of the screen must add safe area to their padding, e.g.: `pb-[calc(24px+env(safe-area-inset-bottom,0px))]` See `webapp-next/src/components/day-detail/DayDetail.tsx` for the Sheet content. --- ## 4. Typography and spacing ### 4.1 Font - **Family:** `system-ui, -apple-system, sans-serif` (set in `globals.css` and Tailwind theme). ### 4.2 Patterns from the calendar and duty list | Element | Classes / tokens | |--------|-------------------| | Month title | `text-[1.1rem]` / `sm:text-[1.25rem]`, `font-semibold` | | Year (above month) | `text-xs`, `text-muted` | | Nav buttons (prev/next month) | `size-10`, `rounded-[10px]` | | Calendar day cell | `text-[0.85rem]`, `rounded-lg`, `p-1` | | Duty timeline card | `px-2.5 py-2`, `rounded-lg` | ### 4.3 Page and block spacing - Page container: `px-3 pb-6` in addition to `.content-safe`. - Between sections: `mb-3`, `mb-4` as appropriate. - Grids: `gap-1` for tight layouts (e.g. calendar grid), larger gaps where needed. --- ## 5. Component patterns ### 5.1 Buttons - **Primary:** Use the default Button variant: `bg-primary text-primary-foreground` (from `webapp-next/src/components/ui/button.tsx`). - **Secondary icon buttons** (e.g. calendar nav): `bg-surface text-accent hover:bg-[var(--surface-hover)] focus-visible:outline-accent active:scale-95` with `size-10` and `rounded-[10px]`. - Keep focus visible (e.g. `focus-visible:outline-accent` or ring); do not remove outline without a visible replacement. ### 5.2 Cards - **Background:** `bg-surface` or `bg-card` (both resolve to theme tokens). - **Borders:** `border`, `--border` (section separator color). - **Emphasis:** `var(--shadow-card)` for highlighted cards (e.g. current duty). - **Left stripe by type:** `border-l-[3px]` with: - `border-l-duty`, `border-l-unavailable`, `border-l-vacation` for event types; - `border-l-today` for “current duty” (see `.border-l-today` in `globals.css`). ### 5.3 Calendar grid - **Structure:** 7 columns × 6 rows; use `role="grid"` on the container and `role="gridcell"` on each cell. - **Layout:** `min-h-[var(--calendar-grid-min-height)]`, cells `aspect-square` with `min-h-8`, `rounded-lg`, `gap-1`. - **Today:** `bg-today text-[var(--bg)]`; hover `hover:bg-[var(--today-hover)]`. - **Other month:** `opacity-40`, `pointer-events-none`, `bg-[var(--surface-muted-tint)]`. ### 5.4 Timeline list (duties) - **Dates:** Horizontal line and vertical tick from shared CSS in `globals.css`: `.duty-timeline-date`, `.duty-timeline-date--today` (with `::before` / `::after`). - **Cards:** Same card rules as above; `border-l-[3px]` + type class; optional flip card for contacts (see `DutyTimelineCard.tsx`). --- ## 6. Motion and performance ### 6.1 Timing - **Tokens:** `--transition-fast: 0.15s`, `--transition-normal: 0.25s`, `--ease-out: cubic-bezier(0.32, 0.72, 0, 1)`. - Use these for transitions and short animations so behaviour is consistent and predictable. ### 6.2 Reduced motion - **Rule:** `@media (prefers-reduced-motion: reduce)` in `globals.css` shortens animation and transition durations globally. New animations should remain optional or short so they degrade gracefully when reduced. ### 6.3 Android low-performance devices - **Detection:** `webapp-next/src/lib/telegram-android-perf.ts` reads Telegram’s User-Agent and sets `data-perf="low"` on the document root when the device performance class is LOW. - **CSS:** `[data-perf="low"] *` in `globals.css` minimizes animation/transition duration. Avoid adding heavy or long animations without considering this; prefer simple or no animation on low-end devices. --- ## 7. Accessibility - **Focus:** Use `focus-visible:outline-accent` (or equivalent ring) on interactive elements; do not remove focus outline without a visible alternative. - **Calendar:** Use `role="grid"` and `role="gridcell"`, `aria-label` on nav buttons (e.g. “Previous month”), and a composite `aria-label` on each day cell (date + event types). See `webapp-next/src/components/calendar/CalendarDay.tsx`. - **Images and inputs:** Always provide labels (per Telegram’s guidelines and WCAG). --- ## 8. Telegram integration - **Ready gate:** `callMiniAppReadyOnce()` (in `lib/telegram-ready.ts`) is invoked by the layout’s `ReadyGate` when `appContentReady` becomes true. Any route (/, /admin, not-found, in-app error) that sets `appContentReady` will trigger it so Telegram hides its loader; no route-specific logic is required. - **Header and background:** On init (layout script and provider’s theme sync), call: - `setBackgroundColor('bg_color')` - `setHeaderColor('bg_color')` - `setBottomBarColor('bottom_bar_bg_color')` when available (Bot API 7.10+). - **Surface contrast:** When `--surface` equals `--bg` (e.g. some iOS OLED themes), `fixSurfaceContrast()` in `use-telegram-theme.ts` adjusts `--surface` using ThemeParams or a light color-mix so cards and panels remain visible. ### 8.1 Native control policy - Use platform wrappers in `src/hooks/telegram/` rather than direct SDK calls in feature components. - **BackButton:** preferred for route-level back navigation in Telegram context. - **SettingsButton:** use for route actions like opening `/admin` from calendar. - **Main/Secondary button:** optional; use only if action must align with Telegram bottom action affordance (do not duplicate with conflicting in-app primary CTA). - **Haptics:** trigger only on meaningful user actions (submit, confirm, close). ### 8.2 Swipe and closing policy - Keep vertical swipes enabled by default (`enableVerticalSwipes` behavior). - Disable vertical swipes only on screens with explicit gesture conflict and document the reason in code review. - Enable closing confirmation only for stateful flows where accidental close can lose user intent (e.g. reassignment flow in admin sheet). ### 8.3 Fullscreen/newer APIs policy - Fullscreen APIs (`requestFullscreen`, `exitFullscreen`) are currently optional and out of scope unless a feature explicitly requires immersive mode. - If fullscreen is introduced, review safe area/content safe area and verify `safeAreaChanged`, `contentSafeAreaChanged`, and `fullscreenChanged` handling. --- ## 9. Checklist for new screens and components Use this for review when adding or changing UI: - [ ] Use only design tokens from `globals.css` and Tailwind/shadcn aliases; no hardcoded colours. - [ ] Page wrapper has `.content-safe`, `max-w-[var(--max-width-app)]`, and appropriate min-height (viewport-stable-height or `min-h-screen` with fallback). - [ ] Buttons and cards follow the patterns above (variants, surfaces, border-l by type). - [ ] Safe area is respected for bottom padding and for sheets/modals. - [ ] Interactive elements and grids/lists have appropriate `aria-label`s and roles. - [ ] New animations respect `prefers-reduced-motion` and `data-perf="low"` (short or minimal on low-end Android). - [ ] User-facing strings and `aria-label`/`sr-only` text are localized via i18n (no hardcoded English in shared UI). - [ ] Telegram controls are connected through platform hooks (`src/hooks/telegram/*`) instead of direct SDK calls. - [ ] Vertical swipe and closing confirmation behavior follows the policy above. ## 10. Verification matrix (Mini App) At minimum verify: - Telegram light + dark themes. - iOS and Android safe area/content-safe-area behavior (portrait + landscape). - Android low-performance behavior (`data-perf="low"`). - Deep link current duty (`startParam=duty`). - Direct `/admin` open and reassignment flow. - Access denied, not-found, and error boundary screens. - Calendar swipe navigation with sticky header and native Telegram controls.