---
url: https://native-shell.com/docs
description: "What native-shell is: a shell that lets native iOS and Android apps load UI modules after they ship, with standalone apps as a second use."
---

# Introduction

native-shell (nsh for short) is designed for **AI-generated, dynamically delivered native UI**. A model writes a small module in TSX, and a native iOS or Android app that has already shipped loads it at run time and shows it with the platform's own views, inside a sandbox the app controls. The same mechanism makes native apps extensible in general — plugins, features, whole screens delivered after release. Mobile clients are the first target; desktop clients come next, through a GPUI backend that is in development.

> [!IMPORTANT]
> nsh is not a framework for writing your whole app in TypeScript. The app stays a native app, built and released the way it always was; nsh is the layer inside it for UI that is generated, changed or added after it ships.

## Why it exists

**First: UI generated by AI.** An assistant inside your app can answer with an interface rather than with text. A model writes a small TSX module (a form, a chart, a booking card), your server checks, builds and signs it, and the app downloads it and shows it in an `NshAppView`: native UI, sandboxed, limited to what you granted, and with no app update. nsh is built around this use, so its design is AI friendly throughout:

- **A format models write well.** A module is one TSX file using signals, a handful of host elements and Tailwind classes — the vocabulary models already know from the web, with no build configuration and no `node_modules`.
- **A precise, generated contract.** Every element, prop, style key and module has TypeScript declarations generated from the Rust sources (`.nsh/types`), so generated code can be type-checked, and a model can be given the exact API instead of guessing. These docs are also served as plain text for models ([`llms.txt`](/llms.txt), [`llms-full.txt`](/llms-full.txt)).
- **Checkable before it ships.** `nsh trace` runs a module headless, without a device, and reports errors and the views it produced; build errors come with a code frame. A generation loop can compile, run, read the result and retry. The compiler also runs in-process as a library (`nsh_bundler::compile_module`) and in the browser ([Playground](/playground)).
- **Safe to run when it is not trusted.** Generated code is untrusted code. Each module runs in its own QuickJS VM with memory, call-time and node limits, reaches only the capabilities the host grants, and arrives as a signed package the app verifies first.
- **Native without native code.** The output is real UIKit and Android views, with the system's text, input and accessibility, yet the model never writes Swift, Kotlin or Java, and the app never needs a new release.

