tilescript is a layout runtime designed for Wayland compositors and tiling window managers.
It lets you define workspace layouts in JSX/TSX, Lua, or Fennel together with CSS, then evaluates those layouts in Rust and hands the resulting placement decisions to a window manager or compositor adapter.
Current adapter target:
Hyprland
Playground: https://akisarou.github.io/tilescript/
- JSX/TSX, Lua, and Fennel layouts
- CSS-driven layout
- runtime layout selection by workspace index, workspace name, and monitor
- flex-inferred resize behavior with configurable resize step and minimum pane size
- Hyprland plugin integration
- CSS language server crates and a VS Code client package
layouts/master-stack/index.tsx
import type { LayoutContext } from "@tilescript/sdk/layout";
import "./index.css";
export default function layout(ctx: LayoutContext) {
return (
<workspace>
<slot take={1} class="master-slot" />
{ctx.windows.length > 1 ? (
<group class="stack-group">
<slot class="stack-slot" />
</group>
) : null}
</workspace>
);
}layouts/master-stack/index.lua
local h = require("tilescript")
---@param ctx Tilescript.LayoutContext
return function(ctx)
return h.workspace() {
h.slot({
take = 1,
class = "master-slot",
}),
h.when(#ctx.windows > 1) {
h.group({ class = "stack-group" }) {
h.slot({
class = "stack-slot",
}),
},
},
}
endlayouts/master-stack/index.fnl
(local h (require "tilescript"))
(fn [ctx]
((h.workspace)
[(h.slot {:take 1
:class "master-slot"})
((h.when (> (# ctx.windows) 1))
[((h.group {:class "stack-group"})
[(h.slot {:class "stack-slot"})])])]))layouts/master-stack/index.css
workspace {
display: flex;
flex-direction: row;
gap: 6px;
padding: 6px;
width: 100%;
height: 100%;
.master-slot {
flex-basis: 0;
flex-grow: 3;
min-width: 0;
min-height: 0;
}
.stack-group {
display: flex;
flex-direction: column;
gap: 6px;
flex-basis: 0;
flex-grow: 2;
min-width: 0;
.stack-slot {
flex-basis: 0;
flex-grow: 1;
min-height: 0;
}
}
}tilescript loads a config root directory.
If config_path is omitted, the default config root is ~/.config/tilescript.
At minimum, that directory contains:
- a config entry such as
config.{ts, tsx, js, jsx, lua, fnl}. - one or more layouts under
layouts/<name>/ - optional root
index.cssfor shared stylesheet rules
For starter layouts and config skeletons, see examples/.
Typical project layout:
config.ts
index.css
layouts/
master-stack/
index.tsx
index.css
dwindle/
index.lua
index.css
To use tilescript with Hyprland:
- Build the plugin with
make hypr-plugin. - Run
make hypr-plugin-snippetand paste the printedpluginblock into your Hyprland config. - Set
config_pathto your config root directory. - Reload Hyprland or reload the plugin after changing layouts or config.
Example Hyprland config:
plugin = /absolute/path/to/tilescript-hypr.so
plugin {
tilescript-hypr {
config_path = /absolute/path/to/your/tilescript-config/
}
}
general {
# disable gaps. those should be better configured via CSS
gaps_in = 0
gaps_out = 0
layout = tilescript
}Related keybindings:
$mainMod = ALT
bind = $mainMod, h, tilescript-hypr, focus, l
bind = $mainMod, j, tilescript-hypr, focus, d
bind = $mainMod, k, tilescript-hypr, focus, u
bind = $mainMod, l, tilescript-hypr, focus, r
bind = $mainMod CTRL, h, tilescript-hypr, resize, l
bind = $mainMod CTRL, j, tilescript-hypr, resize, d
bind = $mainMod CTRL, k, tilescript-hypr, resize, u
bind = $mainMod CTRL, l, tilescript-hypr, resize, r
bind = $mainMod SHIFT, h, tilescript-hypr, move, l
bind = $mainMod SHIFT, j, tilescript-hypr, move, d
bind = $mainMod SHIFT, k, tilescript-hypr, move, u
bind = $mainMod SHIFT, l, tilescript-hypr, move, r
bind = $mainMod, 1, tilescript-hypr, workspace, 1
bind = $mainMod, 2, tilescript-hypr, workspace, 2
bind = $mainMod, 3, tilescript-hypr, workspace, 3
bind = $mainMod, 4, tilescript-hypr, workspace, 4
bind = $mainMod, 5, tilescript-hypr, workspace, 5
bind = $mainMod, 6, tilescript-hypr, workspace, 6
bind = $mainMod, 7, tilescript-hypr, workspace, 7
bind = $mainMod, 8, tilescript-hypr, workspace, 8
bind = $mainMod, 9, tilescript-hypr, workspace, 9
bind = $mainMod, 0, tilescript-hypr, workspace, 10
bind = $mainMod SHIFT, 1, tilescript-hypr, movetoworkspace, 1
bind = $mainMod SHIFT, 2, tilescript-hypr, movetoworkspace, 2
bind = $mainMod SHIFT, 3, tilescript-hypr, movetoworkspace, 3
bind = $mainMod SHIFT, 4, tilescript-hypr, movetoworkspace, 4
bind = $mainMod SHIFT, 5, tilescript-hypr, movetoworkspace, 5
bind = $mainMod SHIFT, 6, tilescript-hypr, movetoworkspace, 6
bind = $mainMod SHIFT, 7, tilescript-hypr, movetoworkspace, 7
bind = $mainMod SHIFT, 8, tilescript-hypr, movetoworkspace, 8
bind = $mainMod SHIFT, 9, tilescript-hypr, movetoworkspace, 9
bind = $mainMod SHIFT, 0, tilescript-hypr, movetoworkspace, 10
bind = $mainMod, q, tilescript-hypr, closewindow
bind = $mainMod, f, tilescript-hypr, fullscreendocs/config.mddocs/hyprland.mddocs/jsx.mddocs/css.mddocs/css-lsp.mddocs/development.mddocs/playground.md
tilescript builds on a small set of foundational libraries:
stylo,cssparser, andselectorsfor CSS parsing and selector machinerytaffyfor layout computationoxcandoxc_resolverfor JS/TS/TSX parsing and module graph analysisrquickjsfor the native JS runtimemluafor the native Lua runtimeleptosandleptos_routerfor the browser playground UIwasm-bindgenandweb-sysfor browser/WASM interopmonaco-editor,monaco-vim, andwasmoonfor the playground editor and browser Lua execution