SuperAPI AI Directory

native-feel

General & other

View repository

Updated 10-15 19:52

About this skill

Passing as a Native App

What gives a web app away is almost never how it looks. It is that the scroll does not carry momentum, the back gesture leaves the app instead of closing the sheet, the keyboard covers the field being typed into, and the primary action sits where the platform puts something else. Fix behaviour before appearance: match the platform's gesture ownership, its latency, and its control conventions, and let the visual language stay your own. The default posture is to give the operating system back everything it already owns — scrolling, the back gesture, the keyboard, the text-selection callout — and to spend your effort only where the web genuinely has no answer. touch-input owns the mechanics of contact — target size, hover gating, tap latency — and gestures owns the physics once a finger is moving; this skill owns only whether the result reads as installed. Notch and home-indicator insets are responsive.

Detect the delivery target before writing any of this. A tab in a browser, an installed PWA, a Capacitor or Tauri shell and an Electron window each get different answers, and shipping standalone-only behaviour to a browser tab produces an app with no way back. Read the manifest, check for display-mode: standalone at runtime rather than sniffing the user agent, and see whether the project already has a platform module — if there is a usePlatform() or a Capacitor.getPlatform() in the codebase, extend it instead of adding a second detection path beside it.

Topic Reference
A control or gesture that differs per platform Open references/platform-conventions.md when you need the concrete iOS / Android / macOS / Windows expectation for a control, a modifier key, a confirmation, or a gesture.

Core Principles

  1. Mimic behaviour, never chrome. An iOS-shaped switch rendered on Android is uncanny, and a hand-drawn copy of a system font is worse than the system font. Use font-family: system-ui (with -apple-system first for older Safari) and let each platform supply its own letterforms, then match the platform's timing, gesture ownership and control placement. Exception: a product with a strong cross-platform identity — a design tool, a game, an editor — should look like itself everywhere; then commit fully and match only the gestures.

  2. Never hijack the scroll. Smooth-scroll and parallax libraries replace the compositor's momentum with per-frame maths, and the result desyncs from the finger, breaks the OS edge-swipe, and disables the scrollbar's own affordances. Use native scrolling with overscroll-behavior: contain on panels and scroll-snap-type for paging. Exception: a marketing page whose scroll is the content — marketing-pages explicitly permits what product UI does not.

  3. The platform back affordance closes exactly one layer. Android's back gesture and iOS's edge swipe must dismiss the topmost sheet, drawer or modal rather than leaving the app. Push a history entry when a dismissible layer opens and call history.back() to close it, so the OS gesture, your close button and the browser back button all take the same path. In display: standalone there is no browser back at all, so a screen with no in-app back control is a dead end. Exception: a destructive-confirm dialog should consume the back event without navigating. The app's history graph and URL-as-state are navigation's.

  1. Keep the edge gutters free. The outer strip on both sides of the screen belongs to the system gesture recogniser, so a horizontally draggable element that starts there will fight it and lose. Inset draggable rows, carousels and sliders from the screen edge, or accept that the first horizontal drag from the edge will navigate instead. Exception: none worth taking — a gesture that competes with the OS loses on every platform, and the failure is silent in desktop testing.

  2. Solve the keyboard with visualViewport, not with guesses. On iOS the keyboard does not resize the layout viewport, so position: fixed; bottom: 0 composers and submit bars end up behind it. Add interactive-widget=resizes-content to the viewport meta, drive the offset from window.visualViewport on its resize and scroll events, and where supported set navigator.virtualKeyboard.overlaysContent = true and lay out against env(keyboard-inset-height). Scroll the focused field into view yourself; the browser's attempt is frequently wrong inside a scroll container. Exception: a single centred field on a short page — let the browser handle it.

  3. Chrome the installed app, or do not install it. A half-configured manifest produces a white flash on launch and a browser-coloured status bar, which reads as a bookmark rather than an app. Set display: "standalone", a background_color matching the app shell, and theme-color per colour scheme via <meta name="theme-color" media="(prefers-color-scheme: dark)">. On desktop, display_override: ["window-controls-overlay"] plus env(titlebar-area-*) lets the title bar become part of the app. Exception: products whose value is shareable links and whose offline story is nothing should stay browser or minimal-ui — the URL bar is a feature there.

  4. Selection off on chrome, on for content. Native apps do not let you drag-select a toolbar label, and a blue smear across your own UI reads as malfunction. Set user-select: none on chrome and controls, and re-enable user-select: text on genuinely copyable content. Exception: never disable selection on anything a user might reasonably want to copy — a code block, an ID, an error message, an address. Selection styling is ui-polish.

  5. Show the platform's modifier, and bind it. Displaying Ctrl+K on a Mac is the kind of detail that instantly reveals a port. Detect the platform once at startup, render Cmd on macOS and Ctrl elsewhere, and bind the matching key — checking metaKey on macOS and ctrlKey elsewhere rather than accepting both everywhere, which silently steals a real system shortcut. Exception: do not render shortcut hints at all on a touch-only surface; they are noise where there is no keyboard.

  6. Adopt the platform's confirmation and control conventions, not your own. Where the close button sits, whether a destructive choice arrives as a bottom action sheet or a centred dialog, whether a setting takes effect immediately or on submit, whether a long-press or a right-click opens the context menu — each of these has a settled answer per platform, and getting it wrong is felt before it is noticed. Look it up in references/platform-conventions.md rather than guessing. Exception: a convention that would break your information architecture is worth overriding once, deliberately, and consistently everywhere.

Smells and Fixes

Smell Fix
A smooth-scroll library in the dependencies Delete it; native scroll plus scroll-snap-type and overscroll-behavior
Back gesture exits the app while a sheet is open Push a history entry per dismissible layer; close on popstate
Composer hidden behind the keyboard on iOS visualViewport offset, interactive-widget=resizes-content
Draggable row flush to the screen edge Inset it out of the system gesture gutter
White flash on PWA launch background_color in the manifest matching the app shell
theme-color declared once, light only One per colour scheme with a media attribute
Standalone screen with no back control Add one; there is no browser chrome to fall back on
Ctrl+K shown on macOS Detect once at startup; render Cmd, bind metaKey
user-select: none on the whole document Chrome only; content stays selectable
User-agent sniffing to detect "app mode" matchMedia('(display-mode: standalone)')

Checklist

  • Delivery target detected by display-mode, not by user agent
  • Native scrolling everywhere; overscroll-behavior: contain on panels
  • Every dismissible layer pushes history; the back gesture closes exactly one
  • No draggable element sits in the system edge gutter
  • Keyboard avoidance driven by visualViewport; focused field scrolled into view
  • Manifest sets display, background_color and per-scheme theme-color
  • Standalone builds have an in-app back affordance on every screen
  • user-select: none on chrome only; content remains copyable
  • Modifier keys and shortcut hints match the host platform
  • Control placement and confirmation style checked against references/platform-conventions.md
  • Tested as an installed app on real hardware, not only in a browser tab