cap-go/capgo-skills

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.

All-time #8196 Trending #9445 First seen Jan 25, 2026
8-week activity · all time api

Installation

$ npx skills add cap-go/capgo-skills --skill ionic-design

Similar popular skills

Related neighbors and high-traction skills in the same topics — useful to compare before installing.

Also in this package

Other skills from cap-go/capgo-skills · top by installs.

npx skills add cap-go/capgo-skills

Browse all from cap-go/capgo-skills

More details

Agent compatibility

Declared targets from SKILL.md / docs. Unmarked agents are not listed — the skill may still install via the CLI.

Claude Code Not declared
Cursor Not declared
Codex Not declared
GitHub Copilot Not declared
Windsurf Not declared
Gemini CLI Not declared
Cline Not declared
OpenCode Not declared

Also listed on

Alternate registries and mirrors of this skill.

Repository health

Stars 68
License MIT
Default branch main
Open issues 0
Status Active

Package contents

Files included with this skill beyond the listing page.

  • skill md SKILL.md 13,926 B
  • docs SUMMARY.md 3,506 B

History

  1. First seen on skills.sh
  2. First recorded snapshot · 1,338 installs

SKILL.md

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
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

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>[email protected]</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