# Ionic Framework Admin & Architecture Guide

This guide is for engineers who need to understand how Ionic Framework is **built**, not just how to use it: architects evaluating it, platform owners pinning versions and auditing security, and contributors working inside the monorepo. It documents the repository's structure, the Stencil-based build and codegen pipeline that turns one set of Web Components into four published packages, the configuration and security models, and the testing strategy — all written from the source at the commit stamped in the footer.

For day-to-day application development (installing, using components, theming), see the companion **User Guide**.

---

## 1. Executive Summary

**Ionic Framework** is the open-source source repository for the Ionic UI toolkit. It is **not an application** — it is a library monorepo that publishes the `@ionic/*` packages consumed by app developers.

| Attribute | Value |
|---|---|
| Repository | `ionic-team/ionic-framework` |
| License | MIT |
| Monorepo manager | Lerna (`lerna.json`) |
| Core version (this snapshot) | `@ionic/core` 8.8.9 (all packages share this version) |
| Web Component compiler | Stencil (`@stencil/core` 4.43.5) |
| Published packages | `@ionic/core`, `@ionic/angular`, `@ionic/angular-server`, `@ionic/react`, `@ionic/react-router`, `@ionic/vue`, `@ionic/vue-router` |
| Component count | ~97 `ion-*` Web Components (under `core/src/components/`) |
| Design modes | `ios`, `md` (Material Design) |
| Icon set | `ionicons` (^8.0.13) |
| Native runtime | Capacitor 8 (integration points) |
| Total source | 9,428 files, ~2.17M source characters |
| Min Node | `>= 16` |

**The central architectural idea:** every UI component is authored **once** as a Stencil Web Component in `core/`. Stencil's build then runs **output targets** that auto-generate the Angular, React, and Vue wrapper packages. This is why the components are pixel-identical across frameworks and why all packages move in version lockstep — they are mechanically derived from a single source of truth.

> [!IMPORTANT]
> When reasoning about this repo, treat `core/` as the product and `packages/{angular,react,vue}` as **generated bindings**. Most component behavior lives in `core/`; the framework packages are thin proxies plus a small amount of hand-written framework-idiomatic glue (routing, lifecycle, controllers, value accessors).

---

## 2. Architecture

```text
                         ┌─────────────────────────────────────────────┐
                         │   core/  (@ionic/core)                       │
                         │   ~97 Stencil Web Components (*.tsx)          │
                         │   utils: animation, gesture, overlays,       │
                         │   config, platform, sanitization, transition │
                         └───────────────────┬─────────────────────────┘
                                             │  stencil build
                                             │  (output targets)
              ┌──────────────────────────────┼──────────────────────────────┐
              ▼                               ▼                              ▼
   @stencil/angular-output-target  @stencil/react-output-target   @stencil/vue-output-target
              │                               │                              │
              ▼                               ▼                              ▼
   packages/angular                  packages/react                 packages/vue
   (@ionic/angular)                  (@ionic/react)                 (@ionic/vue)
   + angular/common                  + react-router                 + vue-router
   + angular/standalone              (@ionic/react-router)          (@ionic/vue-router)
   + angular-server (SSR)
              │                               │                              │
              └───────────────────────────────┴──────────────────────────────┘
                                             ▼
                              App developer's project
                              (+ Capacitor for native packaging)
```

### 2.1 Layered responsibilities

| Layer | Location | Responsibility |
|---|---|---|
| **Component source** | `core/src/components/` | The actual UI components, authored as Stencil `.tsx` with scoped/shadow styles in `.scss` |
| **Core utilities** | `core/src/utils/`, `core/src/global/` | Framework-agnostic services: animation engine, gesture engine, overlay controllers, config, platform detection, sanitization, page transitions, logging |
| **Build/codegen** | `core/stencil.config.ts`, `core/scripts/` | Compiles components and generates the framework proxy files via output targets |
| **Framework bindings** | `packages/{angular,react,vue}/` | Generated proxies + hand-written routing, lifecycle, controllers, forms glue |
| **Router integrations** | `packages/{react-router,vue-router}/` | Bridge each framework's router to Ionic's stack-based navigation |
| **SSR** | `packages/angular-server/` | Server-side rendering support for Angular |
| **Test apps** | `packages/*/test/`, `core/src/**/test/` | Per-framework, per-version end-to-end and screenshot test applications |

