One codebase, every platform, real native performance — and batteries included, so your first screen doesn't start with ten libraries.
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> ); }
Cross-platform JS frameworks lose their performance in the seams — serialization, VDOM diffing, JIT-less interpreters. Skal removes the seams.
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.
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.
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.
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.
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.
bun test for framework logic, flutter test for the host, and Maestro flows driving the real app on a real device — testID just works.
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.
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.
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.
| Metric | Skal | What it means |
|---|---|---|
| Runtime init | 20 ms | bun worker + JSC up, bridge memory mapped |
| First eval | 36 ms | bundle loads as pre-compiled JSC bytecode — the parser never runs |
| Cold boot → first frame | 56 ms | full tree mounted and painted |
| Bridge drain, steady state | 0.151 ms avg | per-frame cost of consuming JS ops on the Dart side |
| 10,000-item feed | 60 fps | virtualized list scrolling with live per-row signals |
| Store write → disk | sub-ms | log-structured native engine, per-leaf delta frames |
Everyone says "write once, run anywhere, feels native." The difference is what happens between your code and the screen.
| Skal | React Native | Flutter | |
|---|---|---|---|
| You write | Solid JSX | React JSX | Dart |
| Reactivity | signals — no VDOM, no diffing | VDOM reconciliation | rebuild + element diff |
| JS ↔ native transport | shared memory ring, zero-copy | JSI calls + serialized boundaries | — (single language) |
| JS engine | JavaScriptCore + bytecode cache | Hermes | — |
| Rendering | Flutter compositor | platform views | Flutter compositor |
| App-code hot reload | < 1 s, navigation + state kept | Fast Refresh | stateful hot reload |
| Native plugin pool | pub.dev, via codegen | npm native modules | pub.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.
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.
| Target | Status | Notes |
|---|---|---|
| Android | Stable | arm64 · JSC bytecode cache · R8-ready release path |
| iOS | Stable | device + simulator · bun/WebKit cross-compiled for aarch64-apple-ios |
| macOS | Stable | debug + release desktop shells |
| Web | Stable | Solid → DOM directly; optional headless Flutter for plugin access |
| Linux · Windows | Coming | Flutter Desktop supports both; libskal linkers in progress |
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.
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}`)} />
// 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 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
<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); }} />
// 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>
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.
# 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.
One command scaffolds it. One more runs it on your phone. The third one ships it.