First Native App
Perry compiles declarative TypeScript UI code to native platform widgets. No Electron, no WebView — real AppKit on macOS, UIKit on iOS, GTK4 on Linux, Win32 on Windows.
A Simple Counter
Create counter.ts:
import { App, Text, Button, VStack, State } from "perry/ui";
const count = State(0);
App("My Counter", () =>
VStack([
Text(`Count: ${count.get()}`),
Button("Increment", () => {
count.set(count.get() + 1);
}),
Button("Reset", () => {
count.set(0);
}),
])
);
Compile and run:
perry counter.ts -o counter
./counter
A native window opens with a label and two buttons. Clicking “Increment” updates the count in real-time.
How It Works
App(title, renderFn)— Creates a native application window. The render function defines the UI.State(initialValue)— Creates reactive state. When you call.set(), the UI re-renders.VStack([...])— Vertical stack layout (like SwiftUI’s VStack or CSS flexbox column).Text(string)— A text label. Template literals with${state.get()}update reactively.Button(label, onClick)— A native button with a click handler.
A Todo App
import { App, Text, Button, TextField, VStack, HStack, State, ForEach } from "perry/ui";
const todos = State<string[]>([]);
const input = State("");
App("Todo App", () =>
VStack([
HStack([
TextField(input, "Add a todo..."),
Button("Add", () => {
const text = input.get();
if (text.length > 0) {
todos.set([...todos.get(), text]);
input.set("");
}
}),
]),
ForEach(todos, (todo, index) =>
HStack([
Text(todo),
Button("Remove", () => {
const items = todos.get();
todos.set(items.filter((_, i) => i !== index));
}),
])
),
])
);
Cross-Platform
The same code runs on all 6 platforms:
# macOS (default)
perry app.ts -o app
./app
# iOS Simulator
perry app.ts -o app --target ios-simulator
# Web (generates HTML)
perry app.ts -o app --target web
open app.html
# Other platforms
perry app.ts -o app --target windows
perry app.ts -o app --target linux
perry app.ts -o app --target android
Each target compiles to the platform’s native widget toolkit. See Platforms for details.
Adding Styling
import { App, Text, Button, VStack, State } from "perry/ui";
const count = State(0);
App("Styled Counter", () => {
const label = Text(`Count: ${count.get()}`);
label.setFontSize(24);
label.setColor("#333333");
const btn = Button("Increment", () => count.set(count.get() + 1));
btn.setCornerRadius(8);
btn.setBackgroundColor("#007AFF");
const stack = VStack([label, btn]);
stack.setPadding(20);
return stack;
});
See Styling for all available style properties.
Next Steps
- Project Configuration — Set up
package.jsonfor Perry projects - UI Overview — Complete guide to Perry’s UI system
- Widgets Reference — All available widgets
- State Management — Reactive state and bindings