-
Notifications
You must be signed in to change notification settings - Fork 1
Description
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 returnstrueif the toggle is on OR ifuseIsMobile()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 konstaAdd 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.tsxCard.tsxToggle.tsx(wraps Switch/Toggle)Dialog.tsxInput.tsxSelect.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.tsto just set Konsta theme
Acceptance Criteria
-
konstainstalled as dependency -
KonstaProviderwraps 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.cssandmaterial-theme.cssremoved - All existing tests pass
- Build succeeds
References
- Konsta UI docs: https://konstaui.com/react
- Kitchen Sink (live demo): https://konstaui.com/kitchen-sink/react/dist/index.html
- Platform themes issue: feat: iOS-inspired CSS theme for native feel on Apple devices #288, feat: Material You (Android) CSS theme for native feel on Android devices #291
- Capacitor app: iOS app: native client via Capacitor + MachineDirectAdapter #253
- Research: research: MeticulousHome repository analysis — findings & impact for milestones 2.2–2.4 #283
- Supersedes: PR feat: iOS and Material You platform themes (#288, #291) #321 (platform themes), PR fix(css): reorder platform theme imports after Tailwind #325 (CSS cascade fix)