theme

package
v0.0.0-...-64e189b Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 3, 2026 License: MIT Imports: 7 Imported by: 0

Documentation

Overview

Package theme is a Go translation of InkUI's theming model (https://inkui-lib.vercel.app/docs/getting-started/theming): a small struct of semantic colors plus a border style, with Dark (the default) and Light built-in values.

InkUI's theme reaches components via React context (a ThemeProvider/ useTheme pair) so nothing needs it passed explicitly. Go has no equivalent ambient-value mechanism, and the alternative — a mutable package-level "current theme" — is a footgun the moment more than one Program runs in a process, and would make widget tests depend on global state instead of their arguments. So themed functions in this module (widgets.Badge, widgets.StatusIndicator, ...) take a Theme argument explicitly, the same way they already take an ansi.Style or similar.

Index

Examples

Constants

View Source
const (
	ComponentAccordion          = "accordion"
	ComponentAppShell           = "appshell"
	ComponentAutocomplete       = "autocomplete"
	ComponentColorPicker        = "colorpicker"
	ComponentCommandPalette     = "commandpalette"
	ComponentConfirm            = "confirm"
	ComponentContextMenu        = "contextmenu"
	ComponentDataTable          = "datatable"
	ComponentDatePicker         = "datepicker"
	ComponentDialog             = "dialog"
	ComponentDrawer             = "drawer"
	ComponentErrorRetry         = "errorretry"
	ComponentFaces              = "faces"
	ComponentFilePicker         = "filepicker"
	ComponentForm               = "form"
	ComponentHelpScreen         = "helpscreen"
	ComponentImageView          = "imageview"
	ComponentLoadingBar         = "loadingbar"
	ComponentMarkdown           = "markdown"
	ComponentMaskedInput        = "maskedinput"
	ComponentMenu               = "menu"
	ComponentMenuBar            = "menubar"
	ComponentMultiSelect        = "multiselect"
	ComponentNotificationCenter = "notificationcenter"
	ComponentPasswordInput      = "passwordinput"
	ComponentPicker             = "picker"
	ComponentPopover            = "popover"
	ComponentScrollbar          = "scrollbar"
	ComponentSkeleton           = "skeleton"
	ComponentSpinner            = "spinner"
	ComponentSplitPane          = "splitpane"
	ComponentStreamText         = "streamtext"
	ComponentTabs               = "tabs"
	ComponentTagInput           = "taginput"
	ComponentTextArea           = "textarea"
	ComponentTextInput          = "textinput"
	ComponentToast              = "toast"
	ComponentToolApproval       = "toolapproval"
	ComponentTreeView           = "treeview"
	ComponentWizard             = "wizard"
)

Component names accepted by WithTokens, TokensFor, ForComponent and Resolve. Use these constants rather than string literals: a misspelt name overrides nothing, silently. Components lists them all.

Variables

This section is empty.

Functions

func Components

func Components() []string

Components returns every component name above, sorted.

func Contrast

func Contrast(fg, bg ansi.Color) float64

Contrast returns the WCAG 2.x contrast ratio (1 to 21) between fg and bg. Named ANSI and 256-colour values are measured against xterm's default palette. It returns 0 if either colour is nil or of an unknown type.

Types

type Auto

type Auto struct {
	Dark, Light Theme
}

Auto picks a theme from the terminal's background: Light when the background is light, Dark when it is dark or could not be detected. Give it to tui.WithTheme.

func DefaultAuto

func DefaultAuto() Auto

DefaultAuto returns Auto{Dark: DarkTheme(), Light: LightTheme()}.

func Pair

func Pair(preset Theme) Auto

Pair returns the Auto for preset: preset on a dark background and its LightTwin on a light one. Give it to tui.WithTheme.

func (Auto) For

func (a Auto) For(bg ansi.RGB) Theme

For returns the theme for a terminal whose background is bg.

type Border

type Border = basetypes.Border

Border is the set of characters drawn around a Box. layout.Border is the same type, so a Theme's Border can be given to layout.Box.Border directly.

type ComponentTokens

