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 detectionimport { 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.