Modals and Portals
Portals let a component render children into a container other than its JSX parent, while it keeps owning those children's state, props, and lifetime.
createPortal
createPortal from @gtkx/react has the same signature as its React DOM namesake, with GTK4 containers in place of DOM nodes:
createPortal(children: ReactNode, container: GObject.Object | RootElement, key?: string): ReactPortalThe container is any live GObject.Object, or rootElement (also exported from @gtkx/react), the marker that mounts children at the top level with no GTK4 parent.
The container has to exist before the portal can target it, so capture it in state rather than a plain ref, which makes the portal render as soon as the widget is created:
import type * as Gtk from "@gtkx/gi/gtk";
import { GtkBox, GtkLabel } from "@gtkx/jsx/gtk";
import { createPortal } from "@gtkx/react";
import { useState } from "react";
const StatusArea = () => {
const [tray, setTray] = useState<Gtk.Box | null>(null);
return (
<>
<GtkBox ref={setTray} />
{tray && createPortal(<GtkLabel>Synced</GtkLabel>, tray)}
</>
);
};Portal children stay in the React tree of the component that rendered them, so context, state, and effects flow from where the portal is written, not from where the widgets land.
Windows
Windows portal themselves. A window element mounts at the top level wherever it sits in the JSX, so opening one is a conditional render:
import { GtkApplicationWindow } from "@gtkx/jsx/gtk";
const MirrorWindow = ({ open }: { open: boolean }) =>
open ? <GtkApplicationWindow title="Mirror" defaultWidth={400} defaultHeight={300} /> : null;A window element presents itself on mount and destroys the window on unmount. GtkApplicationWindow registers with the GtkApplication ancestor it finds in the React tree, and throws when there is none. Relationships between top-level windows are expressed with transientFor, which GtkWindow defaults to the nearest window ancestor in the React tree; pass it explicitly to point at another window, or pass null for a fully independent one.
Wire onCloseRequest to clear the state that mounted a secondary window, so React stays in charge of when it goes away.
Dialogs
Mounting an AdwDialog, or any element derived from it, presents the dialog; unmounting it closes the dialog. These elements come from @gtkx/jsx/adw, which exists once Adw-1 is in your libraries.
import { AdwDialog } from "@gtkx/jsx/adw";
import { GtkLabel } from "@gtkx/jsx/gtk";
const Notice = ({ onClose }: { onClose: () => void }) => (
<AdwDialog onClosed={onClose} title="Notice">
<GtkLabel>Nothing to report.</GtkLabel>
</AdwDialog>
);Reach for AdwDialog when a plain surface is enough, and for a more specific element such as AdwAlertDialog or AdwPreferencesDialog when you want its behavior. Each carries the same contract and takes its own props and children directly. A plain AdwDialog's children fill its whole surface, while a specialized one places them where its own layout expects: AdwPreferencesDialog takes AdwPreferencesPage children.
Set canClose={false} when the dialog is not ready to go away, and handle onCloseAttempt to decide what happens instead. Unmounting still closes the dialog unconditionally.
AdwAlertDialog is the message-and-buttons modal. Its heading and body props are plain strings, it takes a declarative responses array, and the chosen button's id arrives on onResponse:
import * as Adw from "@gtkx/gi/adw";
import { AdwAlertDialog } from "@gtkx/jsx/adw";
import { GtkEntry } from "@gtkx/jsx/gtk";
const RenameDialog = ({ onResponse }: { onResponse: (id: string) => void }) => (
<AdwAlertDialog
heading="Rename"
body="Pick a new name for this list."
defaultResponse="rename"
closeResponse="cancel"
responses={[
{ id: "cancel", label: "Cancel" },
{ id: "rename", label: "Rename", appearance: Adw.ResponseAppearance.SUGGESTED },
]}
onResponse={onResponse}
>
<GtkEntry placeholderText="List name" activatesDefault />
</AdwAlertDialog>
);Children fill the dialog's extra slot, below the heading and body and above the response buttons. extraChild is not part of the element's prop surface, so children are the only way to fill it.
Finding the parent window
useParentWindow() from @gtkx/react returns the Gtk.Window provided by the nearest window ancestor, or null when there is none. It resolves through the React tree, so a dialog portaled out of a window's subtree still finds that window.
The tutorial builds these surfaces in Mounting dialogs, Confirming a permanent delete, and A dialog that is a form. The exported API is in the @gtkx/react reference.
Next
Continue with CSS to style these surfaces with the css tagged template and GTK4's own CSS engine.