type ComponentTokens struct {
	// contains filtered or unexported fields
}

ComponentTokens holds per-component Tokens. It is immutable once built, so Theme stays comparable and copies of a Theme can share it.

type ContrastIssue

type ContrastIssue struct {
	Role  string // field name, e.g. "Muted"
	Fg    ansi.Color
	Bg    ansi.Color
	Ratio float64
}

ContrastIssue is one colour role that falls short of the requested ratio.

func (ContrastIssue) String

func (i ContrastIssue) String() string

String formats the issue as "Role: contrast R.RR".

type Glyphs

type Glyphs struct {
	// BarFull and BarEmpty are the filled and unfilled cell of a progress bar.
	BarFull, BarEmpty string
	// Collapsed and Expanded mark a closed and an open tree node or section.
	Collapsed, Expanded string
	// Spinner holds the spinner frames, one rune each.
	Spinner string

	// Dot and DotEmpty are a filled and an empty status or page marker.
	Dot, DotEmpty string
	// Check and Cross mark a done or failed step; Warning and Info are the
	// other two alert icons.
	Check, Cross, Warning, Info string
	// Bullet and BulletNested mark list items, at the top level and nested.
	Bullet, BulletNested string
	// Arrow separates steps.
	Arrow string
	// RuleH and RuleV are a horizontal rule and a vertical gutter or quote bar.
	RuleH, RuleV string
	// Cursor is a thin cursor and CursorBlock a full block cursor.
	Cursor, CursorBlock string
	// Shades holds four runes from light to dark (heat maps); Sparks holds
	// eight runes from low to high (sparklines).
	Shades, Sparks string
	// Mask replaces each character of a password.
	Mask string
	// Ellipsis marks truncated text and takes exactly one column.
	Ellipsis string
	// Dash and Middot are the separators in "name - description" and
	// "a . b" text.
	Dash, Middot string
}

Glyphs are the non-letter symbols widgets draw: progress fill, tree and accordion markers, spinner frames, status dots, check and cross marks, bullets, rules, cursors, heat and spark ramps, the password mask and a few typographic marks. Two sets exist, UnicodeGlyphSet (the default) and ASCIIGlyphSet for terminals, fonts or locales without box and block characters. A Glyphs value is comparable, so Theme stays comparable.

Empty fields fall back to UnicodeGlyphSet, so the zero Glyphs, and every Theme that never sets one, renders exactly as before. Every ASCII glyph takes the same columns as its Unicode counterpart, except Arrow ("->" against "→"), so a widget's layout does not shift.

func ASCIIGlyphSet

func ASCIIGlyphSet() Glyphs

ASCIIGlyphSet returns the glyph set that uses only 7-bit ASCII, as a copy.

func DetectGlyphs

func DetectGlyphs() Glyphs

DetectGlyphs chooses ASCIIGlyphSet when the environment says the terminal cannot be trusted with Unicode, NerdGlyphSet when TUI_NERD_FONT=1, and UnicodeGlyphSet otherwise; see DetectGlyphsEnv. Typical use:

th := theme.DarkTheme()
th.Glyphs = theme.DetectGlyphs()

func DetectGlyphsEnv

func DetectGlyphsEnv(getenv func(string) string) Glyphs

DetectGlyphsEnv is DetectGlyphs with an injectable getenv. It returns ASCIIGlyphSet when TERM is "dumb", or when the locale (the first non-empty of LC_ALL, LC_CTYPE, LANG) is "C" or "POSIX" or names a charset other than UTF-8; those ASCII checks always win. Otherwise it returns NerdGlyphSet when TUI_NERD_FONT is exactly "1" (an explicit opt-in, valid with or without a locale), and UnicodeGlyphSet. With no locale set at all it cannot tell and falls through to the Nerd or Unicode choice.

func NerdGlyphSet

func NerdGlyphSet() Glyphs

NerdGlyphSet returns UnicodeGlyphSet with Nerd Font icons for the status and marker glyphs that have one, as a copy. Use it only when the terminal font is a Nerd Font (see DetectGlyphsEnv).

func UnicodeGlyphSet