> [!NOTE]
> A large fraction of the 9,428 files are **test applications and Playwright screenshot snapshots** — e.g. `packages/angular/test/apps/ng16…ng20`, `packages/react/test/apps/react17…react19`, and the thousands of `*.e2e.ts-snapshots/` directories. These exist to verify the components render identically across framework versions; they are not part of any shipped package.

---

## 3. Repository Structure

```text
ionic-framework/
├── core/                     @ionic/core — the Web Components (Stencil)
│   ├── src/
│   │   ├── components/       ~97 ion-* components (each: .tsx + .scss + test/)
│   │   ├── utils/            animation, gesture, overlays, config, platform,
│   │   │                     transition, sanitization, logging, forms, theme
│   │   ├── global/           ionic-global.ts (initialize), config.ts (Config)
│   │   ├── css/              global stylesheets and utility classes
│   │   └── themes/           mode/theme SCSS
│   ├── stencil.config.ts     build config + output targets (THE pipeline)
│   ├── custom-rules/         repo-local ESLint plugin
│   └── scripts/              build, clean, treeshaking, custom-elements
├── packages/
│   ├── angular/              @ionic/angular (NgModule API)
│   │   ├── common/           shared Angular providers/utils (@ionic/angular/common)
│   │   ├── standalone/       @ionic/angular/standalone (standalone components API)
│   │   └── src/schematics/   `ng add` / `ng generate` schematics
│   ├── angular-server/       @ionic/angular-server (SSR)
│   ├── react/                @ionic/react
│   ├── react-router/         @ionic/react-router
│   ├── vue/                  @ionic/vue
│   ├── vue-router/           @ionic/vue-router
│   └── docs/                 docs generation helpers
├── docs/                     CONTRIBUTING.md, per-framework docs
├── BREAKING.md / BREAKING_ARCHIVE/   breaking-change logs
├── CHANGELOG.md / CHANGELOG_ARCHIVE/ release history
├── lerna.json                monorepo configuration
├── renovate.json5            automated dependency updates
└── package.json              root (private; build orchestration only)
```

> [!NOTE]
> The root `package.json` is `private` and declares only `lerna` and `semver` as devDependencies with a one-line description: *"used mainly to execute build scripts. This package is not published to npm."* All real package metadata lives in the per-package `package.json` files.

---

## 4. System Requirements

### 4.1 To build the repository

| Requirement | Version |
|---|---|
| Node.js | `>= 16` |
| Package manager | npm (a `package-lock.json` is committed) |
| Monorepo tool | Lerna `^5.5.2` |

### 4.2 To consume the packages (downstream)

| Package | Key peer/runtime requirements |
|---|---|
| `@ionic/core` | `@stencil/core` 4.43.5, `ionicons` ^8.0.13, `tslib` ^2.1.0 |
| `@ionic/angular` | Angular `>=16` (`core`, `forms`, `router`), `rxjs >=7.5`, `zone.js >=0.13` |
| `@ionic/react` | React + `@ionic/react-router` for routing |
| `@ionic/vue` | Vue 3 + `@ionic/vue-router` for routing |

---

## 5. The Build & Codegen Pipeline

This is the most important system to understand in the repository. It lives in `core/stencil.config.ts`.

### 5.1 Stencil build of `@ionic/core`

The core build (`core/package.json` `build` script) runs:

```bash
npm run clean && npm run build.css && stencil build --es5 --docs-json dist/docs.json
```

- **`build.css`** compiles SCSS to CSS (`sass --style compressed src/css:./css`) and minifies the bundle (`cleancss -O2 -o ./css/ionic.bundle.css`).
- **`stencil build`** compiles every `*.tsx` component into Web Components, produces multiple distribution formats (lazy-loaded `dist/`, `dist/esm/`, `dist/collection/`, custom-elements build), generates the `dist/types/` declarations, and emits `dist/docs.json` (the machine-readable component API used to drive documentation).

