tailwind-capacitor ยท diff

git:20260318.5bd0bc2 to git:20261011.86834d4

80 added, 540 removed. Audit A to A.

---
name: tailwind-capacitor
- description: Guide to using Tailwind CSS in Capacitor mobile apps. Covers mobile-first design, touch targets, safe areas, dark mode, and performance optimization. Use this skill when users want to style Capacitor apps with Tailwind.
+ description: Sets up and uses Tailwind CSS v4 in Capacitor apps - CSS-first config (`@import "tailwindcss"`, `@theme`, `@utility`, `@custom-variant`, `@source`, `@plugin`), `@tailwindcss/vite` / `@tailwindcss/postcss`, upgrading from v3 `tailwind.config.js` with `npx @tailwindcss/upgrade`, WebView browser floor (Safari 16.4+ / Chrome 111+ vs Capacitor iOS 15/16 targets), safe-area utilities built on `env(safe-area-inset-*)`, platform variants (`ios:`/`android:` via a root class), class-based dark mode, `pointer-coarse:` touch targets, and hover-on-touch behavior. Use when installing Tailwind in a Capacitor project, styles do not apply after upgrading to v4, colors look wrong on older iPhones, or building mobile layouts with Tailwind. Do not use for Konsta UI components (konsta-ui), Ionic theming (ionic-design), native system bar / edge-to-edge config (safe-area-handling), or keyboard overlap (capacitor-keyboard).
---
- # Tailwind CSS for Capacitor Apps
-
- Build beautiful mobile apps with Tailwind CSS and Capacitor.
-
- ## When to Use This Skill
-
- - User is using Tailwind in Capacitor app
- - User asks about mobile styling
- - User needs responsive mobile design
- - User wants dark mode with Tailwind
- - User needs safe area handling
-
- ## Getting Started
-
- ### Installation
-
- ```bash
- npm install -D tailwindcss postcss autoprefixer
- npx tailwindcss init -p
- ```
-
- ### Configuration
-
- ```javascript
- // tailwind.config.js
- /** @type {import('tailwindcss').Config} */
- export default {
- content: [
- './index.html',
- './src/**/*.{js,ts,jsx,tsx,vue,svelte}',
- ],
- theme: {
- extend: {
- // Mobile-first spacing
- spacing: {
- 'safe-top': 'env(safe-area-inset-top)',
- 'safe-bottom': 'env(safe-area-inset-bottom)',
- 'safe-left': 'env(safe-area-inset-left)',
- 'safe-right': 'env(safe-area-inset-right)',
- },
- // Minimum touch targets (44px)
- minHeight: {
- 'touch': '44px',
- },
- minWidth: {
- 'touch': '44px',
- },
- },
- },
- plugins: [],
- };
- ```
-
- ### Import Styles
-
- ```css
- /* src/index.css */
- @tailwind base;
- @tailwind components;
- @tailwind utilities;
-
- /* Mobile-specific base styles */
- @layer base {
- html {
- /* Prevent text size adjustment on orientation change */
- -webkit-text-size-adjust: 100%;
- /* Smooth scrolling */
- scroll-behavior: smooth;
- /* Prevent pull-to-refresh on overscroll */
- overscroll-behavior: none;
- }
-
- body {
- /* Prevent text selection on long press */
- -webkit-user-select: none;
- user-select: none;
- /* Disable callout on long press */
- -webkit-touch-callout: none;
- /* Prevent elastic scrolling on iOS */
- position: fixed;
- width: 100%;
- height: 100%;
- overflow: hidden;
- }
-
- /* Enable text selection in inputs */
- input, textarea {
- -webkit-user-select: text;
- user-select: text;
- }
- }
- ```
-
- ## Safe Area Handling
-
- ### Utility Classes
-
- ```javascript
- // tailwind.config.js
- theme: {
- extend: {
- padding: {
- 'safe': 'env(safe-area-inset-bottom)',
- 'safe-t': 'env(safe-area-inset-top)',
- 'safe-b': 'env(safe-area-inset-bottom)',
- 'safe-l': 'env(safe-area-inset-left)',
- 'safe-r': 'env(safe-area-inset-right)',
- },
- margin: {
- 'safe': 'env(safe-area-inset-bottom)',
- 'safe-t': 'env(safe-area-inset-top)',
- 'safe-b': 'env(safe-area-inset-bottom)',
- },
- },
- },
- ```
-
- ### Usage in Components
+ # Tailwind CSS in Capacitor Apps
- ```tsx
- // Header with safe area
- function Header() {
- return (
- <header className="
- fixed top-0 left-0 right-0
- pt-safe-t /* Padding for notch */
- bg-white dark:bg-gray-900
- border-b border-gray-200
- ">
- <div className="px-4 h-14 flex items-center">
- <h1 className="font-semibold">App Title</h1>
- </div>
- </header>
- );
- }
+ ## When to Use
- // Footer with safe area
- function Footer() {
- return (
- <footer className="
- fixed bottom-0 left-0 right-0
- pb-safe-b /* Padding for home indicator */
- bg-white dark:bg-gray-900
- border-t border-gray-200
- ">
- <div className="px-4 h-14 flex items-center justify-around">
- <button className="min-h-touch min-w-touch">Home</button>
- <button className="min-h-touch min-w-touch">Search</button>
- <button className="min-h-touch min-w-touch">Profile</button>
- </div>
- </footer>
- );
- }
+ TRIGGER when:
+ - Adding Tailwind to a Capacitor app (Vite, Angular, Next static export, etc.)
+ - Upgrading Tailwind v3 -> v4, or a v4 install produces no styles
+ - `tailwind.config.js` edits have no effect (v4 is CSS-first)
+ - Colors / gradients broken or transparent on older iOS devices
+ - Safe-area, dark mode, platform-specific, or touch-target utilities
- // Main content
- function Main() {
- return (
- <main className="
- pt-safe-t /* Account for header + notch */
- pb-safe-b /* Account for footer + home indicator */
- h-screen
- overflow-y-auto
- overscroll-none
- ">
- {/* Content */}
- </main>
- );
- }
- ```
+ Do not use:
+ - Konsta UI setup/components: `konsta-ui`
+ - Ionic components and `--ion-*` variables: `ionic-design`
+ - SystemBars / Android edge-to-edge / status bar plugin: `safe-area-handling`
+ - Inputs hidden by keyboard: `capacitor-keyboard`
+ - Adding Capacitor to a framework: `framework-to-capacitor`
- ## Touch-Friendly Design
+ ## Version facts (October 2026)
- ### Minimum Touch Targets
+ - `tailwindcss` 4.x is `latest` (4.3.x); 3.4.x on `v3-lts`.
+ - v4 is configured in CSS. `tailwind.config.js` is not read unless you add `@config "./tailwind.config.js";`.
+ - v4 targets **Safari 16.4+, Chrome 111+, Firefox 128+** (uses `@property`, `color-mix()`, cascade layers, oklch).
+ - Capacitor 8 supports iOS 15+, Capacitor 9 iOS 16+. WKWebView = the device's Safari version. **Devices on iOS 15.x - 16.3 render v4 styles incorrectly.** Android System WebView auto-updates, so Chrome 111+ is usually met (Capacitor 9 minSdk 26).
- ```tsx
- // Apple HIG recommends 44x44pt minimum
- function TouchableButton() {
- return (
- <button className="
- min-h-[44px] min-w-[44px]
- px-4 py-3
- flex items-center justify-center
- active:bg-gray-100
- rounded-lg
- ">
- Tap Me
- </button>
- );
- }
+ Decision: if the iOS deployment target (or real user base) includes iOS < 16.4, ask the user: stay on Tailwind 3.4, or raise the minimum OS. Do not silently ship v4.
- // Icon button with proper touch target
- function IconButton() {
- return (
- <button className="
- h-11 w-11 /* 44px */
- flex items-center justify-center
- rounded-full
- active:bg-gray-100
- ">
- <svg className="w-6 h-6" /> {/* Icon smaller than touch area */}
- </button>
- );
- }
- ```
+ ## Workflow
- ### Touch Feedback
+ 1. **Inspect**: `npm ls tailwindcss`, `tailwind.config.*`, `postcss.config.*`, bundler config, main CSS file, `ios/App/App.xcodeproj` `IPHONEOS_DEPLOYMENT_TARGET`, presence of Ionic or Konsta.
+ 2. **New install** (below) or **upgrade**: [references/v3-to-v4.md](references/v3-to-v4.md).
+ 3. **Mobile utilities** (safe areas, platform variants, touch, dark mode): [references/mobile-patterns.md](references/mobile-patterns.md).
+ 4. **Verify** (below).
- ```css
- /* Add to index.css */
- @layer utilities {
- .touch-feedback {
- @apply transition-colors duration-75;
- }
+ Only load a reference when its topic is in play.
- .touch-feedback:active {
- @apply bg-black/5 dark:bg-white/5;
- }
- }
- ```
+ ## Install (Vite: React, Vue, Svelte, Solid)
- ```tsx
- <button className="touch-feedback p-4 rounded-lg">
- With Feedback
- </button>
+ ```bash
+ npm i tailwindcss @tailwindcss/vite
```
- ### Disable Hover on Touch
-
- ```javascript
- // tailwind.config.js
- module.exports = {
- future: {
- hoverOnlyWhenSupported: true, // Disables hover on touch devices
- },
- };
+ ```ts
+ // vite.config.ts
+ import { defineConfig } from 'vite';
+ import tailwindcss from '@tailwindcss/vite';
+ export default defineConfig({ plugins: [tailwindcss() /* + framework plugin */] });
```
- Or use media query:
-
```css
- @media (hover: hover) {
- .hover-only:hover {
- @apply bg-gray-100;
- }
- }
- ```
-
- ## Dark Mode
-
- ### System Dark Mode
-
- ```javascript
- // tailwind.config.js
- module.exports = {
- darkMode: 'media', // or 'class' for manual control
- };
- ```
-
- ### Manual Dark Mode
-
- ```javascript
- // tailwind.config.js
- module.exports = {
- darkMode: 'class',
- };
- ```
-
- ```typescript
- // theme.ts
- import { Preferences } from '@capacitor/preferences';
-
- type Theme = 'light' | 'dark' | 'system';
-
- async function setTheme(theme: Theme) {
- await Preferences.set({ key: 'theme', value: theme });
-
- if (theme === 'system') {
- const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
- document.documentElement.classList.toggle('dark', prefersDark);
- } else {
- document.documentElement.classList.toggle('dark', theme === 'dark');
- }
- }
-
- // Listen for system changes
- window.matchMedia('(prefers-color-scheme: dark)')
- .addEventListener('change', (e) => {
- const theme = await Preferences.get({ key: 'theme' });
- if (theme.value === 'system') {
- document.documentElement.classList.toggle('dark', e.matches);
- }
- });
- ```
-
- ### Dark Mode Components
-
- ```tsx
- function Card() {
- return (
- <div className="
- bg-white dark:bg-gray-800
- border border-gray-200 dark:border-gray-700
- rounded-xl
- shadow-sm dark:shadow-none
- ">
- <h3 className="text-gray-900 dark:text-white">
- Card Title
- </h3>
- <p className="text-gray-600 dark:text-gray-400">
- Card content
- </p>
- </div>
- );
- }
- ```
-
- ## Mobile Patterns
-
- ### Pull to Refresh Container
-
- ```tsx
- function PullToRefresh({ onRefresh, children }) {
- return (
- <div className="
- h-full
- overflow-y-auto
- overscroll-contain
- touch-pan-y
- ">
- {children}
- </div>
- );
- }
- ```
-
- ### Bottom Sheet
-
- ```tsx
- function BottomSheet({ isOpen, onClose, children }) {
- return (
- <>
- {/* Backdrop */}
- <div
- className={`
- fixed inset-0
- bg-black/50
- transition-opacity duration-300
- ${isOpen ? 'opacity-100' : 'opacity-0 pointer-events-none'}
- `}
- onClick={onClose}
- />
-
- {/* Sheet */}
- <div
- className={`
- fixed left-0 right-0 bottom-0
- bg-white dark:bg-gray-900
- rounded-t-2xl
- pb-safe-b
- transition-transform duration-300 ease-out
- ${isOpen ? 'translate-y-0' : 'translate-y-full'}
- `}
- >
- {/* Handle */}
- <div className="flex justify-center py-2">
- <div className="w-10 h-1 bg-gray-300 rounded-full" />
- </div>
-
- {children}
- </div>
- </>
- );
- }
- ```
-
- ### Swipe Actions
-
- ```tsx
- function SwipeableItem({ children, onDelete }) {
- return (
- <div className="relative overflow-hidden">
- {/* Background action */}
- <div className="
- absolute inset-y-0 right-0
- flex items-center
- bg-red-500
- px-4
- ">
- <span className="text-white">Delete</span>
- </div>
-
- {/* Foreground content */}
- <div className="
- relative
- bg-white dark:bg-gray-800
- transform transition-transform
- active:cursor-grabbing
- ">
- {children}
- </div>
- </div>
- );
- }
- ```
+ /* src/styles.css */
+ @import "tailwindcss";
- ### Fixed Header with Blur
+ @custom-variant dark (&:where(.dark, .dark *));
+ @custom-variant ios (&:where(.plt-ios, .plt-ios *));
+ @custom-variant android (&:where(.plt-android, .plt-android *));
- ```tsx
- function BlurHeader() {
- return (
- <header className="
- fixed top-0 left-0 right-0
- pt-safe-t
- bg-white/80 dark:bg-gray-900/80
- backdrop-blur-lg
- border-b border-gray-200/50
- z-50
- ">
- <div className="h-14 px-4 flex items-center">
- <h1 className="font-semibold">Title</h1>
- </div>
- </header>
- );
+ @theme {
+ --color-brand: oklch(0.55 0.2 265);
+ --font-sans: system-ui, -apple-system, "Roboto", sans-serif;
}
- ```
- ## Performance Optimization
-
- ### Reduce Bundle Size
-
- ```javascript
- // tailwind.config.js
- module.exports = {
- content: [/* ... */],
- // Only include used utilities
- safelist: [], // Add dynamic classes here if needed
- };
- ```
-
- ### GPU Acceleration
-
- ```tsx
- // Use transform for animations (GPU accelerated)
- <div className="
- transform transition-transform duration-200
- hover:scale-105
- will-change-transform
- ">
- Animated Element
- </div>
+ @utility pt-safe { padding-top: env(safe-area-inset-top); }
+ @utility pb-safe { padding-bottom: env(safe-area-inset-bottom); }
+ @utility px-safe { padding-left: env(safe-area-inset-left); padding-right: env(safe-area-inset-right); }
```
- ### Avoid Layout Thrashing
-
- ```tsx
- // BAD: Causes reflow
- <div className="w-full h-auto">
+ Other toolchains: PostCSS (Angular, webpack, Next) -> `npm i tailwindcss @tailwindcss/postcss` and `postcss.config.mjs` `{ plugins: { "@tailwindcss/postcss": {} } }`; standalone -> `@tailwindcss/cli`. Angular CLI 17+ reads `.postcssrc.json`.
- // GOOD: Fixed dimensions
- <div className="w-full h-48">
+ ```ts
+ // main.ts: platform class for ios:/android: variants
+ import { Capacitor } from '@capacitor/core';
+ document.documentElement.classList.add(`plt-${Capacitor.getPlatform()}`); // plt-ios | plt-android | plt-web
```
- ## Component Examples
-
- ### Mobile List Item
+ Add `viewport-fit=cover` to the viewport meta or the safe-area utilities resolve to 0.
- ```tsx
- function ListItem({ title, subtitle, image, onClick }) {
- return (
- <button
- onClick={onClick}
- className="
- w-full
- flex items-center gap-4
- px-4 py-3
- min-h-[60px]
- active:bg-gray-50 dark:active:bg-gray-800
- text-left
- "
- >
- {image && (
- <img
- src={image}
- className="w-12 h-12 rounded-full object-cover"
- alt=""
- />
- )}
- <div className="flex-1 min-w-0">
- <p className="font-medium text-gray-900 dark:text-white truncate">
- {title}
- </p>
- {subtitle && (
- <p className="text-sm text-gray-500 truncate">
- {subtitle}
- </p>
- )}
- </div>
- <svg className="w-5 h-5 text-gray-400" />
- </button>
- );
- }
- ```
+ ## Traps
- ### Mobile Button
+ - Content detection is automatic in v4 (respects `.gitignore`). Classes in `node_modules` or outside the project root need `@source "../path";`. Dynamic class names (`` `bg-${color}-500` ``) are never generated; map to full class strings.
+ - v4 `hover:` only applies on devices with `(hover: hover)`, so sticky hover on touch is solved by default. Use `active:` for press feedback.
+ - v4 defaults changed: border color is `currentColor`, `ring` is 1px, `shadow-sm`/`shadow` and `rounded-sm`/`rounded` scales shifted, `outline-none` -> `outline-hidden`. The upgrade tool renames most; review visually.
+ - Ionic + Tailwind: Preflight resets Ionic typography. Import Tailwind without Preflight (`@layer theme, base, components, utilities; @import "tailwindcss/theme.css" layer(theme); @import "tailwindcss/utilities.css" layer(utilities);`) or load Ionic CSS after Tailwind.
+ - Konsta UI v5 already imports Tailwind theme pieces via `konsta/<fw>/theme.css`; follow `konsta-ui` instead of duplicating setup.
+ - Do not use `100vh` for full-screen layouts; use `h-dvh` (v4 has `dvh`/`svh`/`lvh` utilities) or a flex shell.
+ - Android WebView < 140 reports safe-area `env()` as 0; Capacitor 8 SystemBars injects `--safe-area-inset-*`. For Android robustness, use `@utility pt-safe { padding-top: var(--safe-area-inset-top, env(safe-area-inset-top)); }` (see `safe-area-handling`).
- ```tsx
- function MobileButton({ children, variant = 'primary', ...props }) {
- const variants = {
- primary: 'bg-blue-500 text-white active:bg-blue-600',
- secondary: 'bg-gray-100 text-gray-900 active:bg-gray-200',
- danger: 'bg-red-500 text-white active:bg-red-600',
- };
+ ## Verification
- return (
- <button
- className={`
- w-full
- min-h-[48px]
- px-6 py-3
- font-semibold
- rounded-xl
- transition-colors duration-75
- disabled:opacity-50
- ${variants[variant]}
- `}
- {...props}
- >
- {children}
- </button>
- );
- }
- ```
+ 1. `npm run build` succeeds; built CSS contains your utilities: `grep -c "pt-safe" dist/assets/*.css`.
+ 2. `npx cap sync` and run on: oldest supported iOS version (check colors, gradients, shadows render), current iPhone, Android 15/16.
+ 3. Toggle dark mode class and OS appearance; check `ios:`/`android:` variants by inspecting `<html class>`.
+ 4. Leftovers after upgrade: `grep -rn "@tailwind base\|@tailwind components\|@tailwind utilities\|require('tailwindcss')" src postcss.config.* 2>/dev/null` returns nothing.
- ### Mobile Input
+ ## Error Handling
- ```tsx
- function MobileInput({ label, error, ...props }) {
- return (
- <label className="block">
- {label && (
- <span className="text-sm font-medium text-gray-700 dark:text-gray-300 mb-1 block">
- {label}
- </span>
- )}
- <input
- className={`
- w-full
- px-4 py-3
- text-base /* Prevents iOS zoom */
- bg-gray-50 dark:bg-gray-800
- border rounded-xl
- placeholder-gray-400
- focus:outline-none focus:ring-2 focus:ring-blue-500
- ${error
- ? 'border-red-500'
- : 'border-gray-200 dark:border-gray-700'
- }
- `}
- {...props}
- />
- {error && (
- <span className="text-sm text-red-500 mt-1 block">{error}</span>
- )}
- </label>
- );
- }
- ```
+ | Error / symptom | Cause | Fix |
+ |-----------------|-------|-----|
+ | `[postcss] It looks like you're trying to use tailwindcss directly as a PostCSS plugin. The PostCSS plugin has moved to a separate package` | v3-style PostCSS config with v4 | Use `@tailwindcss/postcss` (or `@tailwindcss/vite`) |
+ | No styles at all | Missing `@import "tailwindcss"` or bundler plugin | Add both |
+ | `Cannot apply unknown utility class` in `@apply` | Custom class not defined via `@utility`, or CSS module / Vue `<style>` without theme reference | Define with `@utility`; add `@reference "../styles.css";` in the scoped style |
+ | `tailwind.config.js` theme changes ignored | v4 does not auto-load JS config | Move to `@theme`, or add `@config` |
+ | Transparent/missing colors on some iPhones | iOS < 16.4 lacks features v4 uses | Raise min iOS or stay on 3.4 |
+ | `dark:` follows OS, ignores toggle | Default dark variant is media-based | Add `@custom-variant dark (&:where(.dark, .dark *));` |
+ | Upgrade tool fails: requires Node | `@tailwindcss/upgrade` needs Node 20+ | Upgrade Node (Capacitor 9 needs 24+ anyway) |
## Resources
- - Tailwind CSS Documentation: https://tailwindcss.com/docs
- - Tailwind Mobile Patterns: https://tailwindui.com/
- - CSS Safe Area Guide: https://webkit.org/blog/7929/designing-websites-for-iphone-x/
+ - Tailwind v4 install (Vite): https://tailwindcss.com/docs/installation/using-vite
+ - Upgrade guide: https://tailwindcss.com/docs/upgrade-guide
+ - Theme variables: https://tailwindcss.com/docs/theme
+ - Browser support: https://tailwindcss.com/docs/compatibility
+ - Dark mode: https://tailwindcss.com/docs/dark-mode