The modern alternative
to React Native.

One codebase, every platform, real native performance — and batteries included, so your first screen doesn't start with ten libraries.

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
18:27●●●● ⚡︎ 100%
Hello, Skal
Tap the button
bridge 39 opsdrain 0.15 ms60 fps
56 mscold boot to first frame, bytecode cache
0.151 msavg bridge drain per frame, steady state
60 fpsthrough a 10,000-item virtualized feed
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.

Numbers from the kitchen-sink demo on a mid-range Android emulator — methodology and budget invariants in docs/PERFORMANCE.md. If your hardware disagrees, that's a bug and we want the issue.

MetricSkalWhat it means
Runtime init20 msbun worker + JSC up, bridge memory mapped
First eval36 msbundle loads as pre-compiled JSC bytecode — the parser never runs
Cold boot → first frame56 msfull tree mounted and painted
Bridge drain, steady state0.151 ms avgper-frame cost of consuming JS ops on the Dart side
10,000-item feed60 fpsvirtualized list scrolling with live per-row signals
Store write → disksub-mslog-structured native engine, per-leaf delta frames
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