developing-gtkx-apps
Build GTK4 desktop applications with GTKX React framework. Use when creating GTKX components, working with GTK widgets, handling signals, or building Linux desktop UIs with React.
When & Why to Use This Skill
This Claude skill empowers developers to build native GTK4 desktop applications for Linux using the GTKX React framework. It bridges the gap between web development and native software engineering by providing a declarative React-based workflow for creating complex GTK widgets, handling native signals, and managing high-performance UI components like virtualized lists and grids.
Use Cases
- Developing native Linux desktop applications using familiar React patterns and hooks.
- Designing complex, responsive desktop layouts using native GTK4 widgets like Box, Grid, Stack, and Notebook.
- Implementing high-performance, data-heavy interfaces with virtualized ListView and ColumnView components.
- Handling native system signals and events (like clicks, toggles, and notifications) within a React component lifecycle.
- Creating professional Linux UIs with native elements such as HeaderBars, ActionBars, and declarative application menus.
| name | developing-gtkx-apps |
|---|---|
| description | Build GTK4 desktop applications with GTKX React framework. Use when creating GTKX components, working with GTK widgets, handling signals, or building Linux desktop UIs with React. |
Developing GTKX Applications
GTKX is a React framework for building native GTK4 desktop applications on Linux. It uses a custom React reconciler to render React components as native GTK widgets.
Quick Start
import { ApplicationWindow, render, quit } from "@gtkx/react";
import * as Gtk from "@gtkx/ffi/gtk";
const App = () => (
<ApplicationWindow title="My App" defaultWidth={800} defaultHeight={600}>
<Box orientation={Gtk.Orientation.VERTICAL} spacing={12}>
<Label label="Hello, GTKX!" />
<Button label="Quit" onClicked={quit} />
</Box>
</ApplicationWindow>
);
render(<App />, "com.example.myapp");
Widget Patterns
Container Widgets
Box - Linear layout:
<Box orientation={Gtk.Orientation.VERTICAL} spacing={12}>
<Label label="First" />
<Label label="Second" />
</Box>
Grid - 2D positioning:
<Grid.Root spacing={10}>
<Grid.Child column={0} row={0}>
<Label label="Top-left" />
</Grid.Child>
<Grid.Child column={1} row={0} columnSpan={2}>
<Label label="Spans 2 columns" />
</Grid.Child>
</Grid.Root>
Stack - Page-based container:
<Stack.Root visibleChildName="page1">
<Stack.Page name="page1" title="Page 1">
<Label label="Content 1" />
</Stack.Page>
<Stack.Page name="page2" title="Page 2">
<Label label="Content 2" />
</Stack.Page>
</Stack.Root>
Notebook - Tabbed container:
<Notebook.Root>
<Notebook.Page label="Tab 1">
<Content1 />
</Notebook.Page>
<Notebook.Page label="Tab 2">
<Content2 />
</Notebook.Page>
</Notebook.Root>
Paned - Resizable split:
<Paned.Root orientation={Gtk.Orientation.HORIZONTAL} position={280}>
<Paned.StartChild>
<SideBar />
</Paned.StartChild>
<Paned.EndChild>
<MainContent />
</Paned.EndChild>
</Paned.Root>
Virtual Scrolling Lists
ListView - High-performance scrollable list with selection:
<ListView.Root
vexpand
selected={[selectedId]}
selectionMode={Gtk.SelectionMode.SINGLE}
onSelectionChanged={(ids) => setSelectedId(ids[0])}
renderItem={(item: Item | null) => (
<Label label={item?.text ?? ""} />
)}
>
{items.map(item => (
<ListView.Item key={item.id} id={item.id} item={item} />
))}
</ListView.Root>
GridView - Grid-based virtual scrolling:
<GridView.Root
vexpand
renderItem={(item: Item | null) => (
<Box orientation={Gtk.Orientation.VERTICAL}>
<Image iconName={item?.icon ?? "image-missing"} />
<Label label={item?.name ?? ""} />
</Box>
)}
>
{items.map(item => (
<GridView.Item key={item.id} id={item.id} item={item} />
))}
</GridView.Root>
ColumnView - Table with sortable columns:
<ColumnView.Root
sortColumn="name"
sortOrder={Gtk.SortType.ASCENDING}
onSortChange={handleSort}
>
<ColumnView.Column
title="Name"
id="name"
expand
sortable
renderCell={(item: Item | null) => (
<Label label={item?.name ?? ""} />
)}
/>
{items.map(item => (
<ColumnView.Item key={item.id} id={item.id} item={item} />
))}
</ColumnView.Root>
DropDown - String selection widget:
<DropDown.Root>
{options.map(opt => (
<DropDown.Item key={opt.value} id={opt.value} label={opt.label} />
))}
</DropDown.Root>
HeaderBar
Pack widgets at start and end of the title bar:
<HeaderBar.Root>
<HeaderBar.Start>
<Button iconName="go-previous-symbolic" />
</HeaderBar.Start>
<HeaderBar.End>
<MenuButton.Root iconName="open-menu-symbolic" />
</HeaderBar.End>
</HeaderBar.Root>
ActionBar
Bottom bar with packed widgets:
<ActionBar.Root>
<ActionBar.Start>
<Button label="Cancel" />
</ActionBar.Start>
<ActionBar.End>
<Button label="Save" cssClasses={["suggested-action"]} />
</ActionBar.End>
</ActionBar.Root>
Controlled Input
Entry requires two-way binding:
const [text, setText] = useState("");
<Entry
text={text}
onChanged={(entry) => setText(entry.getText())}
placeholder="Type here..."
/>
Declarative Menus
<ApplicationMenu>
<Menu.Submenu label="File">
<Menu.Item
label="New"
onActivate={handleNew}
accels="<Control>n"
/>
<Menu.Section>
<Menu.Item label="Quit" onActivate={quit} accels="<Control>q" />
</Menu.Section>
</Menu.Submenu>
</ApplicationMenu>
Signal Handling
GTK signals map to on<SignalName> props:
clicked→onClickedtoggled→onToggledchanged→onChangednotify::selected→onNotifySelected
Widget References
import { useRef } from "react";
const entryRef = useRef<Gtk.Entry | null>(null);
<Entry ref={entryRef} />
// Later: entryRef.current?.getText()
Portals
import { createPortal } from "@gtkx/react";
{createPortal(<AboutDialog programName="My App" />)}
Constraints
- GTK is single-threaded: All widget operations on main thread
- Virtual lists need immutable data: Use stable object references
- ToggleButton auto-prevents feedback loops: Safe for controlled state
- Entry needs two-way binding: Use
onChangedto sync state
For detailed widget reference, see WIDGETS.md. For code examples, see EXAMPLES.md.