func UnicodeGlyphSet() Glyphs

UnicodeGlyphSet returns the default glyph set. It returns a copy, so a caller cannot change what every widget draws.

func (Glyphs) ASCII

func (g Glyphs) ASCII() bool

ASCII reports whether every glyph in g, after empty fields are filled from UnicodeGlyphSet, is 7-bit ASCII. Widgets that draw with characters computed at run time (braille dot charts) use it to switch to a plain density ramp.

func (Glyphs) Resolved

func (g Glyphs) Resolved() Glyphs

Resolved returns g with every empty field taken from UnicodeGlyphSet.

func (Glyphs) SpinnerFrames

func (g Glyphs) SpinnerFrames() []string

SpinnerFrames returns the spinner frames, one per rune of g.Spinner.

type Palette

type Palette struct {
	// contains filtered or unexported fields
}

Palette is the terminal's ANSI 0-15 colours. Start from NewPalette (xterm's defaults) and feed it the terminal's OSC 4 answers with Observe. A Palette is not safe for concurrent use.

func NewPalette

func NewPalette() *Palette

NewPalette returns xterm's default palette, the assumption used when the terminal does not answer OSC 4.

func (*Palette) Check

func (p *Palette) Check(t Theme, min float64) []ContrastIssue

Check is Theme.Check with this palette.

func (*Palette) Contrast

func (p *Palette) Contrast(fg, bg ansi.Color) float64

Contrast is the package Contrast measured with this palette's colours for named ANSI and the first 16 of the 256-colour values. A nil Palette means xterm's defaults.

func (*Palette) Observe

func (p *Palette) Observe(msg any) bool

Observe records msg if it is a tui.PaletteColorEvent (the answer to ansi.QueryPalette) and reports whether it was one. Call it from Update.

func (*Palette) Reported

func (p *Palette) Reported(index uint8) bool

Reported reports whether the terminal answered for slot index.

func (*Palette) Set

func (p *Palette) Set(index, r, g, b uint8)

Set records the terminal's colour for slot index (0-15); other slots are ignored.

type SpacingScale

type SpacingScale struct {
	XS, S, M, L int
}

SpacingScale is a comparable set of spacing tokens, in cells. A zero field means "use the default" (see Theme.Spacing), so an unset scale leaves widget output unchanged.

type States

type States struct {
	// Focus styles the control that has keyboard focus.
	Focus ansi.Style
	// Hover styles the control under the pointer.
	Hover ansi.Style
	// Disabled styles a control that cannot be used.
	Disabled ansi.Style
	// Selected styles a chosen item.
	Selected ansi.Style
}

States are the styles for a control's interactive states. A zero Style in any field means "derive it from the colour roles", which Theme.ResolvedStates does, so a Theme built before States existed keeps working and reads the same.

type Theme

type Theme struct {
	Primary     ansi.Color
	Secondary   ansi.Color
	Success     ansi.Color
	Warning     ansi.Color
	Error       ansi.Color
	Info        ansi.Color
	Muted       ansi.Color
	Text        ansi.Color
	TextInverse ansi.Color
	BorderColor ansi.Color
	// Focus is the color used to highlight the focused control; consumed
	// by widgets.Checkbox and widgets.Toggle.
	Focus     ansi.Color
	Selection ansi.Color
	Border    Border
	// Background is the terminal background the theme is designed for; Check
	// measures text roles against it. Unset (nil) falls back to TextInverse.
	Background ansi.Color
	// Surface is the fill of panels and cards, one step above Background.
	Surface ansi.Color
	// Overlay is the fill of floating layers (menus, dialogs, toasts), one step
	// above Surface.
	Overlay ansi.Color
	// Components are per-widget colour overrides; set them with WithTokens and
	// read them with TokensFor or ForComponent.
	Components *ComponentTokens
	// Glyphs are the symbols widgets draw (progress fill, tree markers,
	// spinner frames). The zero value means UnicodeGlyphSet; see Glyphs and
	// Theme.ASCII.
	Glyphs Glyphs
	// Spacing is the spacing scale. Zero fields mean the defaults; read it
	// through Theme.ResolvedSpacing.
	Spacing SpacingScale
	// States are the styles for focus, hover, disabled and selected. Empty
	// styles fall back to the colour roles; read them through
	// Theme.ResolvedStates.
	States States
	// Typography are the heading and inline text styles. Empty styles fall
	// back to the colour roles; read them through Theme.ResolvedTypography.
	Typography Typography
}

