The modern alternative
to React Native.

Performance-first, batteries included, every platform. Fast at your first screen. Still fast at your hundredth.

Get started →
$ npm create skal my-app
src/App.jsx ● hot reload
import { createSignal } from 'solid-js';
import { Column, Text, Button, Row } from 'skal';

export default function App() {
  const [count, setCount] = createSignal(0);

  return (
    <Column padding={24} gap={16}>
      <Text label="Hello, Skal" fontSize={28}
            fontWeight={800} />
      <Text label={count() === 0
        ? 'Tap the button'
        : `Tapped ${count()} times`} />
      <Row gap={12}>
        <Button label="Tap"
          onClick={() => setCount(c => c + 1)} />
        <Button label="Reset"
          onClick={() => setCount(0)} />
      </Row>
    </Column>
  );
}
SET_TEXT · 14 B
20:00●●●● ⚡︎ 100%
Hello, Skal
Tap the button
bridge 39 opsdrain 0.15 ms60 fps
0dropped frames scrolling an image feed
3.53 mstap to render, median — RN 7.30 ms
1.2%idle CPU — RN uses 5.8× more
4 targetsAndroid · iOS · macOS · Web — one bundle
Why Skal

Every layer picked for speed.
Then wired together without copies.

Cross-platform JS frameworks lose their performance in the seams — serialization, VDOM diffing, JIT-less interpreters. Skal removes the seams.

Solid + bun + JSC

Fine-grained reactivity with no virtual DOM — a signal update touches exactly the widgets that read it. The bundle ships as JSC bytecode, so cold start skips the parser entirely. And the web platform — crypto, fetch, Buffer — is built in, not polyfilled.

🔗

A bridge that isn't one

JS and Dart share one permanent 6 MiB memory region. UI mutations are binary ops in a ring buffer — no JSON, no MessageQueue, no copies. Both sides read the same bytes.

🎨

Flutter rendering, pub.dev reach

Pixels come from Flutter's compositor — consistent on every OS. Need camera, geolocation, biometrics? Wrap any pub.dev package with one line of codegen config.

🔥

Sub-second hot reload

Save a .jsx file and the running native app re-evaluates it in place — navigation stack and state preserved. Manual r or automatic on save. Compiled out of release builds.

🔋

Batteries actually included

createSkalStore is a reactive store persisted by a log-structured native engine — no Redux, writes are sub-millisecond. Navigation with Hero transitions and deep links is first-party too. Zero dependency matrix before your first screen.

🧪

Testable end to end

bun test for framework logic, flutter test for the host, and Maestro flows driving the real app on a real device — testID just works.

Architecture

Three runtimes. One region of memory.

Your app runs in JavaScriptCore embedded via libskal (a slice of bun). Flutter drains a binary op ring once per frame. Events flow back through the same region — watch them.

YOUR APP SolidJS + skal-js signals · universal renderer bun + JavaScriptCore libskal — embedded runtime, bytecode cache, native store SHARED MEMORY · 6 MiB Op ring — 4 MiB create / insert / set-prop / text String + reply heaps UTF-8 payloads · RPC results Event ring taps · scrolls · input, Dart → JS HOST Flutter drains ops once per frame, builds real widgets pub.dev plugins camera · geo · biometrics — wrapped by codegen ops drain events wake

