Skip to content

Styles and themes

Customize control appearance and switch between light and dark themes with a button.

1. Run the example

go
package main

import (
	"github.com/dxui-org/dxui"
	"log"
)

func main() {
	dark := false
	app := dxui.NewApp(dxui.AppOptions{
		Background: dxui.RGBA(248, 250, 252, 255),
		Title:      "Theme",
		Width:      640,
		Height:     400,
	})
	if err := app.Run(func() dxui.View {
		return dxui.Box(
			dxui.BoxProps{
				Gap: 16,
				Style: dxui.Style{
					Padding: dxui.Padding(24),
				},
			},
			dxui.TextButton(
				dxui.ButtonProps{
					States: dxui.StateStyles{
						Hover: dxui.StylePatch{
							Opacity: dxui.Some(float32(.8)),
						},
					},
					OnPress: func() {
						next := dxui.DarkTheme()
						if dark {
							next = dxui.LightTheme()
						}
						next.Semantic.Colors[dxui.Color.Semantic.Accent] =
							dxui.TokenColor(dxui.Color.Primitive.Blue600)
						if err := app.SetTheme(next); err != nil {
							log.Print(err)
							return
						}
						dark = !dark
					},
				},
				"Switch theme",
			),
		)
	}); err != nil {
		log.Fatal(err)
	}
}

Click Switch theme to change themes. Hover over the button to make it slightly translucent; press Tab to see keyboard focus.

2. Choose the right setting

What to changeWhere
Control size, spacing, and backgroundThe control's Style.
Hover appearanceStates.Hover; the example changes opacity.
Application colorsCall app.SetTheme.

Start with Style for normal appearance. For example:

go
dxui.Style{
    Width: dxui.Px(240),
    Padding: dxui.Padding(16),
    Background: dxui.ColorRGBA(240, 245, 255, 255),
}

3. Switch themes

The example starts from LightTheme() or DarkTheme(), changes the accent color, then calls app.SetTheme(next). Named theme values are tokens that give controls consistent colors.

Try changing Blue600 to Red600. Call SetTheme after modifying the theme; editing a Go map alone does not update the UI. Theme changes propagate to all live windows.

Advanced: precedence, explicit clearing, and tokens

Style precedence

text
Primitive → Semantic → Component Base/Default
→ local Style → local States.Default
→ Hover → Focus → Checked → Pressed → Disabled
→ Style.Force

Within an active state, component theme patches apply before local patches. Disabled suppresses Hover/Focus/Pressed; a controlled Checked layer remains below it. State patches only change painting, not dimensions on hover.

Explicit zero values

A Go zero value often means unset, not explicitly cleared.

GoalSetting
Clear default paddingPadding: dxui.Padding(0)
Explicit zero opacityOpacity: dxui.Some(float32(0))
Prevent shrinkingShrink: dxui.NoShrink()
Clear a normal borderBorder: dxui.NoBorder()
Clear theme shadowsShadow: []dxui.Shadow{}; nil does not clear
Also clear state shadowsForce: dxui.StylePatch{Shadow: dxui.Some([]dxui.Shadow{})}

Removing focus rings makes keyboard position harder to see. Usually retain default Focus styling and change only normal appearance.

Literals, tokens, and colors

RGBA returns raw RGBAColor for AppOptions.Background and primitive color maps. ColorRGBA returns explicit ColorValue for Style.Background. Wrap existing RGBAColor with LiteralColor; TokenColor references the theme. Metric supplies an explicit logical number; TokenMetric reads a theme metric.

Color.Primitive.Blue600 is a palette token; Color.Semantic.Accent is a semantic role. Themes may map semantic colors to other palette colors. Legacy flat constants are in the token reference; old White/Black/Blue/Gray are not identical to the new palette entries.

Start from a complete LightTheme() or DarkTheme() before editing maps. A Theme containing only Accent does not auto-fill missing entries. SetTheme rejects missing references, cycles, and invalid values. Accepted maps/slices are copied; editing the original map later does not update the UI without another SetTheme call. Equal themes or unused-token changes need not produce a frame.

Next: Build a form. See theme APIs for full configuration.