Claude-skill-registry ionic-design
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.
install
source · Clone the upstream repo
git clone https://github.com/majiayu000/claude-skill-registry
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/ionic-design" ~/.claude/skills/majiayu000-claude-skill-registry-ionic-design && rm -rf "$T"
manifest:
skills/data/ionic-design/SKILL.mdsource content
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
# For React bun create vite my-app --template react-ts cd my-app bun add @ionic/react @ionic/react-router # For Vue bun create vite my-app --template vue-ts cd my-app bun add @ionic/vue @ionic/vue-router # Add Capacitor bun add @capacitor/core @capacitor/cli bunx cap init
Setup (React)
// 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> );
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> </IonHeader> {/* Page content */} <div className="ion-padding"> Your content here </div> </IonContent> </IonPage> ); }
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> <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> ); }
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="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> ); }
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</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> </> ); }
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> </div> </IonCard> ); }
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> </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> </> ); }
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" /> </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> ); }
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
/* 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; }
Custom Component Styling
/* 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; }
Platform-Specific Code
Detect Platform
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 }
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
// 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
Accessibility
// 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>
Safe Area
// 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>
Resources
- Ionic Documentation: https://ionicframework.com/docs
- Ionic Components: https://ionicframework.com/docs/components
- Ionicons: https://ionic.io/ionicons
- Color Generator: https://ionicframework.com/docs/theming/color-generator