Skip to content
The default theme

The default theme

An app uses theme.DefaultTheme() unless you set another theme. It implements the Material Design 3 colour system and type scale. It also selects the “modern” widget style: gradient button backgrounds, filled card backgrounds and deeper shadows.

Light and dark

The theme has two variants, theme.VariantLight and theme.VariantDark. At startup the app picks the one that matches the operating system’s appearance setting.

While the app runs, it also follows changes to that setting on macOS, Windows, Linux (through the desktop’s colour-scheme setting) and the web (through the browser’s prefers-color-scheme). On Android and iOS the app picks up the system appearance when the mobile driver reports a configuration change.

To choose a variant yourself, call SetThemeVariant on the app’s settings:

variant := widget.NewSegmentedButton([]string{"Light", "Dark"}, func(i int) {
	if i == 0 {
		a.Settings().SetThemeVariant(theme.VariantLight)
	} else {
		a.Settings().SetThemeVariant(theme.VariantDark)
	}
})
if a.Settings().ThemeVariant() == theme.VariantDark {
	variant.Selected = 1
}

Settings().ThemeVariant() returns the variant in use. The call takes effect for the running app only. It is not saved.

To let users choose between System, Light and Dark and remember their choice, use the theme selector instead of building it yourself.

For a quick check during development, the BELLUI_THEME environment variable forces a variant. It accepts light or dark:

BELLUI_THEME=dark go run .

Primary colours

The default theme builds its whole colour scheme from one primary colour. From it, the theme generates six tonal palettes (primary, secondary, tertiary, error, neutral and neutral variant) and assigns tones from them to about 30 named colour roles, separately for light and dark. That is why a change of primary colour also changes surfaces, outlines and containers slightly, not just buttons.

The primary colour is chosen by name from eight standard ones: theme.PrimaryColorNames() returns red, orange, yellow, green, blue, purple, brown and gray. Blue is used when nothing is set.

The app’s settings implement bellui.PrimaryColorSettings, which can change it at runtime:

primary := widget.NewSelect(theme.PrimaryColorNames(), func(name string) {
	if s, ok := a.Settings().(bellui.PrimaryColorSettings); ok {
		s.SetPrimaryColor(name)
	}
})
current := a.Settings().PrimaryColor()
if current == "" {
	current = theme.ColorBlue // the default when nothing is set
}
primary.SetSelected(current)

Like the variant, this changes the running app only.

To use any other colour as the starting point, generate the scheme yourself from a seed colour. See Custom themes.

Colour names

Widgets look colours up by name, such as theme.ColorNamePrimary or theme.ColorNameBackground. There are two sets:

  • The MD3 roles, for example ColorNamePrimaryContainer, ColorNameSurfaceContainerHigh, ColorNameOnSurfaceVariant, ColorNameOutline and ColorNameScrim.
  • The general names that all Bellui themes have, for example ColorNameForeground, ColorNameButton, ColorNameInputBackground and ColorNameSeparator. The default theme maps each of them to an MD3 role, so ColorNameBackground is the MD3 surface colour.

ColorNameWarning is not part of MD3. The default theme draws it from a separate palette that does not change with the primary colour.

To read a colour in your own code, call theme.Color(name). It uses the current theme and variant.

The dense theme

theme.DenseTheme() is a compact theme for desktop apps that show a lot of data at once, such as dashboards or trading screens. It uses the same MD3 colours as the default theme, with smaller sizes:

SizeDefault themeDense theme
Body text1412
Padding62
Inner padding104
Inline icons2016
Button and input corner radius84

It also turns shadows off. Switch to it with SetTheme, and back with theme.DefaultTheme():

dense := widget.NewCheck("Dense theme", func(on bool) {
	if on {
		a.Settings().SetTheme(theme.DenseTheme())
	} else {
		a.Settings().SetTheme(theme.DefaultTheme())
	}
})

The full program:

// Switching the light/dark variant, the primary colour and the dense theme
// of a running app.
package main

import (
	"github.com/belluikit/bellui"
	"github.com/belluikit/bellui/app"
	"github.com/belluikit/bellui/container"
	"github.com/belluikit/bellui/theme"
	"github.com/belluikit/bellui/widget"
)

func main() {
	a := app.New()
	w := a.NewWindow("Theme variants")

	variant := widget.NewSegmentedButton([]string{"Light", "Dark"}, func(i int) {
		if i == 0 {
			a.Settings().SetThemeVariant(theme.VariantLight)
		} else {
			a.Settings().SetThemeVariant(theme.VariantDark)
		}
	})
	if a.Settings().ThemeVariant() == theme.VariantDark {
		variant.Selected = 1
	}

	primary := widget.NewSelect(theme.PrimaryColorNames(), func(name string) {
		if s, ok := a.Settings().(bellui.PrimaryColorSettings); ok {
			s.SetPrimaryColor(name)
		}
	})
	current := a.Settings().PrimaryColor()
	if current == "" {
		current = theme.ColorBlue // the default when nothing is set
	}
	primary.SetSelected(current)

	dense := widget.NewCheck("Dense theme", func(on bool) {
		if on {
			a.Settings().SetTheme(theme.DenseTheme())
		} else {
			a.Settings().SetTheme(theme.DefaultTheme())
		}
	})

	save := widget.NewButton("Save", func() {})
	save.Importance = widget.HighImportance

	w.SetContent(container.NewVBox(
		variant,
		primary,
		dense,
		widget.NewEntry(),
		container.NewHBox(widget.NewButton("Cancel", func() {}), save),
	))

	bellui.ShowAndRun(w)
}