Native Apple UI, live in Lua

Make the idea
the app.

Build polished macOS and iOS interfaces with declarative Lua, real AppKit/UIKit controls, and no compile cycle between edits.

Edit. Run. See it immediately.

World Weather
London, England

Light rain

24°
HUMIDITY68%
WIND23 km/h W
VISIBILITY10 km
7-DAY FORECAST
FRI 24°SAT 20°SUN 22°MON 20°TUE 21°

A real native window, rendered from Lua

The short version

Less waiting.
More making.

SwiftUI is elegant. The compile-and-preview loop around it can be anything but. lua-objc keeps the expressive part and removes the ceremony between an edit and the thing you are building.

01

Native by default

AppKit and UIKit controls do the real work: selection, keyboard behavior, accessibility, focus, and platform metrics.

02

Fast to change

Edit a Lua controller or etlua view, run it directly, and get a fresh native window without an Xcode build step.

03

Easy to verify

Headless tests, screenshots, and native layout dumps make behavior and geometry inspectable by people and agents.

A small proof

Weather, without
the weather report.

One app. A real network request, native loading state, a location sidebar, and a seven-day forecast that scrolls when it needs to.

lua-objc Weather app showing a location sidebar and detailed seven-day forecast
EXAMPLE / WEATHER APPKIT

Stable structure belongs in etlua. State, network boundaries, and actions stay in Lua. The result is simple to read, simple to test, and still feels at home on the platform.

Read the weather walkthrough

Start with a folder

Your next screen
is a Lua file.

Keep the shape familiar: a model for data, a controller for actions, and views for declarative structure. The framework instantiates the controller returned by init.lua and calls its createWindow() method. Reusable view components return view trees.

Coming from PHP? Think Laravel with Blade.

The workflow is familiar: a controller prepares model data and passes it to a template. Here, Controller.lua renders views/*.etlua into real AppKit/UIKit controls. Partials, layouts, and reusable Lua view components keep presentation organized.

The key difference is lifetime. Controllers and native widgets stay alive across clicks, typing, and asynchronous results. Native callbacks invoke actions, and controllers explicitly update the views. Model changes do not yet trigger automatic reconciliation.

Use Laravel as a guide for separating responsibilities: models own queries and mutations, controllers coordinate actions, and views own presentation. This is an architectural comparison; the models are ordinary Lua code, with no Eloquent-style ORM implied.

Read the Laravel comparison
examples/weather/views/CityDetail.etluaVIEW
<% if #city.forecast > 0 then -%>
      <Divider />
      <Label text="7-day forecast" size="14" weight="semibold" />
      <ScrollView contentWidth="772" contentHeight="188" fixedHeight="200" fillWidth="true" horizontal="true">
        <HStack spacing="12" alignment="top" fixedWidth="772">
          <% for _, day in ipairs(city.forecast) do -%>
          <VStack spacing="4" alignment="center" fixedWidth="100">
            <Label text="<%= day.date %>" size="11" color="secondary" />
            <Image src="<%= day.icon %>" fixedWidth="28" fixedHeight="28" label="<%= day.desc %>" />
            <Label text="<%= day.tempMax or "--" %>°" size="16" weight="medium" />
            <Label text="<%= day.precipProbability or "--" %>% rain" size="10" color="secondary" />
          </VStack>
          <% end -%>
        </HStack>
      </ScrollView>
    <% end -%>
examples/hello/views/Window.etluaVIEW
<VStack flexGrow="1" padding="24" spacing="12" alignment="leading">
      <Label text="Hello from Lua" size="24" weight="bold" />
      <Label text="A SwiftUI-like API backed by native AppKit controls." size="14" />
      <SystemImage name="swift" size="64" label="Swift" />
</VStack>
examples/hello/Controller.luaLIVE
local ns  = require("AppKit")
local xml = require("ui.xml")

function Controller:createWindow()
  local cfg = xml.renderFile(VIEWS .. "Window.etlua")
  return ns.Window(cfg)
end
$ ./lua-objc examples/hello

Open source, Apple-shaped

A faster path from
thought to interface.

Contributors are very welcome. Bring a widget, an example, a test, a design idea, or simply a sharp question.