05 - Styling and themes¶
style.Style is an immutable declaration of box geometry and appearance. Start
with its zero value and chain the properties you need:
primary := style.New().Background(style.Solid(activeTheme.Colors.Tones[theme.TonePrimary].Fill)).
TextColor(style.Solid(activeTheme.Colors.Tones[theme.TonePrimary].OnFill)).
Radius(8).
Padding(6, 12).
Cursor(style.CursorPointer)
Apply it to a widget:
There is no separate builder step. Each method returns a new style value.
Cascade order¶
The effective style is resolved in this order:
component defaults
-> inherited text
-> variant
-> size
-> resolve.Scope ancestors
-> instance Style
-> transitions
Each layer applies its own matching state rules before merging; later layers
override earlier layers. Within a layer, rules apply in ascending Rank, required
state-bit count, then declaration order. Use On with state masks for runtime
states (On requires all bits, OnAny requires any bit, and OnNot excludes bits):
primary := style.New().Background(style.Solid(activeTheme.Colors.Tones[theme.TonePrimary].Fill)).
TextColor(style.Solid(activeTheme.Colors.Tones[theme.TonePrimary].OnFill)).
On(style.Hovered, style.New().Background(style.Solid(activeTheme.Colors.Tones[theme.TonePrimary].Hover))).
On(style.Pressed, style.New().Background(style.Solid(activeTheme.Colors.Tones[theme.TonePrimary].Pressed))).
On(style.Disabled, style.New().Opacity(0.5))
resolve.Scope supplies defaults to a subtree without changing the global theme.
It lives in github.com/qianniancn/flowui/style/resolve, together with the other
context-bound resolvers — style itself is a pure declaration mechanism, so
anything that needs the active context or theme values sits beside it.
Part targets a named part inside a compound control, such as
PartContent, PartTrack, or PartPanel.
Button variants¶
Button semantic colors, hover/pressed/disabled feedback, and focus rings are
handled by the built-in variants. Choose the action hierarchy with Variant;
geometry remains owned by the button component:
button.New("Save").Variant(button.Primary)
button.New("Cancel").Variant(button.Secondary)
button.New("Delete").Variant(button.Danger)
button.New("More").Variant(button.Ghost)
Add a local declaration on top of the selected variant when needed:
Tokens and colors¶
Prefer semantic theme tokens so the same style works in light and dark themes:
style.New().Background(style.Solid(activeTheme.Colors.Surface))
style.New().TextColor(style.Solid(activeTheme.Colors.Foreground))
style.New().Background(style.Solid(activeTheme.Colors.Tones[theme.TonePrimary].Fill))
Use literal colors when a design requires them:
style.RGB(0x0078d4) // opaque 0xRRGGBB
style.RGBA(0x00000030) // 0xRRGGBBAA
style.WithAlpha(activeTheme.Colors.Ring, 0.5)
Gradients and custom drawing¶
When a custom widget needs a gradient, resolve it against the active theme and reuse the public drawing helpers:
brush, ok := resolve.Brush(style.LinearGradient(
style.ColorStop(0, style.Solid(activeTheme.Colors.Tones[theme.TonePrimary].Fill)),
style.ColorStop(1, style.Solid(activeTheme.Colors.Tones[theme.ToneDanger].Fill)),
))
if ok {
draw.Fill(gtx, image.Rect(0, 0, 160, 32), 6, brush)
}
Use resolve.Color for a single theme-resolved color and draw.FillRRect when
you already have a clip.RRect. This keeps theme resolution in one place and
does not turn custom drawing into an overlay system.
Themes¶
Import github.com/qianniancn/flowui/theme for the default theme.
Use theme.Light() / theme.Dark() directly, or customize its seed and metrics:
The default design uses Slate greys, Indigo accents, 1dp borders and 32dp
standard controls. See Default theme and external styles
for screenshots and geometry. Light / Dark supply the default Indigo accent;
New / NewDark use the supplied seed colours.
seed := theme.DefaultSeed()
seed.Primary = color.NRGBA{R: 0x00, G: 0x78, B: 0xd4, A: 0xff}
ui.Run(ui.NewProgram(Model{}, Update, View),
window.Theme(theme.NewDark(seed)),
)
Adjust component-specific geometry and appearance through the widget's instance style. The global theme contains shared semantic colors, typography, shadows, and motion.
For an Application, use SetTheme and SetLanguage so the replacement is
applied on the target window's event loop.
Fonts¶
The default theme uses sans-serif and monospace and permits Gio's system
font fallback. This is convenient for multilingual text, but the system font
index can add startup and memory cost. For deterministic rendering, parse a
bundled TTF, OTF, or TTC and disable system fonts:
// import "gioui.org/font/opentype"
//go:embed fonts/Inter-Regular.ttf
var interRegular []byte
faces, err := opentype.ParseCollection(interRegular)
if err != nil {
panic(err)
}
t := theme.Light()
t.Typography.Typeface = "Inter"
t.Fonts.Collection = faces
t.Fonts.SystemFonts = false
Prepare the collection before opening windows. Shared face data can be reused, while each window receives its own text shaper. If system fallback is disabled, include every language and symbol the application needs. Supply the font file referenced by the snippet in your own application.
Geometry and transitions¶
style.New().Width(200).Height(40)
style.New().MinWidth(100).MaxWidth(400)
style.New().Padding(12).Margin(8).Radius(8)
style.New().BorderWidth(1).BorderColor(style.Solid(activeTheme.Colors.Border))
style.New().BorderBottom(1, style.Solid(activeTheme.Colors.Border))
style.New().BoxShadow(0, 6, 18, 0, style.RGBA(0x00000030))
style.New().Opacity(0.9)
style.New().Overflow(style.OverflowHidden)
Use Transition for state-driven properties and give the interactive widget a
stable key:
style.New().Background(style.Solid(activeTheme.Colors.Surface)).
Transition(style.PropBackgroundColor, 120*time.Millisecond).
On(style.Hovered, style.New().Background(style.Solid(activeTheme.Colors.SurfaceRaised)))
Continue with Animation.
Instance styles vs. theme appearance¶
Each widget owns its default appearance, using light or dark semantic tokens
from theme. .Style(...) changes one instance; shared semantic colors affect
widgets that consume them.