The Stencil config sets `namespace: 'Ionic'`, `autoprefixCss: true`, and uses `@stencil/sass` for styles. It explicitly groups components into **bundles** (e.g. `ion-app` is bundled with `ion-router-outlet`, `ion-content`, `ion-header`, `ion-toolbar`, `ion-title`; `ion-card` with its sub-components) so that related components load together and tree-shaking works at a sensible granularity.

### 5.2 Output targets — the framework wrappers

The same `stencil build` runs three output targets that **generate the framework packages**:

| Output target | Generates | Notes |
|---|---|---|
| `angularOutputTarget` (component) | `packages/angular/src/directives/proxies.ts` + `proxies-list.ts` | `outputType: 'component'` (for the NgModule API) |
| `angularOutputTarget` (standalone) | `packages/angular/standalone/src/directives/proxies.ts` | `outputType: 'standalone'` |
| `reactOutputTarget` | React component proxies in `packages/react` | from `@stencil/react-output-target` 0.5.3 |
| `vueOutputTarget` | Vue component proxies in `packages/vue` | from `@stencil/vue-output-target` 0.10.8 |

> [!IMPORTANT]
> The generated proxy files (`proxies.ts`) are **build artifacts checked into the repo**. Do not hand-edit them — they are regenerated by `stencil build`. The Angular package's build script even runs `clean-generated` to remove them between builds.

### 5.3 Excluded components (manual implementations)

The Angular output targets `excludeComponents` for elements that need hand-written Angular semantics rather than a generated proxy:

- **Overlays that host user components:** `ion-modal`, `ion-popover` — need Angular's content-projection/`ng-template` handling.
- **Navigation:** `ion-router`, `ion-route`, `ion-route-redirect`, `ion-router-link`, `ion-router-outlet`, `ion-nav`, `ion-back-button`, `ion-tabs` — replaced by Angular-router-aware directives.
- **Auxiliary:** `ion-picker-legacy-column`.

The **standalone** output additionally excludes `ion-icon` (it comes from `ionicons`, not `@ionic/core`, so a generated proxy would import from the wrong place) and the **form controls** `ion-input`, `ion-input-otp`, `ion-textarea`, `ion-searchbar`, `ion-datetime`, `ion-segment`, `ion-checkbox`, `ion-toggle`, `ion-range`, `ion-radio-group`, `ion-select` — these have **manually implemented value accessors** so Angular forms (`ngModel`, `formControlName`) work correctly.

> [!NOTE]
> This exclude list is the precise boundary between "mechanically generated" and "hand-written" in the Angular packages. When debugging Angular-specific behavior for these components, look in `packages/angular/src/directives/` and `packages/angular/common/`, not in the generated proxies.

### 5.4 Per-package build scripts

| Package | Build approach |
|---|---|
| `@ionic/angular` | `ng-packagr` builds the library (`build.ng`), then `build-core.js` copies core assets; `clean-generated` strips generated proxies after |
| `@ionic/react` / `@ionic/vue` | TypeScript build of the generated proxies + hand-written modules |
| `@ionic/core` | Stencil build (above) |

The root uses Lerna to orchestrate building packages in dependency order (`core` first, then the framework packages that depend on it).

---

## 6. Core Subsystems (Contributor Reference)

These framework-agnostic services in `core/src/` are the engine room. The framework packages re-export them.

### 6.1 Configuration — `core/src/global/config.ts` + `core/src/utils/config.ts`

A singleton `Config` class backs the global `config` object:

```typescript
export class Config {
  private m = new Map<keyof IonicConfig, any>();
  reset(configObj: IonicConfig) { /* replace map */ }
  get(key, fallback?) { /* ... */ }
  getBoolean(key, fallback = false) { /* string 'true' coercion */ }
  getNumber(key, fallback?) { /* parseFloat */ }
  set(key, value) { /* ... */ }
}
export const config = /*@__PURE__*/ new Config();
```

`setupConfig(config)` merges the provided object into `window.Ionic.config`. The config is resolved from three sources (later wins where applicable):

