A declarative UI engine written in Swift that renders to Skia (CanvasKit) on the web.
Write SwiftUI-style code, render pixel-perfect UI on an HTML <canvas>.
한국어 | 日本語 | 中文 | Documentation
Important
SkiaUI is currently in an experimental stage. APIs are unstable and may change without notice. Not recommended for production use.
import SkiaUI
struct CounterView: View {
@State private var count = 0
var body: some View {
VStack(spacing: 16) {
Text("Count: \(count)")
.font(size: 32)
.foregroundColor(.blue)
HStack(spacing: 16) {
Text("- Decrease")
.padding(12)
.background(.red)
.foregroundColor(.white)
.onTapGesture { count -= 1 }
Text("+ Increase")
.padding(12)
.background(.blue)
.foregroundColor(.white)
.onTapGesture { count += 1 }
}
}
.padding(32)
}
}- Swift as the single UI language -- declarative ResultBuilder DSL,
@State, modifiers - Canvas-based rendering -- Skia drawing commands on
<canvas>, not DOM elements - Renderer-agnostic core -- a native Skia or Metal backend can be added without changing user code
graph TD
A["Swift DSL<br/>(@ViewBuilder)"] --> B["Element Tree"]
B --> C["Reconciler"]
C --> D["Layout Engine"]
D --> E["Render Tree"]
E --> F["Display List"]
F --> G["CommandEncoder"]
G --> H["Web Host<br/>(CanvasKit)"]
style A fill:#f5a623,color:#000
style B fill:#4a90d9,color:#fff
style C fill:#4a90d9,color:#fff
style D fill:#4a90d9,color:#fff
style E fill:#4a90d9,color:#fff
style F fill:#7b68ee,color:#fff
style G fill:#7b68ee,color:#fff
style H fill:#50c878,color:#000
Each layer is a separate Swift module. The binary display list is the only thing that crosses the Swift–JavaScript boundary — zero JSON, zero object marshalling.
| Category | Feature | Status |
|---|---|---|
| Views | Text, Rectangle, Spacer, EmptyView | Done |
| Containers | VStack, HStack, ZStack, ScrollView | Done |
| Modifiers | padding, frame, background, foregroundColor, font, fontFamily, onTapGesture, drawingGroup | Done |
| Typography | Font struct (.custom, .system, semantic styles), fontFamily pipeline, FontManager | Done |
| Layout | ProposedSize negotiation, layoutPriority, fixedSize, flexible frame (min/ideal/max) | Done |
| State | @State, Binding, automatic re-rendering, incremental evaluation (AttributeGraph) | Done |
| Accessibility | accessibilityLabel, accessibilityRole, accessibilityHint, accessibilityHidden | Done |
| Rendering | Binary display list, CanvasKit replay, retained subtrees, pipeline optimizations | Done |
| Reconciler | Tree diff, Patch, DirtyTracker, RootHost integration | Done |
| Testing | 21 test suites, 161 tests | Done |
| Rendering | List | Planned |
| Rendering | Animation system | Planned |
| Rendering | Image support | Planned |
| Platform | Native Skia backend (Metal / Vulkan) | Planned |
| Product | Description |
|---|---|
| SkiaUI | Umbrella module — import SkiaUI to access all DSL, state, and runtime APIs |
| SkiaUIWebBridge | JavaScriptKit interop layer for WebAssembly builds (isolated dependency) |
| SkiaUIDevTools | TreeInspector, DebugOverlay, SemanticsInspector for development |
- Swift 6.2+
- macOS 14.0+
- Node.js / pnpm (for WebClient)
# Build all modules
swift build
# Run tests
swift testDeploy a SkiaUI app directly to the browser via WebAssembly in 4 steps:
1. Copy the example project
cp -r Examples/BasicApp ~/MySkiaUIApp
cd ~/MySkiaUIApp2. Build
# Build the project (defaults to dist/ folder)
swift run skia build --product App3. Serve
To serve your app, see the Server Integration section below for a complete Vapor example.
See
Examples/BasicApp/for the complete example project.
SkiaUI can be integrated into Swift server environments in two primary ways:
The most common approach is using Vapor to serve your compiled WASM app as static files.
- Build:
swift run skia build -o Public - Run:
swift run App - Example: See
Examples/Server/Vapor/for a complete setup.
You can also use SkiaUI directly on the server to generate binary display lists dynamically. These lists can be streamed to any client (iOS, Android, or Web) for pixel-perfect replay.
- Mechanism: Use
RootHostto render views to[UInt8]binary buffers. - Example: See
Examples/Server/Generic/for a framework-agnostic implementation.
import SkiaUI
let host = RootHost()
host.render(MyView())
host.setOnDisplayList { bytes in
// Send binary 'bytes' to client
}- Text rendering relies on estimated glyph widths (
fontSize × 0.6 × charCount), not real font metrics - No text wrapping or line breaking — single-line text only
- No gesture recognizers beyond
onTapGesture - No keyboard input or focus management
- No image loading or rendering
- No animation or transition support
MIT — see LICENSE for details.
Third-party licenses are listed in THIRD_PARTY_NOTICES.
SwiftUI is a trademark owned by Apple Inc. This project is not affiliated with, endorsed by, or connected to Apple Inc. in any way.
