Skip to content

Slint for Node.js (Beta)

npm ↗

Slint ↗ is a UI toolkit that supports different programming languages. Slint-node is the integration with Node.js ↗, Deno ↗, and Bun ↗.

To get started you use the walk-through tutorial ↗. We also have a Getting Started Template ↗ repository with the code of a minimal application using Slint that can be used as a starting point to your program.

Warning: Beta Slint-node is still in the early stages of development: APIs will change and important features are still being developed.

The Slint Language Documentation ↗ covers the Slint UI description language in detail.

The API group in the sidebar lists types and functions generated from the TypeScript sources in this repository. Before you run pnpm -C docs/nodejs run dev or pnpm -C docs/nodejs run build, compile the native module and declarations from the repo root:

Terminal window
pnpm -C api/node run build
bash

To use Slint with Node.js, ensure the following programs are installed:

To use Slint with Deno, ensure the following programs are installed:

To use Slint with Bun, ensure the following programs are installed:

Slint-node comes with pre-built binaries for macOS, Linux, and Windows. If you’d like to use Slint-node on a system without pre-built binaries, you need to install additional software:

  1. In a new directory, create a new Node.js project by calling npm init ↗.
  2. Install Slint for your project using npm install slint-ui ↗.
  3. Create a new file called main.slint with the following contents:
import { AboutSlint, Button, VerticalBox } from "std-widgets.slint";
export component Demo inherits Window {
in-out property <string> greeting <=> label.text;
VerticalBox {
alignment: start;
label := Text {
text: "Hello World!";
font-size: 24px;
horizontal-alignment: center;
}
AboutSlint {
preferred-height: 150px;
}
HorizontalLayout { alignment: center; Button { text: "OK!"; } }
}
}
slint

This file declares the user interface.

  1. Create a new file called index.mjs with the following contents:
import * as slint from "slint-ui";
let ui = slint.loadFile(new URL("main.slint", import.meta.url));
let demo = new ui.Demo();
await demo.run();
js

This is your main JavaScript entry point:

  • Import the Slint API as an ECMAScript module ↗ module. If you prefer you can also import it as CommonJS ↗ module.
  • Invoke loadFile() to compile and load the .slint file.
  • Instantiate the Demo component declared in main.slint.
  • Run it by showing it on the screen and reacting to user input.
  1. Run the example with node index.mjs

For a complete example, see /examples/todo/node ↗.

  1. Create a new file called main.slint with the following contents:
import { AboutSlint, Button, VerticalBox } from "std-widgets.slint";
export component Demo inherits Window {
in-out property <string> greeting <=> label.text;
VerticalBox {
alignment: start;
label := Text {
text: "Hello World!";
font-size: 24px;
horizontal-alignment: center;
}
AboutSlint {
preferred-height: 150px;
}
HorizontalLayout { alignment: center; Button { text: "OK!"; } }
}
}
slint

This file declares the user interface.

  1. Create a new file called deno.json (a Deno Import Map ↗) with the following contents:
{
"imports": {
"slint-ui": "npm:slint-ui"
}
}
json
  1. Create a new file called index.ts with the following contents:
import * as slint from "slint-ui";
let ui = slint.loadFile(new URL("main.slint", import.meta.url));
let demo = new ui.Demo();
await demo.run();
ts

This is your main JavaScript entry point:

  • Import the Slint API as an ECMAScript module ↗ module through Deno’s NPM compatibility layer.
  • Invoke loadFile() to compile and load the .slint file.
  • Instantiate the Demo component declared in main.slint.
  • Run it by showing it on the screen and reacting to user input.
  1. Run the example with deno run --allow-read --allow-ffi --allow-sys index.ts
  1. In a new directory, create a new bun project by calling bun init ↗.
  2. Install Slint for your project using bun install slint-ui ↗.
  3. Create a new file called main.slint with the following contents:
import { AboutSlint, Button, VerticalBox } from "std-widgets.slint";
export component Demo inherits Window {
in-out property <string> greeting <=> label.text;
VerticalBox {
alignment: start;
label := Text {
text: "Hello World!";
font-size: 24px;
horizontal-alignment: center;
}
AboutSlint {
preferred-height: 150px;
}
HorizontalLayout { alignment: center; Button { text: "OK!"; } }
}
}
slint

