Skip to content

macOS#

On macOS, your screens are drawn with AppKit: layer-backed NSViews for containers, NSTextField for inputs, NSButton, NSImageView, and NSScrollView. Text is measured with the same metrics AppKit draws with, so the layout reserves exactly the space each label needs. In the generated app, a label is a lightweight view that draws its text with AppKit's text system rather than an NSTextField: it sets the same lines in the same place for a fraction of the cost per update. Nothing runs in a web view.

macOS is leptoskit's reference backend: every screenshot on this site is a leptoskit screen committed as real AppKit views.

The shell plugin groups each permitted command with its descendants and terminates that group when the invocation ends. Commands that deliberately leave the group need additional containment from the host.

Build and run#

You need a Mac with Xcode or the Xcode command line tools, and the WebAssembly target:

rustup target add wasm32-unknown-unknown
leptoskit build --platform macos
open .leptoskit/macos/App.app

The build compiles your app to WebAssembly, builds the AppKit host for your Mac's architecture, and assembles .leptoskit/macos/App.app. It signs the app ad hoc, which is enough to run it on your own Mac. To distribute it, leptoskit publish macos signs it with your Developer ID, notarizes it, and makes a .dmg and a .zip; see Publishing to the stores.

While you work, leptoskit dev --platform macos builds and opens the app, and replaces its interface in the same window each time you save. See building and serving.

The macos/ folder is yours: App.swift starts the app and owns its window, Info.plist holds its name and version, and Cargo.toml lists native Rust dependencies such as runtime plugins. A plugin's third-party Swift dependencies come through Swift Package Manager and resolve automatically; CocoaPods libraries are iOS-only, because the macOS host builds with Swift packages.

How it differs from the other platforms#

  • The generated app draws the common controls: View, Text, Button, Pressable, TextInput, PasswordInput, ScrollView, Image, Vector, and Rive, plus Switch, Checkbox, Radio, Slider, LinearProgressIndicator, and ActivityIndicator. Switches use NSSwitch, choices use NSButton, sliders use NSSlider, and indicators use NSProgressIndicator. Update checked or slider state in your handler to accept a change; otherwise the control restores the previous value. Custom widgets remain unsupported. See generated apps and backend crates.
  • Rive displays native vector frames in an NSImageView. The generated host uses a display link and follows the system reduced-motion preference. Native Rive rejects text, image, layout, script, and audio assets.
  • Pressing a button with the keyboard (Space, with Full Keyboard Access on) or through VoiceOver activates it without showing its pressed style. A mouse press shows it.
  • Image loads local paths and file:// URLs, not web URLs.
  • Sign-in works as on iOS: the system's passkey sheet and Sign in with Apple's own sheet once the app has its associated domain, which the generated project doesn't add yet, and other providers in an ASWebAuthenticationSession.
  • Labels in the generated app can't be selected, as on iOS and Android. AppKit's own labels also darken glyph edges slightly with private font dilation that no public API offers, so text there is a shade lighter than in an NSTextField.
  • The generated macOS app doesn't check for code push updates; ship a new build to update it. The iOS and Android apps do.
  • AppKit follows the system's light or dark appearance until the app calls NativeSurface::set_dark_mode. An app with a fixed light theme should call set_dark_mode(false) before it shows its first screen.

See more screens#

The examples page shows seven screens rendered with AppKit. To open them yourself, clone the leptoskit repository and run the gallery:

cargo run -p gallery -- window counter            # a live, clickable window
cargo run -p gallery -- snapshot target/gallery   # one 2x PNG per screen

Snapshots draw the views offscreen, so they need no window and no Screen Recording permission.

esc