Install⋮

nativelayout

Write the screen once. Get real native views.

Ready to build native interfaces from a plain text file? No WebView, no canvas: a .nl file written in HTML becomes the buttons, fields and lists of the platform. Choose from the following options to get started.

How it works

A screen is one text file: HTML you already know, plus three additions. {value}, if / for lines, and a style with simple selectors. The logic stays in your language, in a screen function.

tasks.nl
<nativelayout>
<link rel="stylesheet" href="tasks.css">

<body>
  <header>
    <img id="back" src="icon:arrow-left">
    <h1>Tasks</h1>
  </header>

  if pending == 0:
    <p>Nothing to do.</p>
  else:
    <p>{pending} pending</p>
  end

  <input id="title" placeholder="New task">
  <button id="add">Add</button>

  for task in tasks:
    <div class="card" key="{task.id}">
      <p>{task.title}</p>
    </div>
  end
</body>
</nativelayout>
TasksScreen.kt
// one function holds the state and the taps
fun NativeView.TasksScreen(): Page {
  val s = state { mutableMapOf<String, Any?>(
    "pending" to 2, "tasks" to loadTasks()) }

  if (click("add")) {
    addTask(s, value("title"))
    setValue("title", "")
  }
  if (click(".card")) {
    toggleTask(s, clickIndex())
  }

  return view("tasks.nl", s)
}

Features

Native outputButtons, fields, lists and images are real platform views.
HTML you knowTags and CSS-like selectors, with variables through var(--name).
Conditions and loopsif, elif, else and for, right in the file.
Efficient updatesEach render is diffed and only changes are applied. Typed text is kept.
Long listsGive items a key, and mark a container virtual so only the items on screen become views.
Header, footer and iconsFixed header and footer, and 30 built-in icons with no image files.
Layers and groupsDialogs and bottom sheets with dialog, grouped fields with fieldset and legend.
States and dark themeStyle :pressed, :disabled and :focus, and switch palettes with prefers-color-scheme.
Clear errorsEvery mistake in a .nl file is reported with its line, in red, on the screen.
Absolute positionNEWposition: absolute with top, right, bottom, left and z-index.
Background imagesNEWbackground-image, background-size, background-position and object-fit.
Animations and navigationfade and slide animations, plus go, goBack and goRoot between screens.

Styling

Style it like CSS: variables, pressed states, gradients, shadows and a dark theme, in a plain .css file linked from the screen.

tasks.css
:root { --bg: #F2F2F2; --card: #EDE9FE; --accent: #2F81F7; }

/* dark theme */
@media (prefers-color-scheme: dark) {
  :root { --bg: #0F172A; --card: #1E293B; }
}

body  { background: var(--bg); padding: 1.25rem; gap: 1rem; }
.card { background: var(--card); border-radius: 0.75rem;
        padding: 0.875rem; box-shadow: 0 2px 4px #0003; }
#add  { background: var(--accent); color: #FFFFFF; font-weight: bold; }
#add:pressed { background: #1F6FE0; }
products.nl
<!-- only items on screen become views -->
<div virtual style="gap: 0.5rem">
  for p in products:
    <div class="item" key="{p.id}">
      <p class="name">{p.name}</p>
      <p class="price">{p.price}</p>
    </div>
  end
</div>

Targets

One file, any language. Each language supplies an Expander, an Engine and a native host. The .nl file stays the same.

LanguageTargetStatus
KotlinAndroidimplemented
PythonKivyin progress
PythonTkinterin progress
JavaAndroidplanned
SwiftiOSplanned
JS / TSReact Nativeplanned
DartFlutterplanned
C#.NET MAUIplanned
C++ / Rust / GoQt, desktopplanned

Python lives in the python/ folder. Want to add a language? See the contributing guide.

Install on Android

Published through JitPack. Add both artifacts with the same version. Minimum SDK 21.

settings.gradle.kts
dependencyResolutionManagement {
  repositories {
    google()
    mavenCentral()
    maven("https://jitpack.io")
  }
}
app/build.gradle.kts
dependencies {
  implementation("com.github.getnativelayout.nativelayout:nativelayout-android:v1.2.0")
  implementation("com.github.getnativelayout.nativelayout:nativelayout-core:v1.2.0")
}

To try a screen without creating a project, open the online editor. It has code, data and a live preview, and nothing to install.

Read the article

The motivation, the proposal and the first results on long lists are described in the technical article: Rafael, A. C. (2026). Describing Native Android Interfaces with HTML and CSS: A Proposal for Simplification. Zenodo. doi.org/10.5281/zenodo.23470439

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects nativelayout 1.2.0. View source or report an issue.

nativelayout

Except as otherwise noted, this site is licensed under the Mozilla Public License 2.0, and the specification under the Creative Commons Attribution-ShareAlike 4.0 International License.

Built with .nl