JS sees the region as a Uint8Array (zero-copy via JSC's no-copy ArrayBuffer); Dart sees the same bytes as a typed view over an FFI pointer. Deep dive in Architecture.

Performance

Measured, not marketed.

Head-to-head against React Native on a Samsung Galaxy A14 5G, Android 15, arm64 — release builds on both sides, runs interleaved, screen held awake. React Native 0.86 / Expo 57 with Hermes, zustand and MMKV. Method and raw data in docs/BENCHMARKS.md. If your hardware disagrees, that's a bug and we want the issue.

What you feelSkalReact Native
Dropped frames, scrolling an image feed013
Scroll p95, image feed6 ms12 ms
Input-latency events while scrolling0631
Tap → render, median3.53 ms7.30 ms
Tap → render, p954.87 ms10.24 ms
Idle CPU1.20%6.93%
App size at full WebCrypto parity51.6 MiB67.2 MiB
State updatesSkalReact Native
One frame of work — 200 components × 10 reads + 1 update0.05 ms0.80 ms
One update, 200-key store, no subscribers0.0012 ms0.1081 ms
One update, 200 subscribers0.1425 ms0.2119 ms
200 updates, one subscriber each0.63 ms38.15 ms
Update throughput, 288 live cells103,846/s4,901/s
Dropped frames under that pressure4.6%50.4%
Components woken by one change1 of 2001 of 200
— cost of that update0.0024 ms0.150 ms

Immutable stores copy the whole state object on every write, so the gap widens with store size and narrows with subscriber count. Both ends are shown.

JavaScript engineSkal (JSC)React Native (Hermes)
8 JS workloads, warmJSC 7.3× faster median, wins 8/8
— closure calls, 3k0.0124 ms0.2141 ms
— worst case, JSON.parse1.1755 ms1.9915 ms
— first execution, coldtie — JSC overtakes within ~10 runs

JS engine measured warm. Hermes ships pre-compiled bytecode and stays flat; JSC compiles hot code and overtakes within about ten iterations.

Compare

Same promise. Different physics.

Everyone says "write once, run anywhere, feels native." The difference is what happens between your code and the screen.

SkalReact NativeFlutter
You writeSolid JSXReact JSXDart
Reactivitysignals — no VDOM, no diffingVDOM reconciliationrebuild + element diff
JS ↔ native transportshared memory ring, zero-copyJSI calls + serialized boundaries— (single language)
JS engineJavaScriptCore + bytecode cacheHermes
RenderingFlutter compositorplatform viewsFlutter compositor
App-code hot reload< 1 s, navigation + state keptFast Refreshstateful hot reload
Native plugin poolpub.dev, via codegennpm native modulespub.dev

Both neighbors are excellent at what they optimize for. Skal exists for teams that want JS ergonomics and Flutter's rendering — without paying a serialization boundary between them.

Platforms

One bundle. Every screen.

The same skal-app.js runs unchanged on every target. Release builds ship bytecode; the web target renders Solid straight to the DOM — no Flutter Web required.

TargetStatusNotes
AndroidStablearm64 · JSC bytecode cache · R8-ready release path
iOSStabledevice + simulator · bun/WebKit cross-compiled for aarch64-apple-ios
macOSStabledebug + release desktop shells
WebStableSolid → DOM directly; optional headless Flutter for plugin access
Linux · WindowsComingFlutter Desktop supports both; libskal linkers in progress
Components

Fifty-plus widgets. All of them JSX.

Capitalized tags from 'skal' compile to binary ops that build real Flutter widgets — layout, form controls, virtualized lists, slivers, navigation, canvas. Every snippet below is lifted from the kitchen-sink demo.

controls.jsxSwitch · Slider · TextField
const [name, setName] = createSignal('');

<Switch checked={sw()} onChange={(v) => setSw(v)} />
<Slider value={vol()} min={0} max={100} onChange={setVol} />
<TextInput value={name()} placeholder="Type your name…"
  onChange={setName}
  onSubmit={(v) => showSnackbar(`Hi ${v}`)} />
feed.jsxListView.builder
// virtualized — 10,000 rows scroll at 60 fps
<ListView gap={8} onRefresh={reload}>
  <For each={tweets()}>
    {(t) => <TweetCard tweet={t} />}
  </For>
</ListView>

// also: LazyGrid · ReorderableListView · slivers
tabs.jsxnative navigation
<Tabs activeTab={tab()} onChange={setTab} height="fill">
  <Tab title="UI" icon="grid"><UITab /></Tab>
  <Tab title="Feed" icon="list"><FeedTab /></Tab>
  <Tab title="Store" icon="storage"><StoreTab /></Tab>
</Tabs>

// screens: createRouter → navigate / back, Hero, Drawer
chart.jsxCanvas → CustomPaint
<Canvas width={300} height={170} draw={(c) => {
  vals().forEach((v, i) =>
    c.fillStyle(i === 3 ? ACCENT : PURPLE)
     .fillRect(28 + i * 52, 150 - v, 34, v));
  c.fillStyle(INK).fontSize(12)
   .fillText('live chart', 18, 22);
}} />
pubdev.jsxany Flutter widget
// pub.dev packages wrapped by one line of codegen
<QrImageView data="https://github.com/skal-multiplatform"
  size={200} />
<ShimmerFromColors baseColor={0xFFBDBDBD}
  highlightColor={0xFFE0E0E0}>
  <Greeting name="loading…" fontSize={28} />
</ShimmerFromColors>
dialogs.jsxnative dialogs
const r = await showDialog({
  title: 'Delete file?',
  message: 'This cannot be undone.',
  actions: [
    { label: 'Cancel', value: 'cancel' },
    { label: 'Delete', value: 'delete', style: 'destructive' },
  ],
});

The full set — Layout Column · Row · Stack · Wrap · Box  Scrolling ScrollView · ListView · LazyGrid · PageView · slivers  Input Button · TextInput · Switch · Checkbox · Radio · Chip · Dropdown · SegmentedButton · Stepper  Motion AnimatedList · CrossFade · Dismissible · Hero · InteractiveViewer  — props and events for each in the component docs.

Quickstart

Three commands to a running native app.

terminal
# scaffold — Solid app + platform hosts + prebuilt native runtime
$ npm create skal my-app

# run it — emulator boots automatically if none is attached
$ cd my-app && bun run dev:android

# ship it — release APK with the bytecode cache baked in
$ bun run build:android

The scaffold is a complete app: a Solid entry in src/App.jsx, platform hosts, hot reload wired, a Maestro smoke test, and the store ready to persist. Edit one file, save, watch the device.

Prereqs: bun and Flutter — the native runtime downloads prebuilt, so there's no LLVM, Rust, or NDK to install. Prefer a global command? npm i -g @skal/cli gives you skal dev / build / doctor.

Read the getting-started guide →

Ship your first native app before lunch.

One command scaffolds it. One more runs it on your phone. The third one ships it.

Get started →
$ npm create skal my-app