Skip to content

feat: adopt Konsta UI for native-feeling mobile/Capacitor themes #332

@hessius

Description

@hessius

Summary

Replace our custom platform theme CSS (ios-theme.css, material-theme.css) with Konsta UI v5 — a Tailwind CSS-based mobile UI component library with pixel-perfect iOS and Material Design themes. Use a hybrid approach: Konsta UI on mobile/Capacitor, shadcn/Radix on desktop/server.

Why Konsta UI

Criteria Konsta UI Custom CSS (current) Framework7 Ionic
Tailwind-native ✅ Built on it ✅ Alongside it ❌ Own CSS ❌ Own CSS
Incremental adoption ✅ Per-component N/A ❌ Full rewrite ❌ Full rewrite
Router opinions ✅ None N/A ❌ Own router ❌ Own router
iOS + Material ✅ Pixel-perfect ❌ Subtle/broken ✅ Excellent ✅ Excellent
Bundle impact ~4KB entry ~2KB ~200KB ~500KB
Dependency tailwind-merge (already have) None 6 packages 3+ packages

Architecture: Hybrid Rendering

Use our existing useIsMobile() hook to conditionally render Konsta (mobile) or shadcn (desktop):

// apps/web/src/components/adaptive/Button.tsx
import { useIsMobile } from "@/hooks/use-mobile"
import { Button as ShadcnButton } from "@/components/ui/button"
import { Button as KonstaButton } from "konsta/react"

export function Button({ children, onClick, disabled, variant, ...props }) {
  const isMobile = useIsMobile()
  
  if (isMobile) {
    return (
      <KonstaButton onClick={onClick} disabled={disabled} {...props}>
        {children}
      </KonstaButton>
    )
  }
  
  return (
    <ShadcnButton onClick={onClick} disabled={disabled} variant={variant} {...props}>
      {children}
    </ShadcnButton>
  )
}

App Root Setup

// In App.tsx or main.tsx
import { KonstaProvider } from "konsta/react"

function AppShell({ children }) {
  const isMobile = useIsMobile()
  const platform = detectPlatform() // existing usePlatformTheme logic
  const konstaTheme = platform === "ios" ? "ios" : "material"
  
  if (isMobile) {
    return (
      <KonstaProvider theme={konstaTheme} dark={isDark} touchRipple={platform !== "ios"}>
        {children}
      </KonstaProvider>
    )
  }
  return <>{children}</>
}

Settings Toggle for Testing

Add a "Use Konsta UI (mobile)" toggle in Settings so the adaptive layer can be forced on/off regardless of viewport:

Settings → Appearance
  Platform Theme:  [Auto ▼]  (iOS / Material / None)
  Use Konsta UI:   [Toggle]  (force Konsta components on any viewport)
  • Default behaviour: Konsta renders on mobile (useIsMobile()), shadcn on desktop — no toggle needed for end users.
  • Testing override: When the toggle is ON, Konsta components render even on desktop. This lets you visually verify iOS/Material theming from a desktop browser without needing a phone or resizing the window.
  • Stored in STORAGE_KEYS.USE_KONSTA_UI (localStorage), so it persists across reloads.
  • Implementation: The adaptive components check useKonstaOverride() hook which returns true if the toggle is on OR if useIsMobile() is true.
// apps/web/src/hooks/useKonstaOverride.ts
export function useKonstaOverride(): boolean {
  const isMobile = useIsMobile()
  const [forced] = useLocalStorage(STORAGE_KEYS.USE_KONSTA_UI, false)
  return isMobile || forced
}

This toggle should appear in the Appearance section of Settings alongside the existing Platform Theme selector. It is primarily a developer/testing tool but can remain visible to end users who prefer native-feeling components on tablet viewports.

Why Hybrid?

  • Desktop (server mode): shadcn/Radix provides excellent desktop UX with hover states, tooltips, keyboard navigation, complex dialogs. No benefit to platform themes on desktop browsers.
  • Mobile (PWA + Capacitor): Konsta UI provides native-feeling iOS/Material components with correct touch interactions, ripple effects, iOS press-down animations, proper mobile form controls.
  • No wasted work: Both paths share the same state management, hooks, services, and business logic. Only the leaf UI components differ.

Component Migration Map

Phase 1: High-Impact (Week 1)

shadcn Konsta Files Affected Notes
Button Button 47 Rounded, tonal, raised variants. Most impactful change.
Card Card 27 Outline, raised, with header/footer dividers
Switch Toggle 8 iOS green / Material teal automatic
Dialog Dialog 11 Native-feeling modal with DialogButton

Phase 2: Forms (Week 1-2)

shadcn Konsta Files Affected Notes
Input ListInput 18 iOS/Material styled text input
Label (built into ListInput) 19 Konsta ListInput includes label
Select ListInput type="select" 4 Native-feeling picker
Textarea ListInput type="textarea" 2 Multi-line variant
Checkbox Checkbox 2
Slider Range 3

Phase 3: Navigation & Layout (Week 2)

shadcn Konsta Files Affected Notes
Tabs Segmented 2 iOS segmented / Material tabs
Progress Progressbar 2
Sheet Sheet 1 Bottom sheet with drag
Dropdown Actions / Popover 1 Action sheet on iOS, popover on Material

Keep as shadcn (no Konsta equivalent needed)

  • Tooltip (5 files) — desktop-only interaction
  • Separator (3 files) — simple CSS, no platform difference
  • Scroll-area (1 file) — native scroll sufficient on mobile
  • Accordion (0 files) — not used
  • Carousel — complex component, keep as-is

Implementation Plan

Step 1: Install & Configure

cd apps/web && bun add konsta

Add to index.css:

@import "konsta/react/theme.css";

Step 2: Create Adaptive Component Layer

New directory: apps/web/src/components/adaptive/

Each file exports a component that delegates to Konsta (mobile) or shadcn (desktop):

  • Button.tsx
  • Card.tsx
  • Toggle.tsx (wraps Switch/Toggle)
  • Dialog.tsx
  • Input.tsx
  • Select.tsx

Step 3: KonstaProvider in App Shell

Wrap the app in KonstaProvider on mobile, with platform auto-detection from usePlatformTheme().

Step 4: Migrate Consumers

Replace from "@/components/ui/button"from "@/components/adaptive/Button" across files.

This can be done incrementally — start with StartView, SettingsView, DialInWizard (most mobile-critical views) and expand.

Step 5: Remove Custom Theme CSS

Once Konsta handles all mobile theming:

  • Delete apps/web/src/styles/ios-theme.css
  • Delete apps/web/src/styles/material-theme.css
  • Remove their imports from main.tsx
  • Simplify usePlatformTheme.ts to just set Konsta theme

Acceptance Criteria

  • konsta installed as dependency
  • KonstaProvider wraps app on mobile with auto iOS/Material detection
  • Adaptive Button, Card, Toggle, Dialog, Input, Select components created
  • At least StartView, SettingsView, DialInWizard use adaptive components
  • iOS theme visually matches native iOS styling on iPhone/Safari
  • Material theme visually matches Material You on Android/Chrome
  • Desktop rendering unchanged (shadcn components)
  • Dark mode works on both mobile and desktop paths
  • Custom ios-theme.css and material-theme.css removed
  • All existing tests pass
  • Build succeeds

References

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions