inertia-native

Drive Inertia.js navigation and bridge components from Hotwire Native (iOS & Android)
217 14
Install
npm install inertia-native
Latest Version:1.0.0
Vue:3
License:MIT
Last Updated:Oct 9, 2026
Links: GitHub  ·  npm
Maintainer: zumkorn

Inertia Native

CI

Drive Inertia.js navigation and bridge components from Hotwire Native (iOS & Android).

Hotwire Native injects a turbo.js script into every web view that expects a window.Turbo object to drive. This package provides a small shim that maps the native adapter protocol onto Inertia's router, so Inertia pages push, pop, and restore as native screens — and a web bridge so Inertia pages can use native bridge components (submit buttons, menus, etc.).

In a regular browser it stays inert: with no native adapter connected, Inertia navigates exactly as usual.

  • Framework-agnostic core (.) — peer-depends on @inertiajs/core.
  • React bindings (./react) — optional, peer-depends on react.
  • Vue bindings (./vue) — optional, peer-depends on vue.
  • Svelte bindings (./svelte) — optional, peer-depends on svelte.

Docs: inertia-native.dev. Formerly published as inertia-hotwire-native. A community project, not affiliated with the Inertia.js team.

Install

npm add inertia-native

Usage

Call initInertiaNative() once, before createInertiaApp, in your Inertia entrypoint:

import { createInertiaApp } from '@inertiajs/react'
import { initInertiaNative } from 'inertia-native'

const isNativeApp = !!window.webkit?.messageHandlers?.turbo
initInertiaNative({ debug: import.meta.env.DEV || isNativeApp })

createInertiaApp({ /* ... */ })

That's all that's needed for native navigation (push/pop/replace/restore, modals, forms, error screens, pull-to-refresh).

Form redirects

By default a form's result stays in the web view the form was in. Pass proposeFormRedirects: true to propose the page a form lands on to native instead, as Turbo does — native's path configuration then decides, e.g. dismissing a modal to show the result in the main stack. Validation errors redirect back to the form's own URL and are not proposed.

initInertiaNative({ proposeFormRedirects: true })

Bridge components (React)

useBridgeComponent(name) is the generic primitive: it returns whether the connected native app supports the component and a stable send(event, data?, callback?). Build specific components (form, menu, overflow-menu, …) in your app on top of it.

import { useBridgeComponent } from 'inertia-native/react'

function NativeMenu({ items }) {
  const { supported, send } = useBridgeComponent('menu')
  if (!supported) return null

  return (
    <button
      onClick={() =>
        send('connect', { items }, (message) => onSelect(message.data.index))
      }
    >
      Open menu
    </button>
  )
}

Each send returns a message id; native replies invoke the callback. The hook re-checks support when the native handshake completes after mount.

A component that draws native UI on connect should send it again when the web view returns from a native screen (native:restore, dispatched on Android). Put restored in the deps of the effect that sends it:

const { supported, send, restored } = useBridgeComponent('button')

useEffect(() => {
  if (!supported) return
  const id = send('connect', { title }, onTap)
  return () => window.HotwireNative?.web?.removeCallback(id)
}, [supported, title, send, restored])

Bridge components (Vue)

The Vue entry exposes the same useBridgeComponent(name) composable. supported and restored are Refs, so unwrap them with .value (or use them in a template); send has the same signature.

<script setup>
import { useBridgeComponent } from 'inertia-native/vue'

const props = defineProps(['items'])
const { supported, send } = useBridgeComponent('menu')

function open() {
  send('connect', { items: props.items }, (message) => onSelect(message.data.index))
}
</script>

<template>
  <button v-if="supported" @click="open">Open menu</button>
</template>

To send connect again after native:restore, watch restored alongside the other sources of the watcher that sends it.

Bridge components (Svelte)

The Svelte entry exposes useBridgeComponent(name) too. supported and restored are readable stores (subscribe with $supported, $restored); send has the same signature. Call it during component initialization — it registers an onDestroy cleanup.

<script>
  import { useBridgeComponent } from 'inertia-native/svelte'

  export let items
  const { supported, send } = useBridgeComponent('menu')

  const open = () =>
    send('connect', { items }, (message) => onSelect(message.data.index))
</script>

{#if $supported}
  <button on:click={open}>Open menu</button>
{/if}

To send connect again after native:restore, reference $restored in the reactive statement that sends it.

Requirements

  • @inertiajs/core >= 3.0
  • react >= 18 (only for the ./react entry)
  • vue >= 3.0 (only for the ./vue entry)
  • svelte >= 4.0 (only for the ./svelte entry)

License

MIT

Related Packages

@forinda/kickjs-inertia

Inertia.js adapter for KickJS — server-driven pages rendered by React, Vue or Sv...

448 17
@arkstack/inertia

InertiaJS server-side adapter for Arkstack, building the modern monolith with se...

12,191 6
@nativescript-community/geocoding

Provides access to native geocoding APIs for NativeScript apps (android.location...

2,162 8
@abbieben/rich-text

This is the description for the plugin seed. This is what will be published to N...

1,535 0
@abbieben/webview-interface

This is the description for the plugin seed. This is what will be published to N...

1,515

Version History

Version Released Vue License
1.0.0 >=3.0.0 MIT
Pre-releases (1)
Version Released Vue License
0.1.0-beta.2 >=3.0.0 MIT