Wrapping pub.dev packages
The point of rendering with Flutter isn't just pixels — it's pub.dev: tens of thousands of maintained native plugins. Skal's codegen turns any Flutter widget into a JSX component.
One line of config
List the package in your app's skal_codegen.yaml:
# flutter-host/lib/skal_codegen.yaml
packages:
- qr_flutter
- shimmer
- camera
Then run the generator:
$ bun run codegen
The builder introspects each package's public widgets with Dart's analyzer — constructors, named constructors, parameter types — and emits adapters that register them with Skal's widget registry. On the JS side, typed components appear under skal-flutter:
import { QrImageView, ShimmerFromColors, Camera } from 'skal-flutter'; <QrImageView data="https://skal.dev" size={220} /> <ShimmerFromColors baseColor="#FF1F2937" highlightColor="#FF374151"> <Box width="fill" height={96} cornerRadius={12} /> </ShimmerFromColors>
Shimmer.fromColors becomes <ShimmerFromColors>. Prop types round-trip: enums, colors, durations, edge insets.Calling native APIs (not just widgets)
Plugins with imperative APIs — geolocation, biometrics, file pickers — ship as shim packages with one async JS surface:
import { getCurrentPosition } from 'skal-plugin-geolocator'; const pos = await getCurrentPosition(); // { lat, lon, accuracy, altitude, speed, timestamp }
On web this routes through the hidden Flutter plugin host today; the native routing for the same calls is in progress. Imperative native work is done today through host-widget RPC: bind a ref, then await ref.method(args) — one RPC op out, the reply back through the reply heap, and Dart Streams subscribe via ref.name$(cb). The Camera and Ticker demos in kitchen-sink use exactly this.
Custom Dart widgets
Your own Flutter widgets register the same way — a small adapter reads props off the node and returns your widget:
// flutter-host/lib/adapters/gauge.dart Widget _buildGauge(NodeState n, SkalBridge bridge) => Gauge(value: n.getCustomPropF32('value', 0)); SkalRegistry.registerWidget('gauge', _buildGauge); // <Gauge value={0.7} /> is now available in JSX
For full control (custom prop decoding, children handling, RPC methods), the generated adapters in lib/adapters/generated/ are the documentation for the shape.
When codegen isn't enough
- Function-valued parameters (builders, custom painters) can't cross the bridge declaratively — wrap those widgets manually with a constrained prop surface.
- Web target: plugin calls route through a headless Flutter Web instance, so the same shim API works in the browser for plugins with web implementations.