Theme is the Go shape of InkUI's InkUITheme: 12 semantic colors plus a border style. BorderColor is the color token named "border" in InkUI's theme (the color to draw border lines in); Border is InkUI's separate 'single'/'double'/'rounded'/'bold'/'ascii' style token, represented here with the layout.Border values that already existed before theming did.

func CatppuccinTheme

func CatppuccinTheme() Theme

CatppuccinTheme returns a copy of the Catppuccin preset.

func DarkTheme

func DarkTheme() Theme

DarkTheme returns a copy of the Dark preset.

func Detect

func Detect(msg any, fallback Theme) (Theme, bool)

Detect is the receiving end of tui.WithBackgroundDetection: call it from Update with each Msg and it turns the terminal's answer into a theme. A tui.BackgroundColorEvent returns ForBackground of its colour and true. A tui.BackgroundUnknownMsg (no answer before the timeout) returns fallback and true, so the app can stop waiting. Any other Msg returns fallback and false.

case tui.BackgroundColorEvent, tui.BackgroundUnknownMsg:
	if th, ok := theme.Detect(msg, theme.DarkTheme()); ok {
		m.theme = th
	}
Example (SwitchTheme)

The recipe: start with a default theme, ask for the background with tui.WithBackgroundDetection, and let theme.Detect pick Light or Dark when the answer (or the timeout) arrives. Any later change is one assignment.

tui.NewProgram(app{theme: theme.DarkTheme()}, tui.WithBackgroundDetection(200*time.Millisecond))
package main

import (
	"fmt"

	"github.com/ows4444/tui"
	"github.com/ows4444/tui/theme"
)

type app struct{ theme theme.Theme }

func (a app) Init() tui.Cmd { return nil }

func (a app) View() string { return "" }

// Update switches theme when the terminal answers the background query that
// tui.WithBackgroundDetection sent at startup.
func (a app) Update(msg tui.Msg) (tui.Model, tui.Cmd) {
	if th, ok := theme.Detect(msg, theme.DarkTheme()); ok {
		a.theme = th
	}
	return a, nil
}

// The recipe: start with a default theme, ask for the background with
// tui.WithBackgroundDetection, and let theme.Detect pick Light or Dark when
// the answer (or the timeout) arrives. Any later change is one assignment.
//
//	tui.NewProgram(app{theme: theme.DarkTheme()}, tui.WithBackgroundDetection(200*time.Millisecond))
func main() {
	var m tui.Model = app{theme: theme.DarkTheme()}

	// A light terminal answers with a near-white background.
	m, _ = m.Update(tui.BackgroundColorEvent{R: 250, G: 250, B: 250})
	fmt.Println("light background -> Text is Light's:", m.(app).theme.Text == theme.LightTheme().Text)

	// A dark one answers dark.
	m, _ = m.Update(tui.BackgroundColorEvent{R: 10, G: 10, B: 10})
	fmt.Println("dark background  -> Text is Dark's:", m.(app).theme.Text == theme.DarkTheme().Text)

	// Silence keeps the fallback and tells the app to stop waiting.
	m, _ = m.Update(tui.BackgroundUnknownMsg{})
	fmt.Println("no answer        -> Text is Dark's:", m.(app).theme.Text == theme.DarkTheme().Text)

	// Unrelated messages change nothing.
	m, _ = m.Update(tui.Key{Type: tui.KeyEnter})
	fmt.Println("other message    -> unchanged:", m.(app).theme.Text == theme.DarkTheme().Text)
}
Output:
light background -> Text is Light's: true
dark background  -> Text is Dark's: true
no answer        -> Text is Dark's: true
other message    -> unchanged: true

