The API tour

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.

The packages, in four tiers

Grouped by when you first need them, not alphabetically. Most apps only ever import the first tier.

Tier 1 — you import these on day one

PackageWhat it is forThe names you will use
appThe 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
widgetThe 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
geomPoints, 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
paintThe 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
themeThe 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

Tier 2 — once the app is real

PackageWhat it is forWhen you reach for it
apptestDrives 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.
layoutThe 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.
animCurves 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.
shellThe 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.
chartA 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.
fetchOne 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.
intlLocale-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.

Tier 3 — specialised, and most apps never open them

PackageWhat it is for
textThe 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.
inputPer-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/uitraceThe 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.

Tier 4 — never. These are not public 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:

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.

How one tap becomes one frame

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.

The trap: logical pixels above, physical below

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.

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.

Four things that are not where you would guess

You wantIt is inWhy
A button, a card, a text field, a dialogtheme, not widgetwidget 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 animThey 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.CtxThey 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/layoutboxThey 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.

The ten names to learn first

If the catalog feels large: it is, and you do not need most of it. These ten cover the great majority of real code.

NameWhat 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 / RowFlex 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 / CenterInsets, fixed extents, and positioning. Sized{H: 8} with no child is the idiomatic gap.
widget.TextText. Wrap: true for anything that is a sentence; Font names a family registered in Config.FontFamilies.
widget.InteractiveMakes a child respond to input. Adds no visuals and takes its child's size, so it goes on anything.
widget.LazyListA long list that only builds what is visible. Reach for it past a few dozen rows.
widget.WithKeyStable 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