![From a prompt to native UI: a model writes a TSX module; your server builds, checks and signs it; across the trust boundary, the app verifies the package, runs it in a sandbox with the limits and capabilities it set, and shows UIKit or Android views.](https://native-shell.com/diagrams/ai-flow-light.svg)

**Second: extending native apps, and delivering UI dynamically.** The same mechanism serves any module, not only generated ones. The app embeds an `NshAppView` (iOS) or `NshView` (Android) wherever a module should appear, and nsh runs the module inside it:

- **Delivered after release.** A module is a signed package (`.nshpkg`) the app downloads at run time. The app checks its signature against the keys it trusts and against a revocation list before running it ([Signed packages](https://native-shell.com/docs/signed-packages.md)). A change needs no new binary and no store release.
- **The host stays in control.** Each module runs in its own sandboxed VM, with memory, time and node limits, and reaches only what the host grants: network hosts, web pages, the status bar ([Sandbox and capabilities](https://native-shell.com/docs/sandbox-and-capabilities.md)). Several modules can run side by side; there are no global singletons.
- **It looks like the rest of the app.** Modules render real UIKit and Android views, so text, input, accessibility and scrolling are the system's own. The host can register its own native views — a map, a player, a design-system component — for modules to use ([Native components](https://native-shell.com/docs/native-components.md)).

**Third: standalone apps.** The same modules can also be a whole app. `nsh package` turns them into an installable iOS or Android app with the prebuilt runtime, which is also the quickest way to try nsh: the [getting started](https://native-shell.com/docs/getting-started.md) guide does exactly that. It is a supported use, but not the one nsh is designed around.

## Why native-shell, next to GPUI Shell

[Longbridge](https://longbridge.com) is building nsh as the plugin layer of its mobile client, and its desktop client needs the same layer. [GPUI Shell](https://gpui-kit.com/shell) makes [GPUI](https://gpui-kit.com) desktop apps extensible in JavaScript, and the first idea was to use it on phones too. It does not fit there, because its modules are drawn by GPUI itself, on the GPU, not with the platform's views:

- **System behavior would have to be rebuilt.** A self-drawn surface inside a native screen has none of the platform's text selection, nor the selection menu that comes with it (Copy, Share, Translate, Look Up, Select All), nor the system's input methods, accessibility and new visual materials each iOS release brings. Recreating them in a GPU renderer is a large, never-finished job, and the copies drift from the real thing.
- **Mobile teams would have to adopt a new UI stack.** Running a self-drawn layer at scale in an existing mobile client means its iOS and Android teams take on an unfamiliar renderer, its compatibility issues across OS versions and devices, and its bugs.

nsh therefore renders modules with UIKit and Android views, so a module behaves like the rest of the app because it is made of the same views: one module format and one runtime, on UIKit and Android views today, and on GPUI for desktop clients once its backend lands, so a module written once can run in all three clients.

The two are positioned differently. GPUI Shell is for extending GPUI desktop applications; native-shell is for extending native mobile apps, and through the GPUI backend, desktop clients as well. Native macOS and Windows backends (AppKit, WinUI) are not a goal for now.

## A module

A module is TypeScript and JSX:

```tsx
import { mount, signal, Text, View } from "nsh";

function Counter() {
  const count = signal(0);
  return (
    <View style={{ flex: 1, alignItems: "center", justifyContent: "center", gap: 12 }}>
      <Text style={{ fontSize: 48, fontWeight: "700" }}>{count()}</Text>
      <View
        onPress={() => count.update((n) => n + 1)}
        activeOpacity={0.6}
        class="rounded-lg bg-blue-600 px-5 py-3"
      >
        <Text class="text-white text-lg">Tap</Text>
      </View>
    </View>
  );
}

mount(Counter);
```

`<View>` becomes a `UIView` or an Android `ViewGroup`, `<Text>` a native text view, and the button's press feedback (`activeOpacity`) runs on the main thread without a round trip to JavaScript. When you tap, `count.update` changes one signal, and only the text that reads `count()` is updated: the component function does not run again.

The host app shows it with a few lines of native code ([iOS](https://native-shell.com/docs/embedding-ios.md), [Android](https://native-shell.com/docs/embedding-android.md)):

```swift
let app = NshAppView()                         // a UIView: place it anywhere
try app.grantCapabilities(#"{ "network": { "hosts": ["api.example.com"] } }"#)
let info = try await app.loadPackage(at: downloadedFile, trust: trust)   // verified, then run
```

Only the module's code is JavaScript. Everything it calls into — the reactive graph, the renderer, layout, text and every module it imports — is native, written in Rust.

## How a module runs

A module (an "app" in the CLI's terms) is a set of `.tsx` / `.ts` files with an entry, usually `App.tsx`. The toolchain is a single `nsh` binary:

1. **Build.** `nsh` bundles the module's files (with rolldown and Oxc) and compiles the JSX with its own compiler. Imports of `nsh` and `nsh/*` stay imports: the runtime serves them natively, so the bundle holds only your code. The bundle is compiled to QuickJS bytecode.
2. **Run.** On the device, the engine thread runs the bytecode in [QuickJS](https://github.com/quickjs-ng/quickjs). Compiled JSX calls the native renderer directly; changes are collected during one JavaScript turn, laid out with [Taffy](https://github.com/DioxusLabs/taffy) (flexbox and CSS grid), and handed to the main thread as one batch of view operations per frame.
3. **Render.** The main thread applies the batch to native views. Text is measured and laid out once on the engine thread (CoreText on Apple platforms, `StaticLayout` on Android) and only drawn on the main thread.

Because JavaScript only describes the UI and handles events, the work done in the interpreter is small. Parsing styles, resolving Tailwind classes, diffing, layout and text all happen in Rust.

> [!NOTE]
> The main thread never waits for JavaScript. Questions the system asks synchronously (may this input accept another character, may the user swipe back, may this sheet close) are answered natively from props you set beforehand, such as `maxLength`, `preventBack` or `dismissible`. Events reach your code afterwards.

## What you get

- **Embedding.** `NshAppView` (Swift) and `NshView` (Java/Kotlin) put a module anywhere in a native screen, as many as you like ([iOS](https://native-shell.com/docs/embedding-ios.md), [Android](https://native-shell.com/docs/embedding-android.md)).
- **Signed, revocable delivery.** Ed25519-signed packages, release keys certified by an offline root key, revocation lists ([Signed packages](https://native-shell.com/docs/signed-packages.md)).
- **A sandbox the host configures.** VM limits and capabilities granted per view ([Sandbox and capabilities](https://native-shell.com/docs/sandbox-and-capabilities.md)).
- **The host's own views in modules.** Register native components from Swift, Java or Rust ([Native components](https://native-shell.com/docs/native-components.md)).
- **Native views.** Embedding a native map, web view or video player has no layering problems, accessibility uses the platform's own tree, and text input is the system's.
- **Fine-grained reactivity.** [Signals](https://native-shell.com/docs/reactivity.md) like Solid's, implemented natively. Components run once; only the expressions that read a changed signal run again.
- **Styles and Tailwind**, resolved natively: flexbox, CSS grid, shadows, gradients, filters, transitions ([Styling](https://native-shell.com/docs/styling.md), [Tailwind](https://native-shell.com/docs/tailwind.md)).
- **A mobile UI set** (`nsh/ui/mobile`) whose components are written in Rust and look the same on iOS and Android: [Button](https://native-shell.com/components/button.md), [Card](https://native-shell.com/components/card.md), [ListView](https://native-shell.com/components/list-view.md), [navigation](https://native-shell.com/components/navigation.md), and more.
- **One binary toolchain.** No Node.js and no `node_modules`; `nsh` builds modules, writes their type declarations, serves them with hot module replacement, packages and signs them.

## Where it runs

| Platform | Status | Notes |
|---|---|---|
| iOS | Supported (iOS 15.0+) | Devices and the simulator; embedded with `NativeShell.xcframework` |
| Android | Supported (API 28, Android 9+) | `arm64-v8a` and `x86_64`; embedded with `nsh.aar` |
| Mac Catalyst | Development only | The iPhone-sized window `nsh run` opens on a Mac |
| Web (WebAssembly) | Demos only | Runs the examples on this site, drawn with the DOM rather than native views ([Web](https://native-shell.com/docs/web.md)); not a target to ship to |
| Desktop (GPUI) | In development | A [GPUI](https://gpui-kit.com) backend, so the same modules and runtime can build dedicated desktop clients |

![One module, many hosts: the same bundle and Rust runtime on iOS (UIKit, supported), Android (Android views, supported), desktop (GPUI, in development) and the web (WebAssembly to the DOM, for this site's demos only).](https://native-shell.com/diagrams/platforms-light.svg)

## How it compares

The usual ways to put UI into a mobile client after release are web views, server-driven UI described in JSON, and mini-program containers. An nsh module renders native views where a web view renders a page, carries its own state and logic where a server-driven schema can only recombine built-in components, and runs in a sandbox the host configures in code. [Comparing approaches](https://native-shell.com/docs/comparison.md) goes through each.

## Next steps

- [Embedding in iOS apps](https://native-shell.com/docs/embedding-ios.md) and [Embedding in Android apps](https://native-shell.com/docs/embedding-android.md): host modules in your app.
- [Signed packages](https://native-shell.com/docs/signed-packages.md) and [Sandbox and capabilities](https://native-shell.com/docs/sandbox-and-capabilities.md): deliver modules and decide what they may do.
- [Installation](https://native-shell.com/docs/installation.md) and [Getting started](https://native-shell.com/docs/getting-started.md): get `nsh` and write a first module (run as a standalone app).
- [JSX and components](https://native-shell.com/docs/jsx-and-components.md) and [Reactivity](https://native-shell.com/docs/reactivity.md): the two ideas everything else builds on.
