Introduction
Nativ is a compile-time UI compiler. You describe an app's screens once in an indentation-based .nativ language, and Nativ generates standalone, real SwiftUI and real Jetpack Compose projects — complete Xcode and Gradle output. No runtime, no bridge, no webview.
How it works
Source parses to a typed intermediate representation, then lowers to two native backends. Because both platforms share one IR, the iOS and Android apps stay in lockstep.
.nativ source → parser → typed IR → SwiftUI / Xcode
→ Compose / GradleWhat you get
- Idiomatic SwiftUI in a ready-to-open
.xcodeproj - Idiomatic Jetpack Compose in a ready-to-import Gradle project
- Zero Nativ dependency in the output —
0 KBruntime in your binary
New here? Start with Quick start.
Installation
Nativ ships as a single binary. The compiler runs on macOS, Linux, and Windows.
Prerequisites
- To compile: nothing but the
nativbinary —nativ buildonly generates source files. - To run the iOS output: macOS with Xcode 15+ (targets iOS 17+).
- To run the Android output: Android Studio (targets API 26+, Material 3).
Install the compiler
cargo install from crates.io is the public path today. Homebrew and the install script are next packaging targets.
$ cargo install nativ # Homebrew and curl installers are pending packaging.
Verify the install:
$ nativ version nativ 0.2.0
Quick start
Create a project, write one screen, and compile it into a real Xcode project and a real Gradle project — about a minute, start to finish.
Nativ is pre-release. The language covers core screens, state, and navigation today. See honest status for what isn't generated yet.
1 · Create a project
$ nativ init my-app ✓ Project created: my-app/ — run `nativ build` to get started
2 · Write your first screen
Open src/screens/Home.nativ. A screen is indentation-based: declare state, lay out elements, attach actions. No imports, no boilerplate.
app MyApp:
name: "my-app"
start: Home
screen Home:
state count = 0
text "Hello, Nativ!", big, bold
text "This compiles to real native code.", gray
button "Tapped {count} times":
count += 13 · Compile to native
$ nativ build Compiling: my-app → iOS, Android iOS: 7 files generated Android: 7 files generated ✓ Build complete: 14 files, 0.01s
Open each project in its native IDE and run it like any hand-written app:
$ open build/ios/MyApp.xcodeproj # Android Studio → File → Open → build/android
Project layout
nativ init scaffolds a project with a source folder, models and components directories, assets, and a config file.
my-app/
├─ nativ.toml // project configuration
├─ assets/ // images, app icon, resources
├─ i18n/
│ └─ en.json // translation strings
└─ src/
├─ app.nativ // app declaration + start screen
├─ components/ // reusable components
├─ models/ // data models
└─ screens/
└─ Home.nativ // one file per screennativ.toml
Configuration holds app identity, target platforms, output location, theme, and i18n defaults. App identity and version flow into the generated Xcode and Gradle metadata.
[app]
name = "my-app"
version = "0.1.0"
bundle_id = "com.example.myapp"
[build]
ios = true
android = true
min_ios = "17.0"
min_android = 26
[output]
directory = "build"Images in assets/ and translation JSON in i18n/ are automatically copied into the generated Xcode asset catalogs and Android resources during the build.
UI elements
Nativ provides built-in UI elements for building interfaces. All element names are lowercase.
text
Displays static strings or dynamic values, with optional interpolation and modifiers.
text "Hello, World!"
text user.name
text "You have {todos.count} items"
text "Welcome", big, boldbutton
An interactive element that triggers an action. Buttons require a colon and an indented body.
button "Save":
save todo to "/todos"
button "Delete", red:
todos.remove(todo)Other elements
| Element | Purpose |
|---|---|
image | Displays an image from assets or a URL — image user.avatar, round, size: 80 |
toggle | A switch bound to a boolean — toggle todo.done |
textfield | Text input with binding — textfield "Name", bind: user.name |
spinner | Loading indicator, no arguments |
divider | Horizontal line separator |
spacer | Flexible empty space that pushes content apart |
Layout
Layout elements control how children are arranged. A screen body is implicitly a vertical column.
column & row
column stacks children vertically; row stacks them horizontally. Both take spacing and padding.
row spacing: 12:
image user.avatar, round, size: 40
column:
text user.name, bold
text user.email, small, gray
spacer
text "$9.99", boldscroll, card & section
scroll— makes content scrollable when taller than the screen.card— a styled container with background, shadow, and rounded corners.section— groups related content with an optional header (ideal for settings).
scroll:
column spacing: 12:
each item in items:
card padding: 16:
text item.title, bold
text item.subtitle, grayModifiers & styling
Two styles: shorthand modifiers for speed and detailed modifiers for precision. Both can be combined on one element.
Shorthand
Append after an element, comma-separated. Each lowers to the platform's idiomatic call.
| Modifier | SwiftUI | Compose |
|---|---|---|
big | .font(.title) | fontSize = 24.sp |
small | .font(.caption) | fontSize = 12.sp |
bold | .bold() | FontWeight.Bold |
red | .foregroundColor(.red) | color = Color.Red |
gray | .foregroundColor(.secondary) | onSurfaceVariant |
round | .clipShape(Circle()) | clip(CircleShape) |
Detailed
Written as indented key-value pairs below an element: font, color, align, lines, size, shape, padding, corner, border, plus accessibility metadata.
text user.name, bold
color: #333333
align: center
image user.avatar, round
size: 80
border: 2Data & state
Keywords for defining data models, managing reactive state, fetching, and persisting.
model
Defines a data structure (PascalCase). The compiler infers types, or you declare them explicitly with defaults. ? marks an optional field.
model Todo:
title: text
done: boolean = false
created: date = now
priority: number = 0state
Declares reactive state scoped to a screen or component. When it changes, the UI updates automatically.
screen Counter:
state count: number = 0
state items: list of Todo = []load & persistence
load posts from "/posts"in anon load:block generates a real async GET (URLSession / HttpURLConnection).remember count between sessionspersists primitives —@AppStorageon iOS,rememberSaveableon Android.- A
store:block selectslocal/secure(Keychain · EncryptedSharedPreferences) strategies.
Control flow
Conditionals, loops, and lifecycle hooks for dynamic UI.
if / else
if count == 0:
text "No items"
else if count == 1:
text "1 item"
else:
text "{count} items"each
The only loop in Nativ — iterate a list to render elements for each item. There is no for or while; this is a UI language.
each user in users:
card padding: 12:
text user.name, bold
on tap:
go to UserProfile(user)Lifecycle hooks
on load— runs when the screen first appears (e.g. fetch data).on tap— runs when an element is tapped.on pull down— pull-to-refresh.on close— runs as the screen disappears.
Navigation
Natural-language keywords move between screens. SwiftUI emits a NavigationStack; Compose emits Navigation Compose.
go to · go back
button "Profile":
go to Profile
on tap:
go to TodoDetail(todo)
button "Cancel":
go backTabs & screen parameters
Define tab navigation in the app declaration; start sets the first screen. Screens accept parameters passed via go to.
app MyApp:
navigation:
tabs:
Home, icon: house
Search, icon: search
Profile, icon: person
start: Home
screen TodoDetail(todo):
text todo.title, big
toggle todo.doneCLI commands
The nativ binary drives the whole workflow — check, build, watch, preview, and a native dev loop.
check & build
nativ check validates .nativ files without generating code; diagnostics point at the real source line and column.
$ nativ check No errors found $ nativ build --ios --android ✓ Build complete: 14 files, 0.01s
watch & dev
nativ watch— rebuilds on every save; a parse error reports and keeps watching.nativ dev --ios— builds, installs, launches, then rebuilds/relaunches the native app on each change.nativ preview --open --watch— renders a static HTML mockup (one phone frame per screen).nativ format— normalises spacing and indentation;--checkis CI-friendly.
$ nativ watch Build OK: 14 files generated (0.01s) Watching . for changes... (Ctrl+C to stop) Change detected, rebuilding... Build OK: 14 files generated (0.01s)
Output & ownership
The output is idiomatic, hand-readable code. This screen — text, text, button with count += 1 — becomes the following.
struct HomeView: View {
@State private var count = 0
var body: some View {
VStack {
Text("Hello, Nativ!").font(.title).bold()
Button("Tapped \(count) times") { count += 1 }
}
}
}@Composable
fun HomeScreen() {
var count by remember { mutableStateOf(0) }
Column {
Text("Hello, Nativ!", fontSize = 24.sp, fontWeight = FontWeight.Bold)
Button(onClick = { count += 1 }) { Text("Tapped $count times") }
}
}What you own
The ios/ and android/ folders are ordinary native projects with no dependency on Nativ. Delete the compiler and they still build, run, and ship. The generated code is yours to edit, extend, and sell.