---
url: https://nsh.dev/docs
description: "What Native Shell is: native UI from TSX modules on iOS, Android, macOS, Windows and Linux, loaded by apps after they ship, signed and sandboxed."
---

# 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 app loads it at run time and shows it with the platform's own UI, inside a sandbox the app controls. The same mechanism makes native apps extensible in general — plugins, features, whole screens delivered after release.

NSH runs on phones and desktops alike: iOS (UIKit), Android (Android views), macOS (AppKit), Windows (Win32 windows drawn with Direct2D) and Linux (GTK 4). Each platform has a native backend of its own, and the same module runs on all of them ([Platforms](https://nsh.dev/docs/platforms.md)).

> [!IMPORTANT]
> NSH is the layer inside an app for UI that is generated, changed or added after it ships. On a phone or a desktop, the app stays a native app, built and released the way it always was.

## 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 NPM.
- **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 the platform's own UI — UIKit, Android views, AppKit, GTK 4 or Win32 with Direct2D — with the system's text, input and accessibility, yet the model never writes Swift, Kotlin, Java or Rust, 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 it with the platform's own UI.](https://nsh.dev/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), `NshView` (Android) or `AppView` (macOS, Windows, Linux) 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://nsh.dev/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://nsh.dev/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 with the platform's own UI, so text, input, accessibility and scrolling are the system's. The host can register its own native views — a map, a player, a design-system component — for modules to use ([Native components](https://nsh.dev/docs/native-components.md)).

**Third: standalone apps.** The same modules can also be a whole app. `nsh package` turns them into an installable iOS, Android, macOS, Windows or Linux app with the prebuilt runtime, which is also the quickest way to try NSH: the [getting started](https://nsh.dev/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 each platform's own UI: UIKit on [iOS](https://nsh.dev/docs/embedding-ios.md), Android views on [Android](https://nsh.dev/docs/embedding-android.md), AppKit on [macOS](https://nsh.dev/docs/macos.md), Win32 windows drawn with Direct2D on [Windows](https://nsh.dev/docs/windows.md) and GTK 4 on [Linux](https://nsh.dev/docs/linux.md). One module format and one runtime run in all these hosts.

The two are positioned differently. GPUI Shell is for extending GPUI desktop applications; Native Shell is for native apps on every platform, phones and desktops alike.

## 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` on iOS, a `ViewGroup` on Android, an `NSView` on macOS and a GTK widget on Linux, `<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://nsh.dev/docs/embedding-ios.md), [Android](https://nsh.dev/docs/embedding-android.md), [macOS](https://nsh.dev/docs/macos.md#embedding-in-a-mac-app), [Windows](https://nsh.dev/docs/windows.md#embedding-in-a-win32-app), [Linux](https://nsh.dev/docs/linux.md#embedding-in-a-gtk-app)). On iOS:

```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, DirectWrite on Windows, Pango on Linux) 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), `NshView` (Java/Kotlin) and the desktops' `AppView` (AppKit, GTK, Win32) put a module in a native app, as many as you like ([iOS](https://nsh.dev/docs/embedding-ios.md), [Android](https://nsh.dev/docs/embedding-android.md), [macOS](https://nsh.dev/docs/macos.md#embedding-in-a-mac-app), [Linux](https://nsh.dev/docs/linux.md#embedding-in-a-gtk-app), [Windows](https://nsh.dev/docs/windows.md#embedding-in-a-win32-app)).
- **Signed, revocable delivery.** Ed25519-signed packages, release keys certified by an offline root key, revocation lists ([Signed packages](https://nsh.dev/docs/signed-packages.md)).
- **A sandbox the host configures.** VM limits and capabilities granted per view, in the iOS and Android SDKs ([Sandbox and capabilities](https://nsh.dev/docs/sandbox-and-capabilities.md)).
- **The host's own views in modules.** Register native components from Swift, Java or Rust ([Native components](https://nsh.dev/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://nsh.dev/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://nsh.dev/docs/styling.md), [Tailwind](https://nsh.dev/docs/tailwind.md)).
- **A mobile UI set** (`nsh/ui/mobile`) whose components are written in Rust and drawn by NSH, so they look the same on every platform: [Button](https://nsh.dev/components/button.md), [Card](https://nsh.dev/components/card.md), [ListView](https://nsh.dev/components/list-view.md), [navigation](https://nsh.dev/components/navigation.md), and more. Desktop layouts are built from the primitives with `desktop={{…}}` props and `Platform.select` ([Platform-specific code](https://nsh.dev/docs/platform-specific.md)), and a desktop UI set is on the way.
- **One binary toolchain.** A single `nsh` executable builds modules, writes their type declarations, serves them with hot module replacement, packages and signs them.

## Where it runs

| Platform | Status | Backend | Notes |
|---|---|---|---|
| iOS | Supported (iOS 15.0+) | UIKit | Devices and the simulator; embedded with `NativeShell.xcframework` ([iOS](https://nsh.dev/docs/embedding-ios.md)) |
| Android | Supported (API 28, Android 9+) | Android views | `arm64-v8a` and `x86_64`; embedded with `nsh.aar` ([Android](https://nsh.dev/docs/embedding-android.md)) |
| macOS | Supported (macOS 14+) | AppKit | Standalone apps; embedded with `AppView` ([macOS](https://nsh.dev/docs/macos.md)) |
| Windows | Supported (Windows 10 1809+) | Win32, Direct2D | Standalone apps; embedded with `AppView` ([Windows](https://nsh.dev/docs/windows.md)) |
| Linux | Supported (GTK 4.14+) | GTK 4 | Standalone apps; embedded with `AppView` ([Linux](https://nsh.dev/docs/linux.md)) |
| GPUI (macOS, Windows, Linux) | Supported | GPUI | Standalone apps; embedded with `AppView` in GPUI and GPUI Kit apps ([GPUI](https://nsh.dev/docs/gpui.md)) |
| Web | Supported (modern browsers) | HTML | Static sites; embedded in a page with `createPlayer` or an iframe ([Web](https://nsh.dev/docs/web.md)) |
| Mac Catalyst | For development | UIKit | The iPhone-sized window `nsh run` opens on a Mac, to see a module as on a phone |

![One module, many hosts: the same bundle and Rust runtime on iOS, Android, Linux, macOS with AppKit and Windows with Win32 / Direct2D, GPUI on all three desktops, and web pages drawn with HTML.](https://nsh.dev/diagrams/platforms-light.svg)

## How it compares

The usual ways to put UI into an app after release are web views, server-driven UI described in JSON, and mini-program containers; for whole apps, cross-platform frameworks such as React Native, Flutter, Electron and Tauri. An NSH module renders native UI 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://nsh.dev/docs/comparison.md) goes through each.

## Next steps

- [Installation](https://nsh.dev/docs/installation.md) and [Getting started](https://nsh.dev/docs/getting-started.md): get `nsh`, write a first module and run it on each platform.
- [Platforms](https://nsh.dev/docs/platforms.md): what runs where, and how to run, package and embed on iOS, Android, macOS, Windows and Linux.
- [Signed packages](https://nsh.dev/docs/signed-packages.md) and [Sandbox and capabilities](https://nsh.dev/docs/sandbox-and-capabilities.md): deliver modules and decide what they may do.
- [JSX and components](https://nsh.dev/docs/jsx-and-components.md) and [Reactivity](https://nsh.dev/docs/reactivity.md): the two ideas everything else builds on.
