Claude-skill-registry 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.
git clone https://github.com/majiayu000/claude-skill-registry
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/developing-gtkx-apps" ~/.claude/skills/majiayu000-claude-skill-registry-developing-gtkx-apps && rm -rf "$T"
skills/data/developing-gtkx-apps/SKILL.mdDeveloping 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:
→clickedonClicked
→toggledonToggled
→changedonChanged
→notify::selectedonNotifySelected
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
to sync stateonChanged
For detailed widget reference, see WIDGETS.md. For code examples, see EXAMPLES.md.