This file declares the user interface.

  1. Clear the content of index.ts and add the following code:
import * as slint from "slint-ui";
let ui = slint.loadFile(new URL("main.slint", import.meta.url)) as any;
let demo = new ui.Demo();
await demo.run();
ts

This is your main TypeScript entry point:

  • Import the Slint API as an ECMAScript module ↗ module.
  • Invoke loadFile() to compile and load the .slint file.
  • Instantiate the Demo component declared in main.slint.
  • Run it by showing it on the screen and reacting to user input.
  1. Run the example with bun run index.ts

There are two ways to load a .slint file. Both compile the .slint markup and return constructor functions for each exported component.

Section titled “Option 1: import the .slint File (Recommended)”

Import .slint files directly as ES modules:

import * as slint from "slint-ui";
import { MainWindow } from "./ui/main.slint";
let component = new MainWindow({
counter: 42,
clicked: function() { console.log("hello"); }
});
js

Load slint-ui/register before your own code, so the runtime knows what a .slint file is:

Terminal window
node --import slint-ui/register app.mjs
deno run --preload npm:slint-ui/register app.ts
bun --preload slint-ui/register app.ts
bash

Bun also reads preload from bunfig.toml, so there the flag can live in the project configuration instead of the command line.

This is the recommended approach because:

  • Imports are declarative and statically analyzable
  • Works with TypeScript type checking (see TypeScript Support below)
  • Components, structs, and enums are available as named exports

You still import slint-ui for runtime helpers like ArrayModel and runEventLoop.

The flag is needed even though your code already imports slint-ui. A static import is resolved before any module body runs, so a hook installed while slint-ui loads always comes too late for the .slint file imported next to it.

Where a flag isn’t possible, these work without one:

import "slint-ui/register";
const { MainWindow } = await import("./ui/main.slint");
js
require("slint-ui/register");
const { MainWindow } = require("./ui/main.slint");
js

Call loadFile() to compile and load a .slint file at runtime:

import * as slint from "slint-ui";
let ui = slint.loadFile(new URL("ui/main.slint", import.meta.url));
let component = new ui.MainWindow({
counter: 42,
clicked: function() { console.log("hello"); }
});
js

Use this when you need to:

  • Load .slint files dynamically (e.g. based on user input or configuration)
  • Pass compiler options like style, includePaths, or libraryPaths

With both approaches, the exported component is available as a constructor function. The constructor takes an optional object to set initial property values and callbacks. The returned instance implements the ComponentHandle interface for showing, hiding, and accessing the window.

Given this .slint file:

ui/main.slint

export component MainWindow inherits Window {
callback clicked <=> i-touch-area.clicked;
in property <int> counter;
width: 400px;
height: 200px;
i-touch-area := TouchArea {}
}
slint

Instantiate it:

let component = new MainWindow({
counter: 42,
clicked: function() { console.log("hello"); }
});
js

Properties ↗ declared as out or in-out in .slint files are visible as JavaScript properties on the component instance.

main.slint

export component MainWindow {
in-out property <string> name;
in-out property <int> age: 42;
}
slint
let ui = slint.loadFile(new URL("main.slint", import.meta.url));
let instance = new ui.MainWindow();
console.log(instance.age); // Prints 42
instance.name = "Joe";
js

Callbacks ↗ declared in .slint files are visible as JavaScript function properties on the component instance. Invoke them as function to invoke the callback, and assign JavaScript functions to set the callback handler.

ui/my-component.slint

export component MyComponent inherits Window {
callback clicked <=> i-touch-area.clicked;
width: 400px;
height: 200px;
i-touch-area := TouchArea {}
}
slint

main.mjs

import * as slint from "slint-ui";
let ui = slint.loadFile(new URL("ui/my-component.slint", import.meta.url));
let component = new ui.MyComponent();
// connect to a callback
component.clicked = function() { console.log("hello"); };
// emit a callback
component.clicked();
js

The types used for properties in .slint design markup each translate to specific types in JavaScript. The following table summarizes the entire mapping:

