konsta-ui ยท diff
git:20260501.ed3e92e to git:20261011.86834d4
89 added, 625 removed. Audit B to A.
---
name: konsta-ui
- description: Guide to using Konsta UI for pixel-perfect iOS and Material Design components in Capacitor apps. Works with React, Vue, and Svelte. Use this skill when users want native-looking UI without Ionic, or prefer a lighter framework.
+ description: Builds Capacitor app UI with Konsta UI v5 (Tailwind CSS v4-based iOS and Material components for React 19, Vue 3, Svelte 5) - CSS-first setup (`@import 'tailwindcss'; @import 'konsta/react/theme.css';`), `--color-brand-*` colors in `@theme`, `<App theme="ios|material|parent" safeAreas dark>`, `KonstaProvider` when mixing with Ionic, `ios:`/`material:` variants, `pt-safe`/`no-safe-areas` utilities, `k-md-vibrant`/`k-md-monochrome`, and migrating from Konsta v3/v4 `konstaConfig` in `tailwind.config.js`. Use when installing or upgrading Konsta, components render unstyled, theme or dark mode does not switch, or Navbar/Tabbar overlap system bars. Do not use for Ionic components (ionic-design), plain Tailwind setup without Konsta (tailwind-capacitor), or native safe-area/edge-to-edge config (safe-area-handling).
---
- # Konsta UI Design Guide
-
- Build pixel-perfect iOS and Material Design apps with Konsta UI.
-
- ## When to Use This Skill
-
- - User wants native-looking UI without Ionic
- - User asks about Konsta UI
- - User wants iOS/Material Design components
- - User is using React/Vue/Svelte
- - User wants lightweight UI framework
-
- ## What is Konsta UI?
-
- Konsta UI provides:
- - Pixel-perfect iOS and Material Design components
- - Works with React, Vue, and Svelte
- - Tailwind CSS integration
- - ~40 mobile-optimized components
- - Small bundle size (~30KB gzipped)
-
- ## Getting Started
-
- ### Installation
-
- ```bash
- # React
- npm install konsta
-
- # Vue
- npm install konsta
-
- # Svelte
- npm install konsta
-
- # Required: Tailwind CSS
- npm install -D tailwindcss postcss autoprefixer
- npx tailwindcss init -p
- ```
-
- ### Tailwind Configuration
-
- ```javascript
- // tailwind.config.js
- const konstaConfig = require('konsta/config');
-
- module.exports = konstaConfig({
- content: [
- './src/**/*.{js,ts,jsx,tsx,vue,svelte}',
- ],
- // Extend or override Konsta config
- theme: {
- extend: {},
- },
- });
- ```
-
- ### Setup (React)
-
- ```tsx
- // App.tsx
- import { App, Page, Navbar, Block } from 'konsta/react';
-
- export default function MyApp() {
- return (
- <App theme="ios"> {/* or theme="material" */}
- <Page>
- <Navbar title="My App" />
- <Block>
- <p>Hello Konsta UI!</p>
- </Block>
- </Page>
- </App>
- );
- }
- ```
-
- ### Setup (Vue)
-
- ```vue
- <!-- App.vue -->
- <template>
- <k-app theme="ios">
- <k-page>
- <k-navbar title="My App" />
- <k-block>
- <p>Hello Konsta UI!</p>
- </k-block>
- </k-page>
- </k-app>
- </template>
-
- <script setup>
- import { kApp, kPage, kNavbar, kBlock } from 'konsta/vue';
- </script>
- ```
-
- ## Core Components
-
- ### Page Structure
-
- ```tsx
- import {
- App,
- Page,
- Navbar,
- NavbarBackLink,
- Block,
- BlockTitle,
- } from 'konsta/react';
-
- function MyPage() {
- return (
- <App theme="ios">
- <Page>
- <Navbar
- title="Page Title"
- subtitle="Subtitle"
- left={<NavbarBackLink onClick={() => history.back()} />}
- />
-
- <BlockTitle>Section Title</BlockTitle>
- <Block strong inset>
- <p>Block content with rounded corners and padding.</p>
- </Block>
- </Page>
- </App>
- );
- }
- ```
-
- ### Lists
-
- ```tsx
- import {
- List,
- ListItem,
- ListInput,
- ListButton,
- } from 'konsta/react';
- import { ChevronRight } from 'framework7-icons/react';
-
- function MyList() {
- return (
- <>
- {/* Simple list */}
- <List>
- <ListItem title="Item 1" />
- <ListItem title="Item 2" />
- <ListItem title="Item 3" />
- </List>
-
- {/* List with details */}
- <List strong inset>
- <ListItem
- title="John Doe"
- subtitle="Designer"
- text="Additional info text"
- media={<img src="/avatar.jpg" className="w-10 h-10 rounded-full" />}
- link
- />
- <ListItem
- title="Settings"
- after="On"
- link
- />
- </List>
-
- {/* Form list */}
- <List strongIos insetIos>
- <ListInput
- label="Email"
- type="email"
- placeholder="Enter email"
- />
- <ListInput
- label="Password"
- type="password"
- placeholder="Enter password"
- />
- <ListButton>Login</ListButton>
- </List>
- </>
- );
- }
- ```
-
- ### Forms
-
- ```tsx
- import {
- List,
- ListInput,
- Toggle,
- Radio,
- Checkbox,
- Stepper,
- Range,
- } from 'konsta/react';
- import { useState } from 'react';
-
- function MyForm() {
- const [toggle, setToggle] = useState(false);
- const [gender, setGender] = useState('male');
-
- return (
- <List strongIos insetIos>
- {/* Text inputs */}
- <ListInput
- label="Name"
- type="text"
- placeholder="Your name"
- clearButton
- />
-
- <ListInput
- label="Email"
- type="email"
- placeholder="Email address"
- />
-
- <ListInput
- label="Bio"
- type="textarea"
- placeholder="About yourself"
- inputClassName="!h-20 resize-none"
- />
-
- {/* Toggle */}
- <ListItem
- title="Notifications"
- after={
- <Toggle
- checked={toggle}
- onChange={() => setToggle(!toggle)}
- />
- }
- />
-
- {/* Radio */}
- <ListItem
- title="Male"
- media={
- <Radio
- checked={gender === 'male'}
- onChange={() => setGender('male')}
- />
- }
- />
- <ListItem
- title="Female"
- media={
- <Radio
- checked={gender === 'female'}
- onChange={() => setGender('female')}
- />
- }
- />
-
- {/* Checkbox */}
- <ListItem
- title="Agree to terms"
- media={<Checkbox />}
- />
-
- {/* Stepper */}
- <ListItem
- title="Quantity"
- after={<Stepper value={1} min={1} max={10} />}
- />
+ # Konsta UI for Capacitor
- {/* Range */}
- <ListItem
- title="Volume"
- innerChildren={<Range value={50} />}
- />
- </List>
- );
- }
- ```
+ ## When to Use
- ### Buttons
+ TRIGGER when:
+ - Adding Konsta UI to a Capacitor app or choosing it over Ionic
+ - Upgrading Konsta v3/v4 (Tailwind v3 `konstaConfig`) to v5 (Tailwind v4)
+ - Components unstyled, colors ignored, `ios:`/`material:` classes do nothing
+ - Navbar / Tabbar under the status bar or gesture bar
+ - Theme should follow platform (`ios` on iPhone, `material` on Android)
- ```tsx
- import { Button, Segmented, SegmentedButton } from 'konsta/react';
- import { useState } from 'react';
+ Do not use:
+ - Ionic Framework components or theming: `ionic-design`
+ - Tailwind install/config without Konsta: `tailwind-capacitor`
+ - Android SystemBars / edge-to-edge native config: `safe-area-handling`
+ - Keyboard overlap in forms: `capacitor-keyboard`
- function Buttons() {
- const [active, setActive] = useState(0);
+ ## Versions (October 2026)
- return (
- <div className="space-y-4 p-4">
- {/* Button variants */}
- <Button>Default</Button>
- <Button large>Large</Button>
- <Button small>Small</Button>
- <Button rounded>Rounded</Button>
- <Button outline>Outline</Button>
- <Button clear>Clear</Button>
- <Button tonal>Tonal</Button>
+ | Konsta | Tailwind | Frameworks | Config style |
+ |--------|----------|------------|--------------|
+ | 5.x (`latest`, 5.5.0) | v4 | React 19 (`ref` as prop), Vue 3, Svelte 5 (runes, snippets) | CSS: `@import 'konsta/<fw>/theme.css'` + `@theme` |
+ | 4.x | v3 | adds Svelte 5 types | JS: `konstaConfig()` in `tailwind.config.js` |
+ | 3.x and below | v3 | | JS `konstaConfig()` |
- {/* Colors */}
- <Button colors={{ fillBg: 'bg-red-500', fillText: 'text-white' }}>
- Custom Color
- </Button>
+ Check first: `npm ls konsta tailwindcss react vue svelte`. Never mix v5 CSS setup with a `tailwind.config.js` `konstaConfig` wrapper.
- {/* Disabled */}
- <Button disabled>Disabled</Button>
+ ## Workflow
- {/* Segmented control */}
- <Segmented strong>
- <SegmentedButton active={active === 0} onClick={() => setActive(0)}>
- Tab 1
- </SegmentedButton>
- <SegmentedButton active={active === 1} onClick={() => setActive(1)}>
- Tab 2
- </SegmentedButton>
- <SegmentedButton active={active === 2} onClick={() => setActive(2)}>
- Tab 3
- </SegmentedButton>
- </Segmented>
- </div>
- );
- }
- ```
+ 1. **Inspect** versions, main CSS file, `tailwind.config.*`, `postcss.config.*`, `vite.config.*`, `index.html` viewport meta, whether Ionic is also present.
+ 2. **Install / upgrade** (below). For an existing Tailwind v3 project, migrate Tailwind first (`tailwind-capacitor`).
+ 3. **Wrap the app** in `App` (or `KonstaProvider` with Ionic/Framework7).
+ 4. **Build screens** with components: [references/components.md](references/components.md).
+ 5. **Verify** (below).
- ### Cards
+ Only load a reference when its topic is in play.
- ```tsx
- import { Card, Button } from 'konsta/react';
+ ## Setup (v5, React shown; Vue/Svelte use `konsta/vue/theme.css` / `konsta/svelte/theme.css`)
- function Cards() {
- return (
- <Card>
- <img
- src="/card-image.jpg"
- className="w-full h-48 object-cover"
- alt=""
- />
- <div className="p-4">
- <h3 className="font-bold text-lg">Card Title</h3>
- <p className="text-gray-500 mt-2">
- Card description text goes here. This is a standard
- card with image, title, and content.
- </p>
- <div className="flex gap-2 mt-4">
- <Button small>Action 1</Button>
- <Button small outline>Action 2</Button>
- </div>
- </div>
- </Card>
- );
- }
+ ```bash
+ npm i konsta tailwindcss @tailwindcss/vite
```
- ### Dialogs and Sheets
-
- ```tsx
- import {
- Dialog,
- DialogButton,
- Sheet,
- Popup,
- Button,
- Page,
- Navbar,
- Block,
- } from 'konsta/react';
- import { useState } from 'react';
-
- function Dialogs() {
- const [dialogOpen, setDialogOpen] = useState(false);
- const [sheetOpen, setSheetOpen] = useState(false);
- const [popupOpen, setPopupOpen] = useState(false);
-
- return (
- <>
- <Button onClick={() => setDialogOpen(true)}>Open Dialog</Button>
- <Button onClick={() => setSheetOpen(true)}>Open Sheet</Button>
- <Button onClick={() => setPopupOpen(true)}>Open Popup</Button>
-
- {/* Alert dialog */}
- <Dialog
- opened={dialogOpen}
- onBackdropClick={() => setDialogOpen(false)}
- title="Dialog Title"
- content="Dialog content goes here."
- buttons={
- <>
- <DialogButton onClick={() => setDialogOpen(false)}>
- Cancel
- </DialogButton>
- <DialogButton strong onClick={() => setDialogOpen(false)}>
- OK
- </DialogButton>
- </>
- }
- />
-
- {/* Bottom sheet */}
- <Sheet
- opened={sheetOpen}
- onBackdropClick={() => setSheetOpen(false)}
- >
- <div className="p-4">
- <h2 className="font-bold text-lg mb-4">Sheet Title</h2>
- <p>Sheet content</p>
- <Button onClick={() => setSheetOpen(false)} className="mt-4">
- Close
- </Button>
- </div>
- </Sheet>
-
- {/* Full page popup */}
- <Popup opened={popupOpen} onBackdropClick={() => setPopupOpen(false)}>
- <Page>
- <Navbar
- title="Popup"
- right={
- <Button clear onClick={() => setPopupOpen(false)}>
- Close
- </Button>
- }
- />
- <Block>Popup content</Block>
- </Page>
- </Popup>
- </>
- );
- }
+ ```ts
+ // vite.config.ts
+ import tailwindcss from '@tailwindcss/vite';
+ export default defineConfig({ plugins: [react(), tailwindcss()] });
```
- ### Tabbar Navigation
-
- ```tsx
- import { App, Page, Tabbar, TabbarLink, Icon } from 'konsta/react';
- import { Home, Search, Person } from 'framework7-icons/react';
- import { useState } from 'react';
-
- function TabsApp() {
- const [activeTab, setActiveTab] = useState('home');
-
- return (
- <App theme="ios">
- <Page>
- {/* Page content based on active tab */}
- {activeTab === 'home' && <HomeContent />}
- {activeTab === 'search' && <SearchContent />}
- {activeTab === 'profile' && <ProfileContent />}
+ ```css
+ /* src/index.css */
+ @import 'tailwindcss';
+ @import 'konsta/react/theme.css';
- {/* Tabbar */}
- <Tabbar labels className="left-0 bottom-0 fixed">
- <TabbarLink
- active={activeTab === 'home'}
- onClick={() => setActiveTab('home')}
- icon={<Home />}
- label="Home"
- />
- <TabbarLink
- active={activeTab === 'search'}
- onClick={() => setActiveTab('search')}
- icon={<Search />}
- label="Search"
- />
- <TabbarLink
- active={activeTab === 'profile'}
- onClick={() => setActiveTab('profile')}
- icon={<Person />}
- label="Profile"
- />
- </Tabbar>
- </Page>
- </App>
- );
+ @theme {
+ --color-brand-primary: #007aff; /* only --color-brand-* drive component colors */
+ --color-brand-red: #ff3b30; /* use with class k-color-brand-red */
}
```
- ## Theming
-
- ### Theme Selection
-
```tsx
- import { App } from 'konsta/react';
-
- // Auto-detect platform
- <App theme="parent">
-
- // Force iOS style
- <App theme="ios">
-
- // Force Material style
- <App theme="material">
- ```
-
- ### Dark Mode
-
- ```tsx
+ import { Capacitor } from '@capacitor/core';
import { App } from 'konsta/react';
- // Auto dark mode (follows system)
- <App dark>
-
- // Explicit dark mode
- <App dark={true}>
-
- // Explicit light mode
- <App dark={false}>
- ```
-
- ### Custom Colors with Tailwind
-
- ```javascript
- // tailwind.config.js
- const konstaConfig = require('konsta/config');
-
- module.exports = konstaConfig({
- theme: {
- extend: {
- colors: {
- primary: {
- DEFAULT: '#6366f1',
- dark: '#4f46e5',
- },
- },
- },
- },
- // Override Konsta's primary color
- konstaConfig: {
- colors: {
- primary: '#6366f1',
- },
- },
- });
- ```
-
- ### Component-Level Styling
-
- ```tsx
- // Override individual component colors
- <Button
- colors={{
- fillBg: 'bg-indigo-500',
- fillActiveBg: 'bg-indigo-600',
- fillText: 'text-white',
- }}
- >
- Custom Button
- </Button>
-
- <Toggle
- colors={{
- bgChecked: 'bg-green-500',
- }}
- />
- ```
-
- ## With Capacitor
-
- ### Safe Area Handling
-
- ```tsx
- import { App, Page } from 'konsta/react';
+ const theme = Capacitor.getPlatform() === 'android' ? 'material' : 'ios';
- function MyApp() {
+ export default function Root() {
return (
- <App
- theme="ios"
- safeAreas // Enable safe area handling
- >
- <Page>
- {/* Content respects safe areas */}
- </Page>
+ <App theme={theme} safeAreas>
+ {/* pages */}
</App>
);
}
```
- ### Capacitor Integration
-
- ```tsx
- import { App, Page, Button } from 'konsta/react';
- import { Capacitor } from '@capacitor/core';
-
- function MyApp() {
- const isNative = Capacitor.isNativePlatform();
-
- return (
- <App
- theme={Capacitor.getPlatform() === 'ios' ? 'ios' : 'material'}
- safeAreas={isNative}
- >
- <Page>
- <Button onClick={handleNativeAction}>
- Native Action
- </Button>
- </Page>
- </App>
- );
- }
+ ```html
+ <meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />
```
- ## Comparison: Konsta vs Ionic
-
- | Feature | Konsta UI | Ionic |
- |---------|-----------|-------|
- | Bundle Size | ~30KB | ~200KB |
- | Components | ~40 | ~100+ |
- | Tailwind Integration | Native | Possible |
- | Routing | External | Built-in |
- | Framework Support | React, Vue, Svelte | React, Vue, Angular |
- | Native Features | UI only | UI + Plugins |
+ `App` props: `theme` (`'ios' | 'material' | 'parent'`, default `material`), `safeAreas` (default `true`), `dark`, `iosHoverHighlight`, `materialTouchRipple` (global switches added in v5). Only one `App` per app.
- **Choose Konsta when:**
- - You want Tailwind-first approach
- - You need smaller bundle size
- - You're using Svelte
- - You want simpler setup
+ Material Roboto font: add it yourself (Google Fonts link or self-hosted for offline). iOS uses the system font.
- **Choose Ionic when:**
- - You need comprehensive component library
- - You want built-in routing
- - You need more complex components
- - You prefer all-in-one solution
+ ## Migrating v4 -> v5
- ## Best Practices
+ | v4 | v5 |
+ |----|----|
+ | `tailwind.config.js`: `module.exports = konstaConfig({...})` | Delete wrapper; `@import 'konsta/react/theme.css'` in CSS |
+ | `konstaConfig.colors.primary` | `@theme { --color-brand-primary: ... }` |
+ | Per-component ripple config | Prefer the global `App materialTouchRipple`; check component types in your installed version |
+ | `SegmentedButton strong rounded` | Removed; use Segmented theme-specific style props |
+ | `Link navbar/toolbar/tabbar` props | Removed; context-driven |
+ | React `forwardRef` usage / `innerRef` patterns | `ref` prop (React 19) |
+ | Svelte slots | `{#snippet ...}` |
- ### Performance
+ Then run the Tailwind v4 upgrade (`npx @tailwindcss/upgrade`) if Tailwind itself is still v3; see `tailwind-capacitor`.
- ```tsx
- // Lazy load heavy components
- import { lazy, Suspense } from 'react';
+ ## Traps
- const HeavyComponent = lazy(() => import('./HeavyComponent'));
+ - With Ionic in the same app, use `KonstaProvider` (not `App`) and pick one owner for safe areas (Ionic's `--ion-safe-area-*` or Konsta's `safe-areas`), not both.
+ - Dark mode in Konsta v5 is class-based (`@custom-variant dark (&:where(.dark, .dark *))` in its CSS): toggle `dark` on `<html>`, set `App dark`, and match native bars with `SystemBars.setStyle`.
+ - Konsta's own docs show `user-scalable=no, maximum-scale=1`; prefer allowing zoom for accessibility and fix iOS input zoom with 16px inputs instead.
+ - Modals/side panels that are not full-screen need `no-safe-areas` (or `no-safe-areas-top` etc.) to avoid extra padding.
+ - Konsta is UI only: no router, no page transitions. Pair with React Router / Vue Router / SvelteKit and handle Android back button with `@capacitor/app` `backButton`.
- function App() {
- return (
- <Suspense fallback={<div>Loading...</div>}>
- <HeavyComponent />
- </Suspense>
- );
- }
- ```
+ ## Verification
- ### Accessibility
+ 1. `npm run build && npx cap sync`, run on iOS and Android.
+ 2. Inspect `<html>`/root: `.k-ios` or `.k-material` class present; `ios:`/`material:` utilities apply.
+ 3. Navbar clears the Dynamic Island; Tabbar clears the home indicator and Android gesture bar (Android 15+ edge-to-edge).
+ 4. Toggle dark mode; components and status bar icons switch together.
+ 5. Leftover check: `grep -rn "konstaConfig\|konsta/config" .` returns nothing on v5.
- ```tsx
- // Konsta components are accessible by default
- // Add labels for screen readers
- <ListInput
- label="Email"
- type="email"
- placeholder="Enter email"
- // aria-label is automatically set from label
- />
+ ## Error Handling
- // For icon-only buttons
- <Button aria-label="Close menu">
- <Icon name="close" />
- </Button>
- ```
+ | Error / symptom | Cause | Fix |
+ |-----------------|-------|-----|
+ | `Cannot find module 'konsta/config'` | v5 removed JS config | Use CSS `@import 'konsta/react/theme.css'` |
+ | Components render unstyled | Theme CSS not imported, or Tailwind not scanning (no `@tailwindcss/vite` / `@tailwindcss/postcss`) | Import theme after `@import 'tailwindcss'`; add the Tailwind plugin |
+ | `[postcss] It looks like you're trying to use tailwindcss directly as a PostCSS plugin` | Tailwind v4 PostCSS plugin moved | Use `@tailwindcss/postcss` or `@tailwindcss/vite` |
+ | Brand color ignored | Variable not named `--color-brand-*`, or defined outside `@theme` | Rename inside `@theme` |
+ | `ios:` classes have no effect | `App`/`KonstaProvider` missing, so no `.k-ios` ancestor | Wrap app |
+ | React warning about `ref` / `forwardRef` after upgrade | React 18 with Konsta 5 | Upgrade to React 19 or stay on Konsta 4 |
+ | Double top padding | Konsta `safeAreas` + Ionic or custom `env()` padding | Keep one owner |
## Resources
- - Konsta UI Documentation: https://konstaui.com/
- - Konsta React Docs: https://konstaui.com/react
- - Konsta Vue Docs: https://konstaui.com/vue
- - Konsta Svelte Docs: https://konstaui.com/svelte
- - GitHub: https://github.com/konstaui/konsta
+ - Konsta UI: https://konstaui.com
+ - React installation: https://konstaui.com/react/installation
+ - Safe areas: https://konstaui.com/react/safe-areas
+ - Colors: https://konstaui.com/react/colors
+ - Changelog: https://github.com/konstaui/konsta/blob/master/CHANGELOG.md