developing-gtkx-apps

majiayu000's avatarfrom majiayu000

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.

⭐0stars🔀0forks📁View on GitHub🕐Updated Jan 5, 2026

When & Why to Use This Skill

This Claude skill empowers developers to build native GTK4 desktop applications for Linux using the GTKX React framework. By leveraging a custom React reconciler, it allows for the creation of high-performance, native Linux UIs using familiar React patterns, declarative JSX syntax, and efficient state management.

Use Cases

  • Developing native Linux desktop applications by utilizing React's component-based architecture and hooks.
  • Designing complex UI layouts with native GTK4 widgets such as Grids, Stacks, and Notebooks through declarative code.
  • Implementing high-performance, virtualized lists and tables (ListView, ColumnView) for data-intensive desktop software.
  • Handling native system signals and user interactions using familiar React event props like onClicked and onChanged.
  • Building modern Linux interfaces with advanced components like HeaderBars, ActionBars, and declarative application menus.
namedeveloping-gtkx-apps
descriptionBuild 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 → onClicked
  • toggled → onToggled
  • changed → onChanged
  • notify::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 onChanged to sync state

For detailed widget reference, see WIDGETS.md. For code examples, see EXAMPLES.md.