.slint TypeJavaScript TypeNotes
intNumber
boolBoolean
floatNumber
stringString
colorRgbaColor
brushBrush
keysKeys
imageImageData
styled-textStyledTextStyled text parsed from markdown or plain text. Use StyledText.fromMarkdown() or StyledText.fromPlainText() to create instances.
data-transferDataTransferData associated with a drag-drop transfer.
lengthNumber
physical_lengthNumber
durationNumberThe number of milliseconds
angleNumberThe angle in degrees
relative-font-sizeNumberRelative font size factor that is multiplied with the Window.default-font-size and can be converted to a length.
structureObjectStructures are mapped to JavaScript objects where each structure field is a property.
arrayModel
Point{ x: number; y: number }A two-dimensional point with x and y coordinates.

Array properties ↗ can be set from JavaScript by passing either Array objects or implementations of the Model interface.

When passing a JavaScript Array object, the contents of the array are copied. Any changes to the JavaScript afterwards will not be visible on the Slint side.

Reading a Slint array property from JavaScript will always return a Model.

component.model = [1, 2, 3];
// component.model.push(4); // does not work, because assignment creates a copy.
// Use re-assignment instead.
component.model = component.model.concat(4);
js

Another option is to set an object that implements the Model interface.

An exported struct can be created either by defining an object literal or by using the new keyword.

my-component.slint

export struct Person {
name: string,
age: int
}
export component MyComponent inherits Window {
in-out property <Person> person;
}
slint

main.js

import * as slint from "slint-ui";
let ui = slint.loadFile(new URL("my-component.slint", import.meta.url));
let component = new ui.MyComponent();
// object literal
component.person = { name: "Peter", age: 22 };
// new keyword (sets property values to default e.g. '' for string)
component.person = new ui.Person();
// new keyword with parameters
component.person = new ui.Person({ name: "Tim", age: 30 });
js

A value of an exported enum can be set as string or by using the value from the exported enum.

my-component.slint

export enum Position {
top,
bottom
}
export component MyComponent inherits Window {
in-out property <Position> position;
}
slint

main.js

import * as slint from "slint-ui";
let ui = slint.loadFile(new URL("my-component.slint", import.meta.url));
let component = new ui.MyComponent();
// set enum value as string
component.position = "top";
// use the value of the enum
component.position = ui.Position.bottom;
js

You can declare globally available singletons ↗ in your .slint files. If exported, these singletons are accessible as properties on your main component instance. Each global singleton is represented by an object with properties and callbacks, similar to API that’s created for your .slint component.

For example the following .slint markup defines a global Logic singleton that’s also exported:

export global Logic {
callback to_uppercase(string) -> string;
}
slint

Assuming this global is used together with the MyComponent from the previous section, you can access Logic like this:

import * as slint from "slint-ui";
let ui = slint.loadFile(new URL("ui/my-component.slint", import.meta.url));
let component = new ui.MyComponent();
component.Logic.to_uppercase = (str) => {
return str.toUpperCase();
};
js

Note: Global singletons are instantiated once per component. When declaring multiple components for export to JavaScript, each instance will have their own instance of associated global singletons.

Importing a .slint file works in TypeScript out of the box — it compiles and runs. But TypeScript doesn’t know the types of the exported components until you generate them.

Use slint-compiler to generate a .d.ts file next to the .slint file:

Terminal window
slint-compiler -f typescript ui/main.slint -o ui/main.slint.d.ts
bash

This gives you full IDE autocomplete and type checking for all properties, callbacks, structs, and enums exported from the .slint file.

Wire it into package.json so types stay in sync:

{
"scripts": {
"generate": "slint-compiler -f typescript ui/main.slint -o ui/main.slint.d.ts",
"start": "npm run generate && node --import slint-ui/register app.mjs"
}
}
json

Add *.slint.d.ts to .gitignore — the generated file is a build artifact. The app runs without it; you just lose type checking.

Use moduleResolution: "bundler" so TypeScript resolves import "./main.slint" to the generated main.slint.d.ts:

{
"compilerOptions": {
"module": "esnext",
"moduleResolution": "bundler"
}
}
json

For a complete example, see /examples/todo/node-typescript ↗.


© 2026 SixtyFPS GmbH