---
title: Ionic
description: Use touchcn components inside an existing Ionic Angular, React, or Vue app — coexistence, CSS boundaries, structure conflicts, and a migration path.
sidebar:
  label: Ionic
  icon: '<svg viewBox="0 0 24 24" width="100%" height="100%" fill="currentColor" aria-hidden="true"><path d="M22.922 7.027l-.103-.23-.169.188c-.408.464-.928.82-1.505 1.036l-.159.061.066.155a9.745 9.745 0 0 1 .75 3.759c0 5.405-4.397 9.806-9.806 9.806-5.409 0-9.802-4.397-9.802-9.802 0-5.405 4.402-9.806 9.806-9.806 1.467 0 2.883.319 4.2.947l.155.075.066-.155a3.767 3.767 0 0 1 1.106-1.453l.197-.159-.225-.117A11.905 11.905 0 0 0 12.001.001c-6.619 0-12 5.381-12 12s5.381 12 12 12 12-5.381 12-12c0-1.73-.361-3.403-1.078-4.973zM12 6.53A5.476 5.476 0 0 0 6.53 12 5.476 5.476 0 0 0 12 17.47 5.476 5.476 0 0 0 17.47 12 5.479 5.479 0 0 0 12 6.53zm10.345-2.007a2.494 2.494 0 1 1-4.988 0 2.494 2.494 0 0 1 4.988 0z"/></svg>'
---

touchcn and [Ionic](https://ionicframework.com) can run side by side in the same app. touchcn
components are plain DOM with `tcn-*` classes, so they render fine inside an `ion-content`, and adding
touchcn does not change how you bootstrap Ionic. This guide covers what to watch for when the two share
a page and how to migrate incrementally.

:::tip
**Let an AI agent do the migration.** The `touchcn-ionic-migration` [agent skill](/docs/skills)
teaches Claude Code, Cursor, and other compatible tools the component mapping, the coexistence rules
on this page, and the leaf-first migration order — install it and ask your agent to
*"migrate this Ionic app to touchcn"*:

```bash
npx skills add capawesome-team/touchcn --skill touchcn-ionic-migration
```
:::

## Coexistence model

Install touchcn as in [Getting started](/docs/getting-started) and register its provider once at the
app root, alongside your existing Ionic setup — the two providers are independent. A touchcn component
placed inside `ion-content` behaves like any other child element:

<CodeGroup>

```html Angular
<ion-content>
  <button tcnButton variant="primary">Save</button>
</ion-content>
```

```tsx React
<IonContent>
  <TcnButton variant="primary">Save</TcnButton>
</IonContent>
```

```vue Vue
<ion-content>
  <TcnButton variant="primary">Save</TcnButton>
</ion-content>
```

</CodeGroup>

## CSS boundaries

The two systems keep their styling in separate namespaces, which is what makes coexistence practical:

- **Design tokens do not overlap.** Ionic themes through `--ion-*` CSS variables that only its own
  `ion-*` elements read; touchcn themes through `--color-*` tokens consumed by `.tcn-*` classes.
  Neither sees the other's variables, so re-theming Ionic does not shift touchcn components and vice
  versa.
- **Ionic components are largely shadow-encapsulated.** Most `ion-*` elements render into a shadow root,
  so Tailwind's preflight (which resets base elements in the light DOM) does not reach inside them.
  Preflight still resets *your own* light-DOM markup — headings, lists, buttons you write directly — so
  expect that reset to apply to non-Ionic elements exactly as it would in any Tailwind project.
- **Load order.** Import Ionic's stylesheets and Tailwind (with the touchcn token layer) both from your
  global stylesheet. touchcn's platform chrome lives in an `@layer components` block at single-class
  specificity, so ordinary Ionic rules and your own utilities out-rank it; if a specific reset ever
  collides, adjust import order or scope the narrower rule rather than raising specificity globally.

:::note
Verify your own build: preflight's element resets are the one place the two systems can meet on shared
light-DOM elements. Check forms and typography in a page that mixes `ion-*` and `tcn-*` markup.
:::

## Structure conflicts

**Do not nest touchcn chrome inside Ionic chrome.** A `tcn-navbar` inside `ion-header` /
`ion-toolbar`, or a `tcn-tabbar` inside `ion-footer`, produces two stacked bars and two safe-area
insets applied to the same edge. Pick one chrome per screen: keep Ionic's `ion-header` / `ion-tab-bar`,
**or** use touchcn's — not both on the same edge.

Overlays need a note on stacking. touchcn overlays (dialogs, sheets, the select and datepicker panels)
are `position: fixed` and layer with Tailwind's `z-40` (navbar/tabbar) and `z-50` (overlays) — low
numbers. Ionic layers much higher: its toolbar sits at `z-index: 10`, menus and inline overlays around
`1000`, and **Ionic assigns its runtime overlays** (`ion-modal`, `ion-alert`, `ion-action-sheet`,
`ion-toast`, …) **a z-index starting at 20000**. The practical consequences:

- A touchcn overlay renders **above** ordinary Ionic content and toolbars — fine for a dialog over a
  normal page.
- A touchcn overlay renders **below** an active Ionic runtime overlay (modal, alert, toast). If you
  genuinely need a touchcn overlay on top of an Ionic modal, raise its `z-index` past Ionic's
  `20000`+ range explicitly — but that usually signals you should present one or the other, not stack
  them.

## Migration path

Migrate leaf-first, so each step is low-risk and visually contained:

1. **Start with leaf components** — buttons, badges, cards, list rows. They have no structural
   dependencies, so swapping an `ion-button` for `tcnButton` touches one element.
2. **Replace form controls and overlays next** — inputs, selects, switches, dialogs — screen by
   screen.
3. **Swap the chrome last, per page** — replace `ion-header` / `ion-tab-bar` with `tcn-navbar` /
   `tcn-tabbar` only when a whole screen has otherwise moved over, to avoid the double-chrome conflict
   above.
4. **Keep Ionic's router.** touchcn ships no router; leave routing and page lifecycle to Ionic
   (`ion-router` / Angular Router / your React Router or Vue Router setup). touchcn components live *inside* those
   routed pages.

:::warning
A screen that mixes both design systems mid-migration will look inconsistent — Ionic's Material/iOS
styling and touchcn's are not pixel-identical. Migrate **screen by screen** and ship a screen only once
it is fully on one system, rather than swapping individual components across every page at once.
:::
