Configuration and Codegen
Codegen is driven from gtkx.config.ts, which declares which libraries to generate bindings for, and your application ID.
The config file
defineConfig from @gtkx/config types your config for editor completion and validates it when the CLI loads the file:
import { defineConfig } from "@gtkx/config";
export default defineConfig({
libraries: ["Gtk-4.0", "Adw-1"],
applicationId: "com.gtkx.tutorial",
});mergeConfig(base, override) layers a project config over a shared base. A $development or $production block layers over the top level, per mode.
Every option
applicationId is the only required key; the rest have defaults.
applicationId: the GApplication identifier the app registers under, in reverse-DNS form (com.example.Tasks).libraries: the GIR libraries to bind, asName-Version.Gtk-4.0is the default, and joins any list that does not already name a Gtk version; the bare string"*"(never an array entry) binds everything on the GIR path.girPath: directories searched for.girfiles ahead of the standard locations.reactCompiler: the React Compiler, on by default.falsedisables it; an object forwardscompilationModeandpanicThreshold.codegen: false: skips generation, so the project imports whatever binding store is already installed.userEventSignals: signals, keyed by GLib type name, that GTKX suppresses while writing to a widget itself. Entries merge into the defaults.elements: the element customizations:behaviorsis the module default-exporting yourdefineElementsmap,configsets per-type codegen output (component,props,omittedProps,isLazy).
What codegen emits
Codegen writes packages into node_modules/.gtkx and links them into node_modules/@gtkx, so imports resolve without either appearing in your package.json:
@gtkx/giis the introspected API, one subpath per namespace (@gtkx/gi/gtk,@gtkx/gi/adw): the classes, enums, and functions you call imperatively, for refs and values such asGtk.Orientation.VERTICAL.@gtkx/jsxis the React layer, likewise per namespace (@gtkx/jsx/gtk,@gtkx/jsx/adw): a PascalCase component per widget (GtkButton,AdwHeaderBar), aPropsinterface for each, and aReact.JSX.IntrinsicElementsaugmentation.
A few bindings take a NUL-terminated C string that GIR describes as a byte array (GLib.Variant.newBytestring(string: number[])), so the value silently stops at the first zero byte. Binary payloads go through GLib.Bytes and GLib.Variant.newFromBytes.
The JSX prop model
Every GIR class descending from GObject becomes an intrinsic element whose props follow five rules:
- Properties become camelCase props. Writable, construct, and construct-only properties become optional props:
show-title-buttonsisshowTitleButtons. - Almost every property gets a notify handler.
onNotifyXreceives(value, self), read-only properties included, so you can observe what GTK4 changes on its own. The element-accepting object properties below are the exception: they carry their value as a child element instead. - Object-typed props also accept elements. A writable, non-construct-only property typed as a GObject class takes a
ReactElementas well as an instance, and the reconciler manages the child. - Signals become
onhandlers.clickedisonClicked,row-activatedisonRowActivated, and the handler receives the signal's parameters followed byself. refyields the@gtkx/giinstance. Every element acceptsref?: Ref<Self | null>(Gtk.Button,Adw.ToastOverlay), the escape hatch to the imperative API.
import type * as Gtk from "@gtkx/gi/gtk";
import { GtkButton } from "@gtkx/jsx/gtk";
import { useRef } from "react";
const SaveButton = () => {
const buttonRef = useRef<Gtk.Button | null>(null);
return <GtkButton label="Save" onClicked={(self) => self.setSensitive(false)} ref={buttonRef} />;
};Generating element reference docs
gtkx docs writes one markdown page per JSX element, by default into docs/reference:
gtkx docsEach page carries the widget's documentation, hierarchy, slot rules, props, signal handlers, and ref methods with their signatures. gtkx docs --help covers the output directory and link root.
Advanced: Customizing elements
A GtkScale's marks have no property behind them, only addMark and clearMarks, and adding a child is insertChildAfter on a GtkBox but addTopBar on an AdwToolbarView. Element behaviors cover what property setting cannot: lifecycle hooks bound to a GLib type, which the reconciler calls as elements of that type are created, populated, updated, and removed. Every hook is listed in the ElementBehavior reference: attach, reorder, and detach place, move, and remove a child in a slot, resolve returns the object the container made for that child, and flush runs once the surrounding commit has placed every child. update runs on each commit with the previous and next props, and the prop names it returns are the ones GTKX will not also set as plain properties.
setCursorFromName is another method with no property behind it. Default-export a map keyed by GLib type name, wrapping each behavior in defineBehavior with the class it applies to:
// src/elements.ts
import type * as Gtk from "@gtkx/gi/gtk";
import { defineBehavior, defineElements } from "@gtkx/react/config";
export default defineElements({
GtkWidget: {
behaviors: [
defineBehavior<Gtk.Widget>({
update: (widget, prev, next) => {
if (!Object.is(prev.cursorName, next.cursorName) && typeof next.cursorName === "string") {
widget.setCursorFromName(next.cursorName);
}
return ["cursorName"];
},
}),
],
},
});Pass the class as the type argument so the hooks are typed. Point elements.behaviors at the module, then declare the prop on the generated interface:
// gtkx.config.ts
import { defineConfig } from "@gtkx/config";
export default defineConfig({
libraries: ["Gtk-4.0", "Adw-1"],
applicationId: "com.gtkx.tutorial",
elements: { behaviors: "./src/elements.ts" },
});// src/augmentations.d.ts
import "@gtkx/jsx/gtk";
declare module "@gtkx/jsx/gtk" {
interface GtkWidgetProps {
cursorName?: string | null | undefined;
}
}The leading import is what makes this an augmentation. Without a top-level import or export, declare module becomes an ambient module declaration that shadows the generated one, and @gtkx/jsx/gtk stops exporting elements.
A behavior on a type covers every element descending from it, and your behaviors run before the built-in ones, so they override existing prop and slot handling. isLazy: true in the same map marks a type whose GObject its parent container creates.
Next
With the codegen pipeline in hand, continue to Async Operations.