This is not generated API documentation. Go already gives you that, and it is good here — the house rule is that reasoning goes in the doc comment, so go doc ./widget Gestures is several pages of why. What go doc cannot tell you is which of the module's packages you should ever open, and in what order. That is this page.
The authoritative references, for when this page and the code disagree. go doc <package> for any symbol; internal/apisurface/testdata/api-surface.txt for the complete list of every exported name in the module, which is generated and fails the build when it drifts. Trust those over this page, which is prose and can rot.
Grouped by when you first need them, not alphabetically. Most apps only ever import the first tier.
| Package | What it is for | The names you will use |
|---|---|---|
app | The runtime. It binds a widget tree to a platform and runs frames. Also the headless driver, which is the same core with no display — so behaviour verified in a test is the shipping behaviour. | Run, Config, Headless |
widget | The declarative layer: immutable widget values, the retained element tree they reconcile into, state, keys, focus and gestures. This is where you will spend your time. | Widget, State, StateBase[W], Ctx, Column, Row, Text, Interactive, LazyList, Canvas, WithKey, Provide |
geom | Points, sizes, rectangles and insets. Small immutable float32 values; methods return new ones. In logical pixels — see the trap below. | Pt, Size, Rect, RectXYWH, Insets, InsetsAll |
paint | The drawing layer. For most apps this means colours; for a custom painter it also means the Canvas interface of primitives. | Color, RGB, Lerp, Canvas, Path, Transform |
theme | The styled layer: a token struct plus components wired to consult it. Deliberately a separate package from widget, which stays unstyled primitives — the material/widgets split, as two ordinary Go packages. | Theme, Of, Auto, Light, Dark, Button, Card, Field, ListTile |
| Package | What it is for | When you reach for it |
|---|---|---|
apptest | Drives a UI in a test and asserts on pixels or on the semantics tree. Golden-image plumbing, tolerance, and legible failures. | Step 4 of the learning path — earlier than you think. |
layout | The render-layer protocol: the Box interface, Constraints, hit testing and semantics. You do not implement boxes; you import this for the enum values the widget layer takes. | CrossStretch, MainSpaceBetween, SemInfo, RoleButton — and Constraints when a layout surprises you. |
anim | Curves and a Controller driven by the app's frame tickers. Only for explicit animation; the implicit wrappers live in widget. | Motion that loops, reverses mid-flight, or drives several properties off one clock. |
shell | The platform contract — and the home of every capability interface and its event types. Apps import it for the types, not to implement it. | shell.Key, shell.ShareItem, shell.HapticKind, shell.CursorShape. |
shell/{desktop,web,mobile,terminal} | The backends. The framework never imports these; app.Run picks one for your target, so you normally do not name them either. | Only when embedding gophics in a host that already owns the window. |
chart | A built-in declarative charting library — bars, lines, points, rules, heatmaps, ranges over scales. Depends only on gophics's own layers and the standard library. | Before you draw a conventional chart by hand. |
fetch | One HTTP request, without linking net/http into a browser build. On every platform but the browser it is net/http; on js/wasm it is the browser's fetch(), and the difference is 2MB of binary. | Any app that talks to a network and might target the web. |
intl | Locale-correct numbers, money and dates. Not a full CLDR pipeline, deliberately — see the package doc for the size argument, and the walls for what is missing. | Anything where numbers are the content: a ledger, a dashboard, a table. |
| Package | What it is for |
|---|---|
text | The text stack: shaping, bidi, font fallback, line breaking, built on go-text/typesetting. The widget layer calls it for you. You come here to measure or to lay out a paragraph by hand. |
input | Per-frame poll-style input state for games: which keys are held right now, what was pressed this frame, where the pointer is. Held state is global, so a game canvas can poll WASD without focus. Reached via ctx.Input(). |
sound (/device, /mp3, /ogg, /pitch, /procedural) | A pure-Go DSP mixer for game and UI audio. No device dependency in the core, so mixing is deterministic and unit-testable with no audio hardware. |
tools/uitrace | The scroll-measurement harness: it records one real flick on a device and replays the identical finger deltas through app.Headless. This is where the per-platform scroll curves came from, and it is a tool rather than an API. |
Everything under internal/ is off limits, and Go enforces it. Two are worth naming because you will see them in stack traces and wonder:
internal/gfx/ is a vendored fork of a pure-Go WebGPU and 2D-rendering substrate — about 350,000 lines of it, under an MIT lineage (see THIRD_PARTY.md). It is not this project's API and should not be treated as one. Its comments cite ADR-NNN documents that live upstream and are not readable in this repository; that is expected.internal/scene records and diffs the draw-command stream to compute a damage rectangle. It implements paint.Canvas, which is why a custom painter's measurements come back identical whether the frame is being recorded or drawn.The public surface is measured: internal/apisurface generates a manifest of every exported name and the build fails when the tree drifts from it. Worth knowing what that does and does not buy you — it records names and kinds but not signatures, so a signature-only break can pass a green build.
The thing generated docs cannot show you: what actually happens, in order, and which package owns each step. Follow a tap on a button in a list.
1. shell a platform backend delivers a Pointer event to the Handler
2. widget hit testing walks the layout boxes; the deepest
Interactive whose rect contains the point wins
3. your code its OnTap closure runs — SetState(func(){ s.n++ })
4. widget SetState marks that element dirty and asks for a frame
↓ (nothing has been rebuilt or drawn yet)
5. app the next vsync tick starts a frame; tickers advance first,
so animations can dirty more elements
↓
6. widget build — dirty elements call Build again and the
reconciler diffs the returned values against the tree,
matching on concrete type, sibling position and key.
An unchanged subtree stops the walk; state survives a
match and is disposed on a miss.
↓
7. layout layout — Constraints flow down, sizes flow up, parents
position children. One pass. A node whose constraints
did not move is a relayout boundary and is skipped.
↓
8. paint paint — boxes record draw commands into a Canvas.
internal/scene diffs that command stream against the
previous frame's and produces a damage rectangle — from
real glyph ink, not advance boxes.
↓
9. internal/gfx composite — the layer tree becomes GPU commands
(Metal, Vulkan, OpenGL ES, or the CPU rasterizer)
↓
10. shell present: the damaged region is handed to the platform
in physical pixels
Two consequences are worth drawing out, because they explain most of what feels unusual here.
Build is not a draw call. It returns a fresh description and nothing more. That is what makes rebuilding a whole subtree on every tap cheap, and it is why a widget tree can be constructed, compared and asserted on in a test with no window anywhere near it. If you catch yourself wanting to know where a widget is during Build, you want LayoutBuilder or LayoutObserver, because the answer does not exist yet.
Only what changed is redone, and that is the architecture rather than an optimisation on top of it. Each of the three phases has its own boundary: a rebuild stops where the description is unchanged, a relayout stops at a node whose constraints did not move, a repaint stops at the layer whose damage rectangle covers it. Typing a character in a field does not re-lay-out the page around it.
If you remember one thing from this page, make it this one. It is invisible at 1× and wrong at 2×, and it has shipped in this repository before.
geom, everything in the widget tree, Config.Size, every coordinate in a gesture callback, every rect a widget.Canvas draws into.Mixing them is correct at 1× and wrong at 2×, where a logical rectangle names the top-left quadrant of a physical surface. The symptom when this went wrong on the web presenter was that half the height of every frame was uploaded and the rest kept showing the previous frame — which does not look like a units bug to anyone debugging it.
For application code the rule is simple, because the boundary is not yours: stay in logical pixels and never multiply by a scale factor. If a number you are holding came from geom or from a callback, it is logical, and the framework converts once at the presenter. You only need the distinction when reading an image back out, or when writing a shell.
| You want | It is in | Why |
|---|---|---|
| A button, a card, a text field, a dialog | theme, not widget | widget is unstyled primitives on purpose. theme.Button is built from Fill + Padding + Interactive + Text, and so is yours when the catalog does not have it. |
Implicit animation (AnimateColor, AnimatedScale) | widget, not anim | They are widgets — wrappers that hold a controller for you. anim is the curves and the controller itself. |
Capability interfaces (Battery, Share, FilePicker) | shell, reached through widget.Ctx | They are the platform contract, so they are declared where the platform is. You call ctx.Battery() and never import a backend. |
The concrete layout boxes (Flex, Padded, Aligned) | internal/layoutbox | They used to sit in layout beside the protocol that describes them, shadowing the widget layer name for name — layout.Aligned against widget.Align. Moving them out is why layout is now readable. |
If the catalog feels large: it is, and you do not need most of it. These ten cover the great majority of real code.
| Name | What it does |
|---|---|
app.Run(root, cfg) | Starts the app. cfg.Font is required or no text draws at all. |
widget.StateBase[W] | Embed it in your state struct for W() (the current config, typed) and SetState. It does not give you a Context() — the build context arrives as Build's argument. |
widget.Column / Row | Flex stacks. They return a Flex value, so you can set CrossAlign on the result. |
widget.Expand(child) | Give a child the leftover space. The fix for a scrollable that will not scroll. |
widget.Padding / Sized / Center | Insets, fixed extents, and positioning. Sized{H: 8} with no child is the idiomatic gap. |
widget.Text | Text. Wrap: true for anything that is a sentence; Font names a family registered in Config.FontFamilies. |
widget.Interactive | Makes a child respond to input. Adds no visuals and takes its child's size, so it goes on anything. |
widget.LazyList | A long list that only builds what is visible. Reach for it past a few dozen rows. |
widget.WithKey | Stable identity across a reorder or an insert. Without it, state follows the index instead of the item. |
theme.Of(ctx) | The theme in scope. Every colour and text size in your tree should come from here. |
And the one habit that matters more than any of them: check rather than guess. The catalog is intentionally small and composed from primitives, so a plausible-sounding widget frequently does not exist. go doc ./widget lists all of it in one screen.
The cookbook → · Architecture, in more depth · Coming from Flutter or Compose