---
title: "Architecture"
description: "How GTKX's generated bindings, React renderer, TypeScript runtime, and Rust native bridge fit together."
---

# Architecture

GTKX runs React in Node.js and renders through native Adwaita and GTK4 objects. React owns state and reconciliation; native libraries supply application structure, widgets, layout, input, accessibility, and rendering.

## The core layers

The [package map](/contributing/tech-stack#package-map) lists package responsibilities. At runtime, a JSX update follows this path:

```text
Application components
        ↓
Generated @gtkx/jsx elements
        ↓
@gtkx/react renderer
        ↓
Generated @gtkx/gi methods
        ↓
@gtkx/runtime
        ↓
@gtkx/native
        ↓
Native libraries
```

The [renderer](https://github.com/gtkx-org/gtkx/tree/main/packages/react) translates React commits into native operations. Generated methods supply signatures to the [runtime](https://github.com/gtkx-org/gtkx/tree/main/packages/runtime), which converts values and calls the [native bridge](https://github.com/gtkx-org/gtkx/tree/main/packages/native).

GI bindings also work without React: generated functions and classes call the runtime themselves, and GObject subclasses register through `registerClass` from `@gtkx/runtime` (see [Subclassing](/v2/guide/subclassing)). Component libraries build on JSX and GI APIs and do not depend on how native pointers are represented.

## Generation and execution

GTKX reads [GObject Introspection](https://gi.readthedocs.io/en/latest/) Repository (GIR) files during code generation, before the application runs.

```text
 GIR libraries + element configuration
                   ↓
             @gtkx/codegen
                   ↓
@gtkx/gi + @gtkx/jsx + project reference
```

During generation, `@gtkx/codegen` reads the configured GIR libraries and their transitive dependencies. It determines JavaScript names, TypeScript types, callable signatures, transfer rules, and the metadata needed by the renderer. It emits two linked package stores, `@gtkx/gi` and `@gtkx/jsx`, plus a project reference when enabled.

During execution, Node imports the generated ESM classes and methods. Their descriptors tell `@gtkx/runtime` and the native bridge how to resolve and call functions in the installed shared libraries through libffi. Native type lookup and GObject property access happen at runtime. GIR analysis belongs to code generation; `@gtkx/runtime` receives call descriptors from its consumers and does not load libgirepository.

The project's library selection and GIR versions define the generated API. Changing a GIR search path changes generated declarations; it does not install a native library that implements those declarations. See [Configuration and Codegen](/v2/guide/configuration-and-codegen) for the application-facing configuration.

The entry points for this boundary are the [codegen runner](https://github.com/gtkx-org/gtkx/blob/main/packages/codegen/src/runner.ts), the [runtime callable adapter](https://github.com/gtkx-org/gtkx/blob/main/packages/runtime/src/fn.ts), and the [native binding API](https://github.com/gtkx-org/gtkx/blob/main/packages/native/src/api/bind.rs).

## A render and an interaction

Consider a component that renders a button and updates a label when the button is clicked. Its path through GTKX is:

1. The generated JSX component turns its props into a React host element identified by its GType name. Element-valued props become named child slots.
2. React reconciles the tree and asks GTKX's host configuration to create or update an instance.
3. The renderer uses generated metadata and registered element behaviors to construct the native object, apply values, and place children through the appropriate container API.
4. Generated GI methods describe the native calls. Runtime converts their arguments, and the Rust addon prepares native storage and invokes the underlying C functions.
5. GTK delivers the button's signal through the GLib main context. The runtime calls the connected JavaScript handler, and the renderer gives that handler React's discrete event priority.
6. The handler changes React state. React reconciles again, and the renderer updates the existing native label where the element's identity permits reuse.

React's component tree and the native object tree have different shapes. Function components have no native object of their own. Named slots and parent-created page objects introduce additional routing, and portals can place a native object outside its surrounding React subtree while preserving React context. The [renderer page](/contributing/react-renderer) explains those differences.

## One owning event-loop thread

The native addon acquires GLib's default [main context](https://docs.gtk.org/glib/main-loop.html) on the Node thread that initializes it. It integrates that context with [libuv](https://docs.libuv.org/en/v1.x/) using prepare, timer, and file-descriptor polling handles. Node remains the outer event loop, allowing Node I/O, timers, promises, React scheduling, and GLib sources to make progress in the same application process.

Application startup is adapted to that arrangement. The generated GIO application override invokes GLib's local command-line handling to register and activate an application without entering a blocking `g_application_run()` loop. The application keeps the Node loop alive while active, and its teardown releases that hold. React's application component waits for activation before rendering its children.

The owning-thread requirement extends to native wrappers and widget operations. Worker threads are useful for computation with plain data, which can be sent back to the owning thread. They are not additional GTK render threads. Details and source links are in [Native Runtime](/contributing/native-runtime).

## State and ownership

Component, wrapper, and native allocation lifetimes have separate owners:

| Lifetime                    | Owner                                                                                | GTKX's role                                                                                      |
| --------------------------- | ------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------ |
| Component lifetime          | React's tree, keys, and effects.                                                     | Mount, update, and unmount host instances; release renderer handlers and behaviors.              |
| JavaScript wrapper lifetime | JavaScript reachability and native wrapper references.                               | Preserve wrapper identity for tracked native objects and associate wrappers with native handles. |
| Native allocation lifetime  | GObject reference counts or each native type's copy, reference, and free operations. | Honor ownership descriptors and release allocations through the correct mechanism.               |

Unmounting a component removes its placement and renderer-managed connections. Other JavaScript or native references can keep the object alive. Windows and dialogs also have explicit presentation and close behavior. When investigating a memory bug, check both the React lifetime and the native ownership contract.

A controlled prop and a native widget property represent the same application state. Writing a prop can itself emit a native signal. The renderer tracks its own mutations and suppresses configured user-event signals caused by those writes so they do not become feedback loops. User input continues through the connected handler.

## Finding the responsible layer

| Observable problem                                                           | Start reading                                                                                                                         |
| ---------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------- |
| A generated method has an incorrect type, argument order, or return shape.   | `packages/codegen/src/gir`, `analysis`, and `store/gi`.                                                                               |
| A correctly described call produces an invalid value or loses native memory. | `packages/runtime/src/fn.ts`, then `packages/native/src/ffi/codec` and `packages/native/src/handle.rs`.                               |
| A JSX prop is missing or has the wrong type.                                 | `packages/codegen/src/store/jsx` and `packages/config/src/builtin-elements.ts`.                                                       |
| A prop type is correct but the widget receives the wrong update.             | `packages/react/src/reconciler/apply-props.ts` and the registered element behaviors.                                                  |
| A child appears in the wrong place, disappears, or fails to reorder.         | `packages/react/src/reconciler/child-routing.ts` and `placement.ts`.                                                                  |
| Signals, timers, or shutdown stop making progress.                           | `packages/codegen/overrides/gio/overrides/application.ts`, `packages/runtime/src/lifecycle.ts`, and `packages/native/src/runloop.rs`. |

A renderer symptom can begin in generated metadata; a native ownership failure can begin in an incorrect GIR annotation. Follow the failing operation across the relevant boundaries.