func DraculaTheme

func DraculaTheme() Theme

DraculaTheme returns a copy of the Dracula preset.

func EverforestDarkTheme

func EverforestDarkTheme() Theme

EverforestDarkTheme returns a copy of the EverforestDark preset.

func ForBackground

func ForBackground(bg ansi.RGB) Theme

ForBackground returns Light for a light terminal background and Dark for a dark one, judged by the background's perceived luminance (Rec. 709). Feed it the color from an OSC 11 reply (tui.BackgroundColorEvent).

func GitHubDarkTheme

func GitHubDarkTheme() Theme

GitHubDarkTheme returns a copy of the GitHubDark preset.

func GruvboxTheme

func GruvboxTheme() Theme

GruvboxTheme returns a copy of the Gruvbox preset.

func HighContrastTheme

func HighContrastTheme() Theme

HighContrastTheme returns a copy of the HighContrast preset.

func LightTheme

func LightTheme() Theme

LightTheme returns a copy of the Light preset.

func MonokaiTheme

func MonokaiTheme() Theme

MonokaiTheme returns a copy of the Monokai preset.

func NightOwlTheme

func NightOwlTheme() Theme

NightOwlTheme returns a copy of the NightOwl preset.

func NordTheme

func NordTheme() Theme

NordTheme returns a copy of the Nord preset.

func OneDarkTheme

func OneDarkTheme() Theme

OneDarkTheme returns a copy of the OneDark preset.

func RosePineTheme

func RosePineTheme() Theme

RosePineTheme returns a copy of the RosePine preset.

func SolarizedDarkTheme

func SolarizedDarkTheme() Theme

SolarizedDarkTheme returns a copy of the SolarizedDark preset.

func SolarizedLightTheme

func SolarizedLightTheme() Theme

SolarizedLightTheme returns a copy of the SolarizedLight preset.

func TokyoNightTheme

func TokyoNightTheme() Theme

TokyoNightTheme returns a copy of the TokyoNight preset.

func (Theme) ASCII

func (t Theme) ASCII() Theme

ASCII returns a copy of t for ASCII-only terminals: ASCIIGlyphSet and layout.ASCIIBorder. Colours are unchanged; pair with ForProfile for a colourless terminal.

func (Theme) Check

func (t Theme) Check(min float64) []ContrastIssue

Check reports every text and accent role (Text, Primary, Secondary, Success, Warning, Error, Info, Muted, Focus) whose contrast against the theme's Background is below min, e.g. 4.5 for WCAG AA. A theme without a Background falls back to TextInverse. Unset (nil) roles are skipped. Use it in an app's CI to vet a custom theme.

func (Theme) CheckOn

func (t Theme) CheckOn(bg ansi.Color, min float64) []ContrastIssue

CheckOn is Check against bg instead of the theme's Background, for vetting a theme on Surface, Overlay or a terminal background you probed.

func (Theme) ForComponent

func (t Theme) ForComponent(component string) Theme

ForComponent returns t with component's tokens applied to its colour roles. Give the result to that widget's SetTheme (or Theme field) and only that widget changes; t, and every other widget, keeps the shared theme.

func (Theme) ForProfile

func (t Theme) ForProfile(p ansi.Profile) Theme

ForProfile returns a copy of t with every color downgraded to what p can display (ansi.Downgrade), so a truecolor preset degrades gracefully on a 256- or 16-color terminal. Under ansi.NoColor all colors become nil (unset) while Border is kept. Pair with ansi.DetectColorProfile:

th := theme.DarkTheme().ForProfile(ansi.DetectColorProfile())

func (Theme) GlyphSet

func (t Theme) GlyphSet() Glyphs

GlyphSet returns the theme's glyphs with any empty field filled from UnicodeGlyphSet. Widgets call it; a theme that never set Glyphs gets the Unicode set.

func (Theme) LightTwin

func (t Theme) LightTwin() Theme

