Skip to content
touchcn
Esc
navigateopen⌘Jpreview
On this page

Capacitor

Use touchcn in a Capacitor Angular, React, or Vue app — safe areas, status bar overlay, platform detection, and keyboard behavior.

touchcn is a web-first component library, so inside a Capacitor app it runs exactly as it does in the browser — it renders in the native WebView (WKWebView on iOS, Android System WebView on Android). The install, Tailwind wiring, and provider setup are unchanged from Getting started; this guide covers only what a native shell adds on top: drawing under the system chrome, syncing the status bar, and platform detection.

Install

Follow Getting started to install the engine packages, wire up Tailwind v4, register the provider, and add components. Nothing about that changes for Capacitor — the same @touchcn/* packages and the same CLI-copied sources run in the WebView.

Draw under the system chrome (viewport-fit=cover)

touchcn’s navbar, tabbar, and bottom-anchored sheets reserve space for the device’s safe areas using the CSS env(safe-area-inset-*) variables — the iOS navbar adds env(safe-area-inset-top) to its top padding, the tabbar and sheets add env(safe-area-inset-bottom) to their bottom padding, and the .tcn-safe-* helper classes expose the same insets.

Those variables only resolve to non-zero values when the viewport is told to extend into the safe areas. Add viewport-fit=cover to the viewport meta tag in your index.html:

<meta
  name="viewport"
  content="viewport-fit=cover, width=device-width, initial-scale=1.0"
/>

Without viewport-fit=cover, env(safe-area-inset-*) resolves to 0 and touchcn’s chrome sits flush against the screen edges — sliding under the notch, Dynamic Island, or home indicator.

Status bar overlay

For touchcn’s safe-area chrome to own the top inset, the WebView should draw under the status bar rather than being pushed below it. Install the official status bar plugin:

npm install @capacitor/status-bar

Enable overlay mode in capacitor.config.ts so it applies at launch:

import type { CapacitorConfig } from '@capacitor/cli';

const config: CapacitorConfig = {
  appId: 'com.example.app',
  appName: 'My App',
  webDir: 'dist',
  plugins: {
    StatusBar: {
      overlaysWebView: true,
      style: 'DEFAULT',
    },
  },
};

export default config;

With overlaysWebView: true, content draws beneath the status bar and touchcn’s env(safe-area-inset-top) padding on the navbar keeps it clear.

Sync the status bar style with the color scheme

The @capacitor/status-bar Style enum is named by the text color, not the background: Style.Dark renders light text (for a dark background), Style.Light renders dark text (for a light background), and Style.Default follows the device appearance. Drive it from touchcn’s resolved color scheme so the status bar text stays legible when the theme flips:

import { effect, inject, Injectable } from '@angular/core';
import { StatusBar, Style } from '@capacitor/status-bar';
import { TouchcnAppearance } from '@touchcn/angular';

@Injectable({ providedIn: 'root' })
export class StatusBarSync {
  private readonly appearance = inject(TouchcnAppearance);

  constructor() {
    // `appearance.scheme` is a signal — the effect re-runs whenever it changes.
    effect(() => {
      StatusBar.setStyle({ style: this.appearance.scheme() === 'dark' ? Style.Dark : Style.Light });
    });
  }
}
import { useEffect } from 'react';
import { StatusBar, Style } from '@capacitor/status-bar';
import { useTouchcnAppearance } from '@touchcn/react';

export function useStatusBarSync() {
  const { scheme } = useTouchcnAppearance();

  useEffect(() => {
    StatusBar.setStyle({ style: scheme === 'dark' ? Style.Dark : Style.Light });
  }, [scheme]);
}
import { watchEffect } from 'vue';
import { StatusBar, Style } from '@capacitor/status-bar';
import { useTouchcnAppearance } from '@touchcn/vue';

export function useStatusBarSync() {
  // `scheme` is a ref — the effect re-runs whenever it changes.
  const { scheme } = useTouchcnAppearance();

  watchEffect(() => {
    StatusBar.setStyle({ style: scheme.value === 'dark' ? Style.Dark : Style.Light });
  });
}

Platform detection

touchcn’s detectPlatform() reads navigator.userAgent. In a Capacitor WebView that user-agent carries the native platform: iOS reports iPhone / iPad, Android reports Android. As a result the theme resolves correctly with no extra configuration — iOS renders the ios (liquid-glass) theme, Android renders the md (Material 3) theme, and the provider toggles the .ios / .md classes on <html> automatically. iPadOS is handled too (it is detected via navigator.platform and maxTouchPoints).

To force a theme regardless of the device — for a screenshot build, a design preview, or a deliberate single-platform look — override it explicitly:

import { inject } from '@angular/core';
import { TouchcnAppearance } from '@touchcn/angular';

const appearance = inject(TouchcnAppearance);
appearance.setTheme('ios'); // force 'ios' or 'md'; pass null to fall back to detection
import { TouchcnProvider } from '@touchcn/react';

// Force the theme for the whole subtree; omit `theme` to auto-detect.
<TouchcnProvider theme="ios">
  <App />
</TouchcnProvider>
<!-- App.vue — force the theme for the whole app; omit `theme` to auto-detect. -->
<script setup lang="ts">
import { provideTouchcn } from '@touchcn/vue';

provideTouchcn({ theme: 'ios' }); // force 'ios' or 'md'
</script>

Keyboard

The @capacitor/keyboard plugin controls how the WebView reacts when the software keyboard appears. Its resize config accepts native (the default — the whole WebView resizes, so vh-based heights shrink), body, ionic, or none.

With the default native mode, the viewport shrinks when the keyboard opens: a bottom-anchored tcn-tabbar and any position: fixed touchcn overlays move up with the new viewport bottom, and the top tcn-navbar stays pinned. That is usually the behavior you want for a focused input above the keyboard. If you switch to body, relative units are left untouched (the env(safe-area-inset-*) insets still apply), so verify that overlays and the tabbar still land where you expect. On Android, resizeOnFullScreen: true is the documented workaround for the WebView not resizing while the status bar overlays the app.

Last updated on August 16, 2026

Was this page helpful?