
Unified system APIs across multiple platforms
Android β’ iOS β’ Linux β’ macOS β’ Windows
nimble install nkit
nkit is a Nim toolkit for building desktop and mobile applications that look and behave like real native applications. Instead of drawing its own widgets, it talks to the platform's own UI framework through small C and Objective-C shims, so every button, menu, alert or tray icon you create is the genuine OS control your users already know.
The same philosophy applies below the GUI layer: clipboard, dialogs, notifications, global shortcuts, secure storage, display queries and friends are thin, unified wrappers over the platform services, exposed through one idiomatic Nim surface.
- Unified system APIs across multiple platforms from a single import
- Cross-platform desktop and mobile development in pure Nim
- Based on simple C and Objective-C shims, compiled directly into your binary
- No dynamic bridge, no runtime reflection
- Access to low-level system APIs and bindings when you need full control
- Native system GUI built on AppKit today, with other platforms on the way
- Three API tiers so you can pick the right altitude per screen: raw views, Flutter-style composition, or a macro DSL
- A pure-Nim layout solver (rows, columns, flex, padding, alignment) that drives native views and stays portable across backends
- Beautiful macro-based application DSL with state and render blocks
$ nkit
Development
init <?name> β Scaffold nkit.yaml, a nimble file and a starter app
build <platform> β Produces an .app bundle under build/
run <platform> <?device> β Targets the booted simulator, or a device by name
Diagnostics
devices β List iOS Simulator devices known to Xcode
Simulator runtimes
runtimes.list Show the runtimes installed on this machine
runtimes.install <name> β Download e.g. "iOS 18" or "watchOS 10"
logs <platform> β Stream simulator logs for the app process
clean Remove the build directory
The same counter app, written against each of the three tiers.
You touch exactly what the shim exposes, one call at a time. Nothing hides allocation, event registration or geometry from you. Best for wrapping your own custom controls, auditing hot paths, or porting an existing ObjC/C++ design line by line.
import nkit
import nkit/gui/view
import nkit/gui/theme
import nkit/gui/stack
import nkit/gui/label
import nkit/gui/button
let app = initApplication()
let win = newWindow()
win.setTitle("Counter")
win.setSize(size(420.0, 320.0), false)
var clicks = 0
let countLabel = newLabel("0")
countLabel.setFontSize(48.0)
countLabel.setFontWeight(fwLight)
let root = newStack(stVertical, spacing = 16.0)
root.setPadding(24.0)
let plusBtn = newButton("increment", bsPush)
discard onClick(plusBtn, proc(e: ButtonClickEvent) =
inc clicks
setText(countLabel, $clicks))
addArranged(root, countLabel)
addArranged(root, plusBtn)
setContent(win, View(root))
win.show()
discard app.run()Widgets are values. Constructors like button,
h1, slider, card return nodes that compose into row/column
trees laid out by a portable solver with flex, spacing and nine-point
alignment. Handlers wire at construction, setText/getText route by
runtime type, and the whole tree stays backend agnostic. Best for everyday
screen building where you want structure without ceremony.
import nkit
import nkit/gui/sugar
let app = initApplication()
let win = newWindow()
win.setTitle("Counter")
win.setSize(size(420.0, 320.0), false)
var clicks = 0
var countLabel = text("0", 48.0, fwLight)
var statusText = p("press increment to start")
layout(
padding(
column(
h1("Counter"),
expanded(centered(countLabel)),
row(
button("increment", proc(e: ButtonClickEvent) =
inc clicks
setText(countLabel, $clicks)),
button("reset", proc(e: ButtonClickEvent) =
clicks = 0
setText(countLabel, "0"))
).spacing(12).crossAlign(caStretch),
centered(statusText)
).spacing(16),
all(24.0)
)
)
win.show()
discard app.run()The boilerplate disappears. initApp creates the
window, applies your size, runs the render block once, mounts the last
expression as the root and starts the run loop. State lives in one block,
UI in another, which keeps screens readable and encourages keeping widget
references in scope. Best for full applications and quick prototypes that
should still ship as real native windows.
import nkit/gui/appdsl_cocoa
initApp("Counter") do:
state do:
var clicks = 0
var countLabel = text("0", 48.0, fwLight)
var statusText = p("press increment to start")
render do:
padding(
column(
h1("Counter"),
expanded(centered(countLabel)),
row(
button("increment", proc(e: ButtonClickEvent) =
inc clicks
setText(countLabel, $clicks)
setText(statusText, "clicked " & $clicks & "x")),
button("reset", proc(e: ButtonClickEvent) =
clicks = 0
setText(countLabel, "0")
setText(statusText, "counter cleared"))
).spacing(12).crossAlign(caStretch),
centered(statusText)
).spacing(16),
all(24.0)
)All three tiers interoperate freely: a DSL app can drop to the low level for one tricky view, and low-level apps can adopt sugar constructors incrementally.
Platform support:
| Platform | Status |
|---|---|
| macOS (Cocoa/AppKit) | Done, active development |
| Windows | Planned |
| Linux | Planned |
| iOS | active development |
| Android | Planned |
Planned work:
- Windows and Linux implementations of the existing service contracts (window, dialogs, clipboard, notifications, menus, tray)
- Backend ports of the layout host layer, reusing the pure-Nim solver
- More composite widgets (tables, trees, rich text)
- Multi-window and multi-display ergonomics
- Packaging story for bundling, icons and code signing
- π Found a bug? Create a new Issue
- π Wanna help? Fork it!
MIT license | (c) 2026 George Lemon for Nimbase Community.