npx claudepluginhub cap-go/capgo-skills --plugin capacitor-uiThis skill uses the workspace's default tool permissions.
Build beautiful, native-looking mobile apps with Ionic Framework and Capacitor.
Guides building pixel-perfect iOS and Material Design UIs in Capacitor apps using Konsta UI with React, Vue, Svelte, and Tailwind CSS.
Guides integrating web frameworks like Next.js, React, Vue, Angular, Svelte with Capacitor for mobile apps via static exports. Use when converting web apps to hybrid native mobile.
Provides MCP tools for AI-assisted Capacitor/Ionic mobile development including component APIs, plugin documentation, CLI commands, and automation.
Share bugs, ideas, or general feedback.
Build beautiful, native-looking mobile apps with Ionic Framework and Capacitor.
Ionic provides:
# For React
npx create-vite@latest my-app --template react-ts
cd my-app
npm install @ionic/react @ionic/react-router
# For Vue
npx create-vite@latest my-app --template vue-ts
cd my-app
npm install @ionic/vue @ionic/vue-router
# Add Capacitor
npm install @capacitor/core @capacitor/cli
npx cap init
// main.tsx
import React from 'react';
import { createRoot } from 'react-dom/client';
import { IonApp, setupIonicReact } from '@ionic/react';
import App from './App';
/* Core CSS required for Ionic components to work properly */
import '@ionic/react/css/core.css';
/* Basic CSS for apps built with Ionic */
import '@ionic/react/css/normalize.css';
import '@ionic/react/css/structure.css';
import '@ionic/react/css/typography.css';
/* Optional CSS utils */
import '@ionic/react/css/padding.css';
import '@ionic/react/css/float-elements.css';
import '@ionic/react/css/text-alignment.css';
import '@ionic/react/css/text-transformation.css';
import '@ionic/react/css/flex-utils.css';
import '@ionic/react/css/display.css';
/* Theme */
import './theme/variables.css';
setupIonicReact();
const root = createRoot(document.getElementById('root')!);
root.render(
<IonApp>
<App />
</IonApp>
);
import {
IonPage,
IonHeader,
IonToolbar,
IonTitle,
IonContent,
IonButtons,
IonBackButton,
} from '@ionic/react';
function MyPage() {
return (
<IonPage>
<IonHeader>
<IonToolbar>
<IonButtons slot="start">
<IonBackButton defaultHref="/home" />
</IonButtons>
<IonTitle>Page Title</IonTitle>
</IonToolbar>
</IonHeader>
<IonContent fullscreen>
{/* Large title for iOS */}
<IonHeader collapse="condense">
<IonToolbar>
<IonTitle size="large">Page Title</IonTitle>
</IonToolbar>
</IonHeader>
{/* Page content */}
<div className="ion-padding">
Your content here
</div>
</IonContent>
</IonPage>
);
}
import {
IonList,
IonItem,
IonLabel,
IonNote,
IonAvatar,
IonIcon,
IonItemSliding,
IonItemOptions,
IonItemOption,
} from '@ionic/react';
import { chevronForward, trash, archive } from 'ionicons/icons';
function ContactList() {
return (
<IonList>
{/* Simple item */}
<IonItem>
<IonLabel>Simple Item</IonLabel>
</IonItem>
{/* Item with detail */}
<IonItem detail button>
<IonLabel>
<h2>Item Title</h2>
<p>Item description text</p>
</IonLabel>
<IonNote slot="end">Note</IonNote>
</IonItem>
{/* Item with avatar */}
<IonItem>
<IonAvatar slot="start">
<img src="/avatar.jpg" alt="" />
</IonAvatar>
<IonLabel>
<h2>John Doe</h2>
<p>john@example.com</p>
</IonLabel>
</IonItem>
{/* Sliding item */}
<IonItemSliding>
<IonItem>
<IonLabel>Swipe me</IonLabel>
</IonItem>
<IonItemOptions side="end">
<IonItemOption color="danger">
<IonIcon slot="icon-only" icon={trash} />
</IonItemOption>
<IonItemOption>
<IonIcon slot="icon-only" icon={archive} />
</IonItemOption>
</IonItemOptions>
</IonItemSliding>
</IonList>
);
}
import {
IonInput,
IonTextarea,
IonSelect,
IonSelectOption,
IonToggle,
IonCheckbox,
IonRadioGroup,
IonRadio,
IonItem,
IonLabel,
IonButton,
} from '@ionic/react';
function MyForm() {
return (
<form>
{/* Text input */}
<IonItem>
<IonInput
label="Email"
labelPlacement="floating"
type="email"
placeholder="Enter email"
/>
</IonItem>
{/* Password */}
<IonItem>
<IonInput
label="Password"
labelPlacement="floating"
type="password"
/>
</IonItem>
{/* Textarea */}
<IonItem>
<IonTextarea
label="Bio"
labelPlacement="floating"
rows={4}
placeholder="Tell us about yourself"
/>
</IonItem>
{/* Select */}
<IonItem>
<IonSelect label="Country" placeholder="Select">
<IonSelectOption value="us">United States</IonSelectOption>
<IonSelectOption value="uk">United Kingdom</IonSelectOption>
<IonSelectOption value="de">Germany</IonSelectOption>
</IonSelect>
</IonItem>
{/* Toggle */}
<IonItem>
<IonToggle>Enable notifications</IonToggle>
</IonItem>
{/* Checkbox */}
<IonItem>
<IonCheckbox slot="start" />
<IonLabel>I agree to terms</IonLabel>
</IonItem>
{/* Radio group */}
<IonRadioGroup>
<IonItem>
<IonRadio value="small">Small</IonRadio>
</IonItem>
<IonItem>
<IonRadio value="medium">Medium</IonRadio>
</IonItem>
<IonItem>
<IonRadio value="large">Large</IonRadio>
</IonItem>
</IonRadioGroup>
<IonButton expand="block" type="submit">
Submit
</IonButton>
</form>
);
}
import { IonButton, IonIcon } from '@ionic/react';
import { heart, share, download } from 'ionicons/icons';
function Buttons() {
return (
<>
{/* Fill variants */}
<IonButton>Solid</IonButton>
<IonButton fill="outline">Outline</IonButton>
<IonButton fill="clear">Clear</IonButton>
{/* Colors */}
<IonButton color="primary">Primary</IonButton>
<IonButton color="secondary">Secondary</IonButton>
<IonButton color="danger">Danger</IonButton>
<IonButton color="success">Success</IonButton>
{/* Sizes */}
<IonButton size="small">Small</IonButton>
<IonButton size="default">Default</IonButton>
<IonButton size="large">Large</IonButton>
{/* With icons */}
<IonButton>
<IonIcon slot="start" icon={heart} />
Like
</IonButton>
{/* Icon only */}
<IonButton>
<IonIcon slot="icon-only" icon={share} />
</IonButton>
{/* Full width */}
<IonButton expand="block">Block Button</IonButton>
<IonButton expand="full">Full Width</IonButton>
</>
);
}
import {
IonCard,
IonCardHeader,
IonCardTitle,
IonCardSubtitle,
IonCardContent,
IonImg,
IonButton,
} from '@ionic/react';
function Cards() {
return (
<IonCard>
<IonImg src="/card-image.jpg" alt="" />
<IonCardHeader>
<IonCardSubtitle>Card Subtitle</IonCardSubtitle>
<IonCardTitle>Card Title</IonCardTitle>
</IonCardHeader>
<IonCardContent>
Card content goes here. This is a standard card with
an image, title, subtitle, and content.
</IonCardContent>
<div className="ion-padding-horizontal ion-padding-bottom">
<IonButton fill="clear">Action 1</IonButton>
<IonButton fill="clear">Action 2</IonButton>
</div>
</IonCard>
);
}
import { IonModal, IonButton, IonContent, IonHeader, IonToolbar, IonTitle } from '@ionic/react';
import { useState, useRef } from 'react';
function ModalExample() {
const [isOpen, setIsOpen] = useState(false);
const modal = useRef<HTMLIonModalElement>(null);
return (
<>
<IonButton onClick={() => setIsOpen(true)}>Open Modal</IonButton>
{/* Full page modal */}
<IonModal isOpen={isOpen} onDidDismiss={() => setIsOpen(false)}>
<IonHeader>
<IonToolbar>
<IonTitle>Modal Title</IonTitle>
<IonButton slot="end" onClick={() => setIsOpen(false)}>
Close
</IonButton>
</IonToolbar>
</IonHeader>
<IonContent>
<p>Modal content</p>
</IonContent>
</IonModal>
{/* Bottom sheet */}
<IonModal
ref={modal}
trigger="open-sheet"
initialBreakpoint={0.5}
breakpoints={[0, 0.25, 0.5, 0.75, 1]}
>
<IonContent>
<div className="ion-padding">
<h2>Sheet Content</h2>
<p>Drag to resize</p>
</div>
</IonContent>
</IonModal>
<IonButton id="open-sheet">Open Sheet</IonButton>
</>
);
}
import {
IonTabs,
IonTabBar,
IonTabButton,
IonIcon,
IonLabel,
IonRouterOutlet,
} from '@ionic/react';
import { Route, Redirect } from 'react-router-dom';
import { home, search, person } from 'ionicons/icons';
function TabsLayout() {
return (
<IonTabs>
<IonRouterOutlet>
<Route exact path="/tabs/home" component={HomePage} />
<Route exact path="/tabs/search" component={SearchPage} />
<Route exact path="/tabs/profile" component={ProfilePage} />
<Route exact path="/tabs">
<Redirect to="/tabs/home" />
</Route>
</IonRouterOutlet>
<IonTabBar slot="bottom">
<IonTabButton tab="home" href="/tabs/home">
<IonIcon icon={home} />
<IonLabel>Home</IonLabel>
</IonTabButton>
<IonTabButton tab="search" href="/tabs/search">
<IonIcon icon={search} />
<IonLabel>Search</IonLabel>
</IonTabButton>
<IonTabButton tab="profile" href="/tabs/profile">
<IonIcon icon={person} />
<IonLabel>Profile</IonLabel>
</IonTabButton>
</IonTabBar>
</IonTabs>
);
}
import { IonReactRouter } from '@ionic/react-router';
import { IonRouterOutlet } from '@ionic/react';
import { Route } from 'react-router-dom';
function App() {
return (
<IonReactRouter>
<IonRouterOutlet>
<Route exact path="/" component={Home} />
<Route exact path="/detail/:id" component={Detail} />
</IonRouterOutlet>
</IonReactRouter>
);
}
/* theme/variables.css */
:root {
/* Primary */
--ion-color-primary: #3880ff;
--ion-color-primary-rgb: 56, 128, 255;
--ion-color-primary-contrast: #ffffff;
--ion-color-primary-shade: #3171e0;
--ion-color-primary-tint: #4c8dff;
/* Secondary */
--ion-color-secondary: #3dc2ff;
/* Custom colors */
--ion-color-brand: #ff6b35;
--ion-color-brand-rgb: 255, 107, 53;
--ion-color-brand-contrast: #ffffff;
--ion-color-brand-shade: #e05e2f;
--ion-color-brand-tint: #ff7a49;
}
/* Dark mode */
@media (prefers-color-scheme: dark) {
:root {
--ion-background-color: #121212;
--ion-text-color: #ffffff;
--ion-color-step-50: #1e1e1e;
--ion-color-step-100: #2a2a2a;
}
}
/* iOS specific */
.ios {
--ion-toolbar-background: #f8f8f8;
}
/* Android specific */
.md {
--ion-toolbar-background: #ffffff;
}
/* Global styles */
ion-content {
--background: var(--ion-background-color);
}
ion-card {
--background: #ffffff;
border-radius: 16px;
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.1);
}
/* Platform-specific */
.ios ion-toolbar {
--border-width: 0;
}
.md ion-toolbar {
--border-width: 0 0 1px 0;
}
import { isPlatform } from '@ionic/react';
// Check platform
if (isPlatform('ios')) {
// iOS-specific code
}
if (isPlatform('android')) {
// Android-specific code
}
if (isPlatform('hybrid')) {
// Running in native app
}
if (isPlatform('mobileweb')) {
// Running in mobile browser
}
import { isPlatform, IonIcon } from '@ionic/react';
import { chevronBack, arrowBack } from 'ionicons/icons';
function BackButton() {
return (
<IonIcon
icon={isPlatform('ios') ? chevronBack : arrowBack}
/>
);
}
// Use IonVirtualScroll for long lists
import { IonVirtualScroll } from '@ionic/react';
<IonVirtualScroll
items={items}
renderItem={(item) => (
<IonItem key={item.id}>
<IonLabel>{item.name}</IonLabel>
</IonItem>
)}
/>
// Lazy load images
<IonImg src={url} /> // Automatically lazy loads
// Always provide labels
<IonButton aria-label="Delete item">
<IonIcon slot="icon-only" icon={trash} />
</IonButton>
// Use semantic elements
<IonItem button role="link">
<IonLabel>Clickable item</IonLabel>
</IonItem>
// Content respects safe areas by default
<IonContent>
{/* Auto padding for notch/home indicator */}
</IonContent>
// Custom safe area handling
<div style={{ paddingTop: 'env(safe-area-inset-top)' }}>
Custom header
</div>