1. **Code** — the object passed to `setupConfig` / `provideIonicAngular` / `setupIonicReact` / `IonicVue`.
2. **URL** — `configFromURL()` reads any query parameter prefixed `ionic:` (e.g. `?ionic:mode=md`).
3. **Session** — `configFromSession()` / `saveConfig()` persist config under `sessionStorage` key `ionic-persist-config` when `persistConfig: true`.

`getMode()` returns the active mode, defaulting to `'md'` when nothing is configured.

The complete `IonicConfig` interface (defined in `core/src/utils/config.ts`) is the authoritative configuration surface — see [§8](#8-configuration-reference).

### 6.2 Overlay controllers — `core/src/utils/overlays.ts`

A generic `createController<Opts, Elm>(tagName)` factory produces each controller with `create`/`dismiss`/`getTop`. The exported singletons:

| Export | Tag | Options type |
|---|---|---|
| `alertController` | `ion-alert` | `AlertOptions` |
| `actionSheetController` | `ion-action-sheet` | `ActionSheetOptions` |
| `loadingController` | `ion-loading` | `LoadingOptions` |
| `modalController` | `ion-modal` | `ModalOptions` |
| `pickerController` | `ion-picker-legacy` | `PickerOptions` |
| `popoverController` | `ion-popover` | `PopoverOptions` |
| `toastController` | `ion-toast` | `ToastOptions` |

`createOverlay(tagName, options)` builds the element; the file also centralizes Escape-key and hardware-back-button dismissal behavior, wired when the first overlay is created. The string `"framework delegate is missing"` is thrown here (and in `framework-delegate.ts`) when a component-hosting overlay is used without a framework delegate.

### 6.3 Animation engine — `core/src/utils/animation/`

`createAnimation()` returns a chainable `Animation` built on the Web Animations API. It supports element/group targeting (`addElement`, `addAnimation`), timing (`duration`, `delay`, `iterations`, `direction`, `easing`, `fill`), property keyframes (`from`/`to`/`fromTo`/`keyframes`), playback (`play`/`pause`/`stop`/`destroy`), gesture-driven progress (`progressStart`/`progressStep`/`progressEnd`), and before/after read-write hooks for batched DOM access. Page transitions (`iosTransitionAnimation`, `mdTransitionAnimation`) and all overlay enter/leave animations are themselves built with this engine, which is why they are fully overridable via config.

### 6.4 Gesture engine — `core/src/utils/gesture/`

`createGesture(config: GestureConfig)` provides normalized pointer handling with a `GestureDetail` payload (coordinates, deltas, velocity). Config supports `direction`, `threshold`, `gesturePriority` (collision resolution), `passive`, `maxAngle`, `disableScroll`, `blurOnStart`, and `canStart`/`onWillStart`/`onStart`/`onMove`/`onEnd`/`notCaptured` callbacks. Swipe-to-go-back, item-sliding, the range knob, refresher, and sheet-modal drag are all built on this.

### 6.5 Platform detection — `core/src/utils/platform.ts`

`isPlatform(name)`, `getPlatforms()`, and the `Platforms`/`PlatformConfig` types. Platform detection is also overridable via the config `platform` key.

### 6.6 Sanitization — `core/src/utils/sanitization/`

A DOM sanitizer plus `IonicSafeString` (a wrapper marking a string as trusted HTML). Governed by the `sanitizerEnabled` and `innerHTMLTemplatesEnabled` config keys — see [§9 Security](#9-security).

### 6.7 Global initialization — `core/src/global/ionic-global.ts`

`initialize(config)` is the single entry point each framework calls (Vue's `IonicVue.install` calls it directly; the other frameworks call it through their setup functions). It applies config, sets the document mode, and bootstraps the global Ionic state.

### 6.8 Lifecycle constants — `core/src/components/nav/constants.ts`

`LIFECYCLE_WILL_ENTER`, `LIFECYCLE_DID_ENTER`, `LIFECYCLE_WILL_LEAVE`, `LIFECYCLE_DID_LEAVE`, `LIFECYCLE_WILL_UNLOAD` — the event names the page lifecycle is built on.

---

## 7. Framework Binding Internals

### 7.1 Angular

- **`packages/angular/src/`** — the NgModule API. `IonicModule.forRoot(config)` (in `ionic-module.ts`) returns `ModuleWithProviders` and accepts `IonicConfig & OptInAngularFeatures`. Exports providers (`AlertController`, `ModalController`, …, `Platform`, `Config`, `NavController`, `DomController`, `AngularDelegate`), directives, control value accessors, validators, and `IonicRouteStrategy`.
- **`packages/angular/common/`** — shared code (`@ionic/angular/common`) used by both the NgModule and standalone APIs: `OverlayBaseController`, `IonicRouteStrategy`, routing utils, overlay proxies.
- **`packages/angular/standalone/`** — the standalone-components API (`@ionic/angular/standalone`). `provideIonicAngular(config)` returns `EnvironmentProviders`; components and controllers are imported individually. This is the modern, tree-shakeable path.
- **`packages/angular/src/schematics/`** — `ng add @ionic/angular` and `ng generate` schematics. The `SchematicsException` messages (`Could not find project`, `Invalid projectType`, `Invalid builder`, `Could not find file for path`) originate here and surface during scaffolding.

> [!NOTE]
> `OptInAngularFeatures` is merged into the config type for both `forRoot` and `provideIonicAngular`, so Angular-specific opt-ins are configured alongside the standard `IonicConfig`.

### 7.2 React

`packages/react/src/` barrels (`index.ts`) re-export `./components`, `./routing`, `./lifecycle`, `./models`, `./contexts`, and utilities. `setupIonicReact(config)` (in `components/index.ts`) initializes the framework. Routing is split into `@ionic/react-router` (`IonReactRouter`, `IonReactMemoryRouter`, `IonReactHashRouter`) layered on `react-router`. Lifecycle hooks (`useIonViewWillEnter`, etc.) and contexts (`NavContext`, `IonLifeCycleContext`) implement the page lifecycle in React idioms.

### 7.3 Vue

`packages/vue/src/index.ts` exports component proxies (`./proxies`), the `IonicVue` plugin, hand-written components that need Vue-specific behavior (`IonPage`, `IonRouterOutlet`, `IonTabs`, `IonTabBar`, `IonTabButton`, `IonNav`, `IonBackButton`, `IonApp`, `IonIcon`, overlays), composition hooks (`useBackButton`, `useKeyboard`, `useIonRouter`, lifecycle), and controllers. `IonicVue.install` calls `initialize(config)` and adds the `ion-ce` class to the document element (custom-elements build has no hydration, so non-hydrated elements must not be hidden).

### 7.4 Router integrations

Both router packages exist because Ionic needs a **navigation stack** on top of URL routing:

- **`@ionic/vue-router`** — `createRouter(opts)` wraps `vue-router`'s `createRouter`, then injects `navManager` (an `IonRouter`) and `viewStacks` into the app and patches `router.install`. Also re-exports `createWebHistory`/`createWebHashHistory`/`createMemoryHistory`.
- **`@ionic/react-router`** — `IonReactRouter` and the memory/hash variants wrap `react-router` similarly, maintaining a `LocationHistory` and view stacks so transitions and back-navigation animate correctly.

---

## 8. Configuration Reference

The full `IonicConfig` interface from `core/src/utils/config.ts`. All keys are optional.

### 8.1 Public configuration keys

| Key | Type | Default / Notes |
|---|---|---|
| `animated` | `boolean` | `false` disables all animations/transitions |
| `rippleEffect` | `boolean` | default `true`; Material ripple |
| `mode` | `'ios' \| 'md'` | platform-detected; defaults to `md` if unset |
| `hardwareBackButton` | `boolean` | default `true` on mobile |
| `statusTap` | `boolean` | default `true` on mobile; tap status bar to scroll top |
| `backButtonIcon` | `string` | overrides all `ion-back-button` icons |
| `backButtonText` | `string` | overrides all `ion-back-button` text |
| `backButtonDefaultHref` | `string` | default fallback href |
| `menuIcon` | `string` | overrides all `ion-menu-button` icons |
| `menuType` | `string` | overrides default `ion-menu` type |
| `spinner` | `SpinnerTypes` | overrides all `ion-spinner` |
| `toggleOnOffLabels` | `boolean` | overrides `ion-toggle` on/off labels |
| `loadingSpinner` | `SpinnerTypes \| null` | spinner for loading overlays |
| `refreshingIcon` | `string` | icon for `ion-refresh-content` |
| `refreshingSpinner` | `SpinnerTypes \| null` | spinner for `ion-refresh-content` |
| `infiniteLoadingSpinner` | `SpinnerTypes \| null` | spinner for `ion-infinite-scroll-content` |
| `swipeBackEnabled` | `boolean` | iOS swipe-to-go-back across all `ion-nav` |
| `tabButtonLayout` | `TabButtonLayout` | layout of tab buttons |
| `toastDuration` | `number` | default `ion-toast` duration |
| `navAnimation` | `AnimationBuilder` | override all nav/router-outlet transitions |
| `sanitizerEnabled` | `boolean` | enable the basic DOM sanitizer |
| `innerHTMLTemplatesEnabled` | `boolean` | **`false` by default**; allow `innerHTML` HTML in select components |
| `platform` | `PlatformConfig` | override platform detection |
| `logLevel` | `'OFF' \| 'ERROR' \| 'WARN'` | framework logging level |

### 8.2 Animation override keys (each an `AnimationBuilder`)

`actionSheetEnter`, `actionSheetLeave`, `alertEnter`, `alertLeave`, `loadingEnter`, `loadingLeave`, `modalEnter`, `modalLeave`, `popoverEnter`, `popoverLeave`, `toastEnter`, `toastLeave`, `pickerEnter`, `pickerLeave`.

### 8.3 Experimental keys

| Key | Type | Notes |
|---|---|---|
| `focusManagerPriority` | `('content' \| 'heading' \| 'banner')[]` | Move focus after each transition (a11y) |
| `experimentalCloseWatcher` | `boolean` | Use the CloseWatcher API; requires `hardwareBackButton: true` |

### 8.4 Private / internal keys

These are documented in the interface as private and may change without notice; do not depend on them in application code:

`keyboardHeight`, `inputShims`, `scrollPadding`, `inputBlurring`, `scrollAssist`, `hideCaretOnScroll`, `persistConfig`, `_forceStatusbarPadding`, `_testing`, `_zoneGate`.

> [!CAUTION]
> The `_`-prefixed keys (`_forceStatusbarPadding`, `_testing`, `_zoneGate`) are framework-internal test/integration hooks. Setting them in production configuration is unsupported.

---

## 9. Security

### 9.1 HTML sanitization

Components that render developer-supplied HTML — `ion-alert`, `ion-toast`, `ion-loading`, `ion-infinite-scroll-content`, `ion-refresher-content` — are the framework's XSS-sensitive surface. Two config keys govern them:

| Key | Default | Effect |
|---|---|---|
| `innerHTMLTemplatesEnabled` | **`false`** | When `false`, `innerHTML` usage is disabled in those components and custom HTML cannot be injected. Must be explicitly enabled to use custom HTML. |
| `sanitizerEnabled` | (basic sanitizer) | Enables Ionic's built-in DOM sanitizer on properties that accept custom HTML. |

`IonicSafeString` lets a developer explicitly mark a string as trusted HTML when sanitization would otherwise strip it.

> [!CAUTION]
> The secure default is to **not** allow `innerHTML` templates (`innerHTMLTemplatesEnabled: false`, the constant `ENABLE_HTML_CONTENT_DEFAULT = false`). Only enable it for content you fully control. Never pass untrusted/user-generated content into these components with the flag enabled — doing so is a direct XSS vector.

### 9.2 Configuration trust boundary

Because config is also read from the URL (`?ionic:*`) and from `sessionStorage`, treat URL-driven config as **client-controllable**. The public keys are presentational (mode, spinners, animations) so this is low-risk, but never gate security decisions on values that can be set via the `ionic:` URL prefix.

### 9.3 Dependency supply chain

The repo uses **Renovate** (`renovate.json5`) for automated dependency updates and pins exact versions for build-critical tooling (`@stencil/core` 4.43.5, `@stencil/react-output-target` 0.5.3, `@stencil/vue-output-target` 0.10.8). When auditing, verify these pins match your security policy, since the output-target versions directly determine the generated wrapper code shipped to downstream apps.

---

## 10. Operations

There is no server to run — "operations" here means building, testing, and releasing the packages.

### 10.1 Key npm scripts (`core/package.json`)

| Script | Command | Purpose |
|---|---|---|
| `build` | `clean && build.css && stencil build --es5 --docs-json dist/docs.json` | Full core build |
| `build.css` | `css.sass && css.minify` | Compile + minify styles |
| `start` | `build.css && stencil build --dev --watch --serve` | Dev server with watch |
| `test` | `test.spec && test.e2e` | Spec + e2e |
| `test.spec` | `stencil test --spec --max-workers=2` | Jest spec tests |
| `test.e2e` | `npx playwright test` | Playwright e2e |
| `test.e2e.update-snapshots` | `… --update-snapshots='changed'` | Refresh changed screenshots |
| `test.treeshake` | `node scripts/treeshaking.js dist/index.js` | Verify tree-shaking |
| `lint` | `lint.ts && lint.sass && prettier --write` | ESLint + Stylelint + Prettier |
| `validate` | `lint && test && build && test.treeshake` | Full pre-release gate |
| `test.e2e.docker` | `docker.build && node ./scripts/docker.mjs` | Run e2e in a pinned Playwright Docker image |

> [!TIP]
> The `validate` script is the canonical "is this ready to ship" gate: it lints, tests, builds, and verifies tree-shaking. Run it (or its Angular/Vue equivalents) before opening a PR.

### 10.2 Release & versioning

Lerna manages versions; all packages publish in lockstep at the same version (8.8.9 in this snapshot). Per-package `prerelease` scripts run `validate` then publish via `np` with a `next` tag for prereleases. Breaking changes are logged in `BREAKING.md` (archived in `BREAKING_ARCHIVE/`) and releases in `CHANGELOG.md` (archived in `CHANGELOG_ARCHIVE/`).

### 10.3 Cross-framework / cross-version testing

The `packages/*/test/apps/` directories contain real apps targeting multiple framework versions — Angular `ng16` through `ng20`, React `react17`/`react18`/`react19`, and Vue 3. Combined with Playwright screenshot snapshots under `*.e2e.ts-snapshots/`, this catches visual and behavioral regressions across the framework version matrix. This is the operational backbone that lets Ionic claim "works identically on Angular/React/Vue."

---

## 11. Code Patterns & Conventions

- **Single source, generated bindings.** Author components once in `core/`; never duplicate logic into the framework packages. Framework packages hold only generated proxies + framework-idiomatic glue.
- **Pure-annotation for tree-shaking.** Singletons are created with `/*@__PURE__*/` annotations (`config`, the overlay controllers) so bundlers can drop unused ones.
- **Mode-aware styling.** Components ship `ios` and `md` styles; never hardcode a single platform's look.
- **Direction-aware layout.** Use logical `start`/`end` slots and `-start`/`-end` CSS utilities rather than left/right, for RTL support.
- **Repo-local lint rules.** `core/custom-rules/` is a local ESLint plugin (`eslint-plugin-custom-rules`) enforcing Ionic-specific conventions, wired in via `file:custom-rules`.
- **Stylelint for SCSS** (`stylelint` + `stylelint-order`) and **Prettier** with the shared `@ionic/prettier-config`.

---

## 12. Testing Strategy

| Layer | Tooling | Location |
|---|---|---|
| Unit / spec | Jest via `@stencil/core/testing` preset (`stencil test --spec`) | colocated `*.spec.ts(x)` |
| Component e2e + visual | Playwright (`@playwright/test` ^1.59.1), with `@axe-core/playwright` for a11y | `core/src/**/test/**/*.e2e.ts` + `*.e2e.ts-snapshots/` |
| Cross-framework e2e | Per-framework test apps (Cypress/Playwright) | `packages/*/test/` |
| Tree-shaking | `scripts/treeshaking.js` | run in `validate` |

Custom Jest matchers exist for shadow DOM assertions (`core/setupJest.js` defines `toHaveShadowPart`, throwing `expected toHaveShadowPart to be called with a string …` / `… on an element with a shadow root` when misused).

> [!NOTE]
> The skipped/CI-flaky tests visible in community issues (e.g. infinite-scroll scroll-to-top flaky on Safari, issue #30423) indicate where the screenshot/e2e suite is sensitive to timing. Contributors touching scroll or transition code should run the affected e2e specs locally with `--update-snapshots='changed'` and review the diff carefully.

---

## 13. Potential Improvements & Tech Debt

Derived from the source and the open issue tracker at this snapshot:

- **Legacy picker removal.** `ion-picker-legacy` / `ion-picker-legacy-column` are retained for back-compat alongside the modern `ion-picker`. A future major can drop them.
- **Deprecated reorder event.** `core/src/index.ts` and the wrapper exports carry a `TODO(FW-6590)` to remove a deprecated reorder event (`ItemReorderEventDetail`/`ItemReorderCustomEvent`).
- **`:host-context` CSS deprecation (issue #30024).** Ionic emits `:host-context()`, which Lightning CSS flags as deprecated; a CSS-output update is warranted.
- **Newest-framework support.** Angular 21 native Vitest support and full zoneless support (issues #30982, #30907, #30804) are in progress; ESM directory-import resolution (#30730) affects modern bundlers.
- **Datetime overlay edge cases.** Several open issues (#30504, #31155, #30933, #31151, #31143, #30449) cluster around `ion-datetime` inside overlays and event reliability — the densest defect area in the tracker.
- **Router query-param handling (#31152).** `IonRouter.handleHistoryChange` compares search-bearing and search-free URLs inconsistently.
- **Config typing TODO.** `core/src/utils/config.ts` carries `TODO(FW-2832): types` — internal config keys are loosely typed (`any` map) and could be tightened.

---

## 14. Contributing

1. Read `docs/CONTRIBUTING.md` and the `CODE_OF_CONDUCT.md`.
2. Look for issues labeled **help wanted**.
3. Make component changes in `core/` — never edit generated `proxies.ts` files; regenerate them with `stencil build`.
4. Run the package's `validate` gate (lint + test + build + treeshake) before opening a PR.
5. For visual changes, update Playwright screenshots with `test.e2e.update-snapshots` and include the diff rationale.
6. Document breaking changes in `BREAKING.md`.
7. The community gathers on the official Discord and Forums (linked from the README); use GitHub issues for bugs and feature requests.

---

## 15. Quick Reference

### 15.1 Published packages

| Package | Purpose |
|---|---|
| `@ionic/core` | The Web Components (Stencil) |
| `@ionic/angular` | Angular NgModule bindings |
| `@ionic/angular/standalone` | Angular standalone-components bindings |
| `@ionic/angular/common` | Shared Angular providers/utils |
| `@ionic/angular-server` | Angular SSR |
| `@ionic/react` | React bindings |
| `@ionic/react-router` | React routing integration |
| `@ionic/vue` | Vue 3 bindings |
| `@ionic/vue-router` | Vue routing integration |

### 15.2 Framework initialization

| Framework | Call |
|---|---|
| Angular (NgModule) | `IonicModule.forRoot(config)` |
| Angular (standalone) | `provideIonicAngular(config)` |
| React | `setupIonicReact(config)` |
| Vue | `app.use(IonicVue, config)` (calls `initialize(config)`) |

### 15.3 Core entry-point exports (`@ionic/core`)

`createAnimation`, `createGesture`, `initialize`, `setupConfig`, `getMode`, `componentOnReady`, `isPlatform`, `getPlatforms`, `IonicSafeString`, `openURL`, `IonicSlides`, lifecycle constants, `menuController`, and the overlay controllers (`alert`/`actionSheet`/`modal`/`loading`/`picker`/`popover`/`toast`).

### 15.4 Build commands

| Goal | Command (from `core/`) |
|---|---|
| Full build | `npm run build` |
| Dev server | `npm run start` |
| Spec tests | `npm run test.spec` |
| E2E tests | `npm run test.e2e` |
| Pre-release gate | `npm run validate` |

---

_Generated June 2026 from ionic-team/ionic-framework at 3da72cd. AI-generated documentation. The library is checked by sampling (three repositories so far); this guide was not individually reviewed. Verify against the project before relying on it. Unofficial; not affiliated with or reviewed by the project._
