| name | ionic-design |
| description | Guide to using Ionic Framework components for beautiful native-looking Capacitor apps. Covers component usage, theming, platform-specific styling, and best practices for mobile UI. Use this skill when users need help with Ionic components or mobile UI design. |
Ionic Framework Design Guide
Build beautiful, native-looking mobile apps with Ionic Framework and Capacitor.
When to Use This Skill
- User is using Ionic components
- User wants native-looking UI
- User asks about Ionic theming
- User needs mobile UI patterns
- User wants platform-specific styling
What is Ionic Framework?
Ionic provides:
- 100+ mobile-optimized UI components
- Automatic iOS/Android platform styling
- Built-in dark mode support
- Accessibility out of the box
- Works with React, Vue, Angular, or vanilla JS
Getting Started
Installation
npx create-vite@latest my-app --template react-ts
cd my-app
npm install @ionic/react @ionic/react-router
npx create-vite@latest my-app --template vue-ts
cd my-app
npm install @ionic/vue @ionic/vue-router
npm install @capacitor/core @capacitor/cli
npx cap init
Setup (React)
import React from 'react';
import { createRoot } from 'react-dom/client';
import { IonApp, setupIonicReact } from '@ionic/react';
import App from './App';
import '@ionic/react/css/core.css';
import '@ionic/react/css/normalize.css';
import '@ionic/react/css/structure.css';
import '@ionic/react/css/typography.css';
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';
import './theme/variables.css';
setupIonicReact();
const root = createRoot(document.getElementById('root')!);
root.render(
<IonApp>
<App />
</IonApp>
);
Core Components
Page Structure
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>
</>
{/* Page content */}
Your content here
);
}
Lists
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>
John Doe
john@example.com
{/* Sliding item */}
Swipe me
);
}
Forms
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=
=
=
/>
{/* Select */}
United States
United Kingdom
Germany
{/* Toggle */}
Enable notifications
{/* Checkbox */}
I agree to terms
{/* Radio group */}
Small
Medium
Large
Submit
);
}
Buttons
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
Default
Large
{/* With icons */}
Like
{/* Icon only */}
{/* Full width */}
Block Button
Full Width
);
}
Cards
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>
);
}
Modals and Sheets
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>
</>
Modal content
{/* Bottom sheet */}
Sheet Content
Drag to resize
Open Sheet
);
}
Navigation
Tab Navigation
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" />
</>
Home
Search
Profile
);
}
Stack Navigation
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>
);
}
Theming
Theme Variables
:root {
--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;
--ion-color-secondary: #3dc2ff;
--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;
}
@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 {
--ion-toolbar-background: #f8f8f8;
}
.md {
--ion-toolbar-background: ;
}
Custom Component Styling
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);
}
.ios ion-toolbar {
--border-width: 0;
}
.md ion-toolbar {
--border-width: 0 0 1px 0;
}
Platform-Specific Code
Detect Platform
import { isPlatform } from '@ionic/react';
if (isPlatform('ios')) {
}
if (isPlatform('android')) {
}
if (isPlatform('hybrid')) {
}
if (isPlatform('mobileweb')) {
}
Conditional Rendering
import { isPlatform, IonIcon } from '@ionic/react';
import { chevronBack, arrowBack } from 'ionicons/icons';
function BackButton() {
return (
<IonIcon
icon={isPlatform('ios') ? chevronBack : arrowBack}
/>
);
}
Best Practices
Performance
import { IonVirtualScroll } from '@ionic/react';
<IonVirtualScroll
items={items}
renderItem={(item) => (
<IonItem key={item.id}>
<IonLabel>{item.name}</IonLabel>
</IonItem>
)}
/>
<IonImg src={url} />
Accessibility
<IonButton aria-label="Delete item">
<IonIcon slot="icon-only" icon={trash} />
</IonButton>
<IonItem button role="link">
<IonLabel>Clickable item</IonLabel>
</IonItem>
Safe Area
<IonContent>
{}
</IonContent>
<div style={{ paddingTop: 'env(safe-area-inset-top)' }}>
Custom header
</div>
Resources