Use when adding new error messages to React, or seeing "unknown error code" warnings.
npx skills add Cap-go/capacitor-skills --skill "konsta-ui"
Install specific skill from multi-skill repository
# 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.
# SKILL.md
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.
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
# React
bun add konsta
# Vue
bun add konsta
# Svelte
bun add konsta
# Required: Tailwind CSS
bun add -D tailwindcss postcss autoprefixer
bunx tailwindcss init -p
Tailwind Configuration
// 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)
// 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)
<!-- 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
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
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
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} />}
/>
{/* Range */}
<ListItem
title="Volume"
innerChildren={<Range value={50} />}
/>
</List>
);
}
Buttons
import { Button, Segmented, SegmentedButton } from 'konsta/react';
import { useState } from 'react';
function Buttons() {
const [active, setActive] = useState(0);
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>
{/* Colors */}
<Button colors={{ fillBg: 'bg-red-500', fillText: 'text-white' }}>
Custom Color
</Button>
{/* Disabled */}
<Button disabled>Disabled</Button>
{/* 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>
);
}
Cards
import { Card, Button } from 'konsta/react';
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>
);
}
Dialogs and Sheets
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>
</>
);
}
Tabbar Navigation
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 />}
{/* 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>
);
}
Theming
Theme Selection
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
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
// tailwind.config.js
const konstaConfig = require('konsta/config');
module.exports = konstaConfig({
theme: {
extend: {
colors: {
primary: {
DEFAULT: '#6366f1',
dark: '#4f46e5',
},
},
},
},
// Override Konsta's primary color
konpistaConfig: {
colors: {
primary: '#6366f1',
},
},
});
Component-Level Styling
// 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
import { App, Page } from 'konsta/react';
function MyApp() {
return (
<App
theme="ios"
safeAreas // Enable safe area handling
>
<Page>
{/* Content respects safe areas */}
</Page>
</App>
);
}
Capacitor Integration
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>
);
}
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 |
Choose Konsta when:
- You want Tailwind-first approach
- You need smaller bundle size
- You're using Svelte
- You want simpler setup
Choose Ionic when:
- You need comprehensive component library
- You want built-in routing
- You need more complex components
- You prefer all-in-one solution
Best Practices
Performance
// Lazy load heavy components
import { lazy, Suspense } from 'react';
const HeavyComponent = lazy(() => import('./HeavyComponent'));
function App() {
return (
<Suspense fallback={<div>Loading...</div>}>
<HeavyComponent />
</Suspense>
);
}
Accessibility
// 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
/>
// For icon-only buttons
<Button aria-label="Close menu">
<Icon name="close" />
</Button>
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
# Supported AI Coding Agents
This skill is compatible with the SKILL.md standard and works with all major AI coding agents:
Learn more about the SKILL.md standard and how to use these skills with your preferred AI coding agent.