Files
duty-teller/docs/miniapp-design.md
Nikolay Tatarinov 40e2b5adc4 feat: enhance theme handling and layout components for Telegram Mini App
- Updated theme resolution logic to utilize a shared inline script for consistent theme application across routes.
- Introduced `AppShell` and `ReadyGate` components to manage app readiness and theme synchronization, improving user experience.
- Enhanced `GlobalError` and `NotFound` pages with a unified full-screen layout for better accessibility and visual consistency.
- Refactored CSS to implement safe area insets for sticky headers and content safety, ensuring proper layout on various devices.
- Added unit tests for new functionality and improved existing tests for better coverage and reliability.
2026-03-06 16:48:24 +03:00

12 KiB
Raw Blame History

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 Telegrams official Mini App design guidelines (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

Telegrams 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 users 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 Telegrams 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).

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 Telegrams 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 Telegrams guidelines and WCAG).

8. Telegram integration

  • Ready gate: callMiniAppReadyOnce() (in lib/telegram-ready.ts) is invoked by the layouts 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 providers 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.

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-labels and roles.
  • New animations respect prefers-reduced-motion and data-perf="low" (short or minimal on low-end Android).