LightTwin returns the light-background counterpart of t. The twin keeps t's hues, Border and other settings, but swaps the roles of text and background: the background is t's Text tinted toward white, Text is t's background, and every accent is darkened until it reaches a 4.5:1 contrast against the new background, so the result passes Check(4.5). A theme that is already light is returned unchanged.

func (Theme) Plain

func (t Theme) Plain() Theme

Plain returns a copy of t with Border cleared to the zero value, so any widget that draws a border via t.Border (layout.Box.Border checks exactly this — the zero value means "no border") renders flat, undecorated text instead of box-drawing characters. Every other field — colors included — is unchanged. Pairs with (*tui.Program).ReducedMotion: an app honoring reduced motion typically wants to drop decorative framing as well as animation, not just one or the other, and Plain works with any existing Theme value (Dark, a preset, ...) rather than requiring a separate "plain" theme per palette.

func (Theme) Resolve

func (t Theme) Resolve(component string, inst Tokens) Theme

Resolve returns t with the overrides registered for component (WithTokens) applied, then inst, a per-instance override, on top. Only overridden roles change; with nothing to apply it returns t unchanged. Widgets call it to build the theme they render with.

func (Theme) ResolvedSpacing

func (t Theme) ResolvedSpacing() SpacingScale

ResolvedSpacing returns t's spacing with zero fields replaced by defaults.

func (Theme) ResolvedStates

func (t Theme) ResolvedStates() States

ResolvedStates returns t.States with each empty style replaced by one built from t's colour roles: Focus in t.Focus, Hover in t.Primary, Disabled in t.Muted, Selected on a t.Selection background in t.Text. A style the theme set explicitly is kept as it is.

func (Theme) ResolvedTypography

func (t Theme) ResolvedTypography() Typography

ResolvedTypography returns t.Typography with each empty style replaced by its default: H1 bold underlined Primary, H2 bold Primary, Emphasis italic, Strong bold, Code in Secondary, Link underlined Info. A style the theme set explicitly is kept as it is.

func (Theme) TokensFor

func (t Theme) TokensFor(component string) Tokens

TokensFor returns the tokens of component, every field resolved from the theme's roles and then overridden by anything set with WithTokens.

func (Theme) WithTokens

func (t Theme) WithTokens(component string, tok Tokens) Theme

WithTokens returns a copy of t whose component (a widget name such as "tabs" or "toast") carries tok. t is not modified and the copy shares no state with it. A second call for the same component replaces the first.

type Tokens

type Tokens struct {
	Text       ansi.Color // Theme.Text
	Muted      ansi.Color // Theme.Muted
	Accent     ansi.Color // Theme.Primary
	Focus      ansi.Color // Theme.Focus
	Selection  ansi.Color // Theme.Selection
	Border     ansi.Color // Theme.BorderColor
	Background ansi.Color // Theme.Background
	Surface    ansi.Color // Theme.Surface

	// Semantic roles. They follow the same nil-means-inherit rule.
	Success     ansi.Color // Theme.Success
	Warning     ansi.Color // Theme.Warning
	Error       ansi.Color // Theme.Error
	Info        ansi.Color // Theme.Info
	Overlay     ansi.Color // Theme.Overlay
	TextInverse ansi.Color // Theme.TextInverse
}

Tokens are one component's colour overrides. A nil field leaves the theme's role in place, so a Tokens value names only what differs.

func (Tokens) IsZero

func (tok Tokens) IsZero() bool

IsZero reports whether tok overrides nothing.

func (Tokens) Over

func (tok Tokens) Over(o Tokens) Tokens

Over returns tok with every non-nil field of o laid on top.

type Typography

type Typography struct {
	// H1 and H2 style top-level and second-level headings (markdown
	// headings, dialog titles).
	H1, H2 ansi.Style
	// Emphasis and Strong style italic and bold inline text. A style set
	// here replaces the surrounding text colour.
	Emphasis, Strong ansi.Style
	// Code styles inline code; Link styles links.
	Code, Link ansi.Style
}

Typography is the set of text styles a theme gives to headings and inline emphasis. A zero Style in any field means "derive it from the colour roles", which Theme.ResolvedTypography does, so a Theme built before Typography existed reads the same.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL