Gestalt is a modular framework for building interactive applications in Nix. You describe initial state, actions, and a view, then compile to a target (CLI, TUI, Web). A small runtime executes actions, enforces action params, and renders the view.
This is an early, experimental project. APIs change and target capabilities are minimal. The core structure, targets, and example apps do work.
A Gestalt app is defined by a Nix module and transformed into an intermediate representation (IR). Targets consume the IR and produce runnable artifacts.
Flow:
- Nix module(s): state, actions, view, tests
- IR (computed by
lib.mkGestaltIR) - Target build (CLI/TUI/Web)
The module interface lives in src/modules/default.nix and src/modules/ir.nix.
Top-level options:
initialState: attrset, default{}actions: attrset of actionsview: list of functions(state) -> { elements = [...]; actions = [...] }stateHooks: list of functions(state) -> statetests.unit: list of unit tests (run during build)tests.e2e: list of e2e test specs
Action shape:
function:{ state, params } -> { state, effect }paramType: optional type for params (used by runtimes to prompt users)
View shape:
elements: list of{ content, annotations? }actions: list of{ content, actionId, annotations?, params? }
If a view action includes params, the runtime dispatches immediately without prompting.
Targets provide effects in target.capabilities.effects. Available effects (all targets):
nooploghttpRequestrandominvokeAction
Effects are plain attrsets with id and params. The web/CLI/TUI runtimes implement them.
Targets also expose UI/action annotations via target.capabilities.annotations (e.g. primary, danger, heading, code). These currently influence Web styling and can be used by future targets.
Implemented targets:
cli: Node.js TTY looptui: C++ terminal UI (static lib, uses libcurl for HTTP)web: Static HTML/CSS/JS with a tiny runtime and a built-in dev server
Each target builds a runnable derivation with a bin/<app> entry point.
The flake exports:
overlays.default: addslibandgestaltPlatformpackages.<system>: example apps
nix run github:lomenzel/gestalt#counter.extraTargets.tuinote: gestalt uses a patched version of nix to be able to compile nix functions to c++ or js. So you either need to run it with the patched version
nix run github:lomenzel/nix -- build github:lomenzel/gestalt#counter, or you need to have therecursive-nixexperimental feature enabled for gestalt to automatically switch to upstream nix compatibility mode
Gestalt’s IR transformation needs function reification and comparison. That requires a Nix fork providing:
builtins.reifybuiltins.sameFunction
If those builtins are missing, buildApplication automatically switches to an upstream compatibility mode (useUpstreamNix = true). This mode:
- Requires
srcandmainFile(default:default.nix) to be passed instead ofmodules - Builds using a nested flake and the Nix fork
- Requires the
recursive-nixsystem feature - ignores custom targets (only uses their name to find it in gestalt lib.)
Defined in src/platform/buildApplication.nix.
Arguments:
modules: list of module paths (native mode only)src: source directory (required in upstream mode)mainFile: defaults todefault.nixtarget: defaults togestaltPlatform.targets.tuiextraTargets: defaults to all targets (exposed viapassthru.extraTargets)useUpstreamNix: override automatic detection
Examples live in examples/:
counter: basic state/actions + unit/e2e testshttp: demonstrateshttpRequesteffectpracticeHelper: multi-step workflow withrandom+invokeAction
- Effects are minimal and partially implemented (HTTP is GET-only in CLI/TUI).
- E2E tests are still pretty barebones.
- Some Nix features require the custom fork (see above).