The easiest way to get started with Ionic Capacitor with React is a one-liner via the Ionic CLI although doing so will scaffold a project using create-react-app. In this post, I’ll explain the steps needed to scaffold a new Capacitor project with Vite.

Ionic Framework is a mobile UI toolkit for building cross-platform apps based on JavaScript. Capacitor is a native runtime for building cross-platform apps targeting Mobile and Web, similar to, but improving on, Apache Cordova. Vite is a frontend development tool similar to Create React App (CRA) that improves on build speed and developer experience. Ionic CLI allows us to, amongst other things, scaffold a new Ionic project with React, but doing so results in CRA dependencies. While the approach for starting a new Capacitor app without Ionic results in a Vite project, it’s a vanilla Javascript template without TypeScript or React.

The stack we want is Vite + React + Typescript + Ionic + Capacitor on Android and iOS and the approach will be to add them in this order. We’ll be able to run builds and debug with live reload directly via CLI or optionally with Xcode/Android Studio. The example code used in this post can be seen in context here.

Starting with Vite

Adding Vite first results in Vite + Typescript + React, though you could use any of the templates specified in the Vite docs.

npm create vite@latest vite-and-capacitor -- --template react-ts

Adding Ionic Framework

Adding Ionic Framework to an existing React project includes adding the Ionic React library, the react-router wrapper, and the types we need for TypeScript compilation:

npm i @ionic/react @ionic/react-router
npm i -D @types/react-router @types/react-router-dom 

Prior to adding Ionic our main.tsx looks like this:

import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App'
import './index.css'

ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
  <React.StrictMode>
    <App />
  </React.StrictMode>,
)

We’ll add Ionic style, configuration and react-navigation support, and wrap the root with the IonApp component:

import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App";
import { IonApp, IonRouterOutlet, setupIonicReact } from "@ionic/react";
import { IonReactRouter } from "@ionic/react-router";
import { Redirect, Route } from "react-router";
import "./index.css";

/* Core Ionic framework styles */
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 that can be commented out */
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";

/* Ionic Theme variables */
import "./variables.css";

setupIonicReact();

ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(
  <React.StrictMode>
    <IonApp>
      <IonReactRouter>
        <IonRouterOutlet>
          <Route path="/home" component={App} />
          <Redirect exact from="/" to="/home" />
        </IonRouterOutlet>
      </IonReactRouter>
    </IonApp>
  </React.StrictMode>
);

Most of the styles above come from @ionic/react, however variables.css does not. The purpose of variables.css above is to configure the default theme and to implement support for Dark Mode. An example variables.css can be found here.

To correctly support style and navigation transitions our screen components should be updated to use IonPage so where we had:

return (
  <h2>My header!</h2>
  <div>My content!</div>
);

We’ll now have:

return (
  <IonPage>
    <IonHeader>My header!</IonContent>
    <IonContent>My content!</IonContent>
  </IonPage>
);

Adding Capacitor

While Capacitor has its own CLI, it’s better to use Ionic CLI as we’ll need it anyway to support live reload. For Android development I prefer to use Windows/PowerShell, so on *nix you can drop the -- -- for a single --.

npm install -g "@ionic/cli"
ionic init "vite-and-capacitor" --type=react
ionic integrations enable capacitor -- -- "vite-and-capacitor" "com.stafford.vite.capacitor" --web-dir=dist

Adding Android

We can now use the Capacitor CLI to add Android support:

npm i @capacitor/android
npm run build
npx cap add android

To build and run an Android project on an emulator or attached device needs at the very least the Android SDK and tools. The easiest way to get these is via an Android Studio install. It’s often easiest to debug problems across emulator, SDK and gradle versions in Android Studio, so we’ll use it confirm our first build. Configure any required environment variables and open the project in Android Studio:

$env:Path += ";$env:LOCALAPPDATA\Android\sdk\platform-tools"
$env:ANDROID_SDK_ROOT="C:\Users\stafford\AppData\Local\Android\sdk"
$env:JAVA_HOME='C:\Program Files\Android\Android Studio\jbr'
npx cap open android

Note: At the time of writing, Android Studio (Electric Eel) installs an unsupported version of the Android SDK and the builds fails with ERR_UNSUPPORTED_API_LEVEL: Unsupported API level: 33x. Uninstalling this SDK and adding the normal 33 SDK resolves this. See more.

After confirming a build, we can drop back to the CLI and run using live reload. Add the following to package.json/scripts:

"ionic:serve": "vite dev --host"

And then run the project with:

ionic capacitor run android --external --livereload -p 5173

Adding iOS

To run on iOS we’ll need a Mac with Xcode installed. To add iOS run:

npm i @capacitor/ios
npm run build
npx cap add ios
npx cap open ios

After testing that we can build, running with live reload is similar to the above:

ionic capacitor run ios --external --livereload -p 5173

Summary and Source

With the above steps we can run both Android and iOS projects with live reload. The source for this project is here, and you can view each commit to see the specific change in each step:

Commits

  1. Scaffold with vite
  2. Add Ionic Framework
  3. Add Capacitor
  4. Add Android project
  5. Run Android with live reload
  6. Add iOS project
  7. Run iOS with live reload