about summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--Makefile7
-rw-r--r--commands/compose/attach.go5
-rw-r--r--commands/compose/detach.go5
-rw-r--r--commands/exec.go15
-rw-r--r--commands/msg/pipe.go14
-rw-r--r--config/aerc.conf.in11
-rw-r--r--config/config.go55
-rw-r--r--config/default_styleset33
-rw-r--r--config/style.go379
-rw-r--r--doc/aerc-config.5.scd14
-rw-r--r--doc/aerc-stylesets.7.scd189
-rw-r--r--lib/ui/borders.go13
-rw-r--r--lib/ui/stack.go10
-rw-r--r--lib/ui/tab.go11
-rw-r--r--lib/ui/text.go42
-rw-r--r--lib/ui/textinput.go32
-rw-r--r--widgets/account-wizard.go102
-rw-r--r--widgets/account.go5
-rw-r--r--widgets/aerc.go20
-rw-r--r--widgets/compose.go68
-rw-r--r--widgets/dirlist.go10
-rw-r--r--widgets/exline.go6
-rw-r--r--widgets/getpasswd.go19
-rw-r--r--widgets/msglist.go50
-rw-r--r--widgets/msgviewer.go63
-rw-r--r--widgets/pgpinfo.go34
-rw-r--r--widgets/selecter.go (renamed from widgets/selector.go)46
-rw-r--r--widgets/spinner.go6
-rw-r--r--widgets/status.go49
-rw-r--r--widgets/tabhost.go3
30 files changed, 284 insertions, 1032 deletions
diff --git a/Makefile b/Makefile
index 1c1de75..1e7fbd6 100644
--- a/Makefile
+++ b/Makefile
@@ -35,8 +35,7 @@ DOCS := \
 	aerc-notmuch.5 \
 	aerc-smtp.5 \
 	aerc-tutorial.7 \
-	aerc-templates.7 \
-	aerc-stylesets.7
+	aerc-templates.7
 
 .1.scd.1:
 	scdoc < $< > $@
@@ -59,7 +58,7 @@ clean:
 
 install: all
 	mkdir -m755 -p $(DESTDIR)$(BINDIR) $(DESTDIR)$(MANDIR)/man1 $(DESTDIR)$(MANDIR)/man5 $(DESTDIR)$(MANDIR)/man7 \
-		$(DESTDIR)$(SHAREDIR) $(DESTDIR)$(SHAREDIR)/filters $(DESTDIR)$(SHAREDIR)/templates $(DESTDIR)$(SHAREDIR)/stylesets
+		$(DESTDIR)$(SHAREDIR) $(DESTDIR)$(SHAREDIR)/filters $(DESTDIR)$(SHAREDIR)/templates
 	install -m755 aerc $(DESTDIR)$(BINDIR)/aerc
 	install -m644 aerc.1 $(DESTDIR)$(MANDIR)/man1/aerc.1
 	install -m644 aerc-search.1 $(DESTDIR)$(MANDIR)/man1/aerc-search.1
@@ -71,7 +70,6 @@ install: all
 	install -m644 aerc-smtp.5 $(DESTDIR)$(MANDIR)/man5/aerc-smtp.5
 	install -m644 aerc-tutorial.7 $(DESTDIR)$(MANDIR)/man7/aerc-tutorial.7
 	install -m644 aerc-templates.7 $(DESTDIR)$(MANDIR)/man7/aerc-templates.7
-	install -m644 aerc-stylesets.7 $(DESTDIR)$(MANDIR)/man7/aerc-stylesets.7
 	install -m644 config/accounts.conf $(DESTDIR)$(SHAREDIR)/accounts.conf
 	install -m644 aerc.conf $(DESTDIR)$(SHAREDIR)/aerc.conf
 	install -m644 config/binds.conf $(DESTDIR)$(SHAREDIR)/binds.conf
@@ -80,7 +78,6 @@ install: all
 	install -m755 filters/plaintext $(DESTDIR)$(SHAREDIR)/filters/plaintext
 	install -m644 templates/quoted_reply $(DESTDIR)$(SHAREDIR)/templates/quoted_reply
 	install -m644 templates/forward_as_body $(DESTDIR)$(SHAREDIR)/templates/forward_as_body
-	install -m644 config/default_styleset $(DESTDIR)$(SHAREDIR)/stylesets/default
 
 RMDIR_IF_EMPTY:=sh -c '\
 if test -d $$0 && ! ls -1qA $$0 | grep -q . ; then \
diff --git a/commands/compose/attach.go b/commands/compose/attach.go
index 148442b..2b633dc 100644
--- a/commands/compose/attach.go
+++ b/commands/compose/attach.go
@@ -4,9 +4,11 @@ import (
 	"fmt"
 	"os"
 	"strings"
+	"time"
 
 	"git.sr.ht/~sircmpwn/aerc/commands"
 	"git.sr.ht/~sircmpwn/aerc/widgets"
+	"github.com/gdamore/tcell"
 	"github.com/mitchellh/go-homedir"
 )
 
@@ -50,7 +52,8 @@ func (Attach) Execute(aerc *widgets.Aerc, args []string) error {
 	composer, _ := aerc.SelectedTab().(*widgets.Composer)
 	composer.AddAttachment(path)
 
-	aerc.PushSuccess(fmt.Sprintf("Attached %s", pathinfo.Name()))
+	aerc.PushStatus(fmt.Sprintf("Attached %s", pathinfo.Name()), 10*time.Second).
+		Color(tcell.ColorDefault, tcell.ColorGreen)
 
 	return nil
 }
diff --git a/commands/compose/detach.go b/commands/compose/detach.go
index b48159d..e8b07ed 100644
--- a/commands/compose/detach.go
+++ b/commands/compose/detach.go
@@ -3,8 +3,10 @@ package compose
 import (
 	"fmt"
 	"strings"
+	"time"
 
 	"git.sr.ht/~sircmpwn/aerc/widgets"
+	"github.com/gdamore/tcell"
 )
 
 type Detach struct{}
@@ -42,7 +44,8 @@ func (Detach) Execute(aerc *widgets.Aerc, args []string) error {
 		return err
 	}
 
-	aerc.PushSuccess(fmt.Sprintf("Detached %s", path))
+	aerc.PushStatus(fmt.Sprintf("Detached %s", path), 10*time.Second).
+		Color(tcell.ColorDefault, tcell.ColorGreen)
 
 	return nil
 }
diff --git a/commands/exec.go b/commands/exec.go
index 7d24fdc..e15afbe 100644
--- a/commands/exec.go
+++ b/commands/exec.go
@@ -7,6 +7,8 @@ import (
 	"time"
 
 	"git.sr.ht/~sircmpwn/aerc/widgets"
+
+	"github.com/gdamore/tcell"
 )
 
 type ExecCmd struct{}
@@ -33,15 +35,14 @@ func (ExecCmd) Execute(aerc *widgets.Aerc, args []string) error {
 		if err != nil {
 			aerc.PushError(" " + err.Error())
 		} else {
+			color := tcell.ColorDefault
 			if cmd.ProcessState.ExitCode() != 0 {
-				aerc.PushError(fmt.Sprintf(
-					"%s: completed with status %d", args[0],
-					cmd.ProcessState.ExitCode()))
-			} else {
-				aerc.PushStatus(fmt.Sprintf(
-					"%s: completed with status %d", args[0],
-					cmd.ProcessState.ExitCode()), 10*time.Second)
+				color = tcell.ColorRed
 			}
+			aerc.PushStatus(fmt.Sprintf(
+				"%s: completed with status %d", args[0],
+				cmd.ProcessState.ExitCode()), 10*time.Second).
+				Color(tcell.ColorDefault, color)
 		}
 	}()
 	return nil
diff --git a/commands/msg/pipe.go b/commands/msg/pipe.go
index 4e4ba67..20cb8b4 100644
--- a/commands/msg/pipe.go
+++ b/commands/msg/pipe.go
@@ -12,6 +12,7 @@ import (
 	"git.sr.ht/~sircmpwn/aerc/worker/types"
 
 	"git.sr.ht/~sircmpwn/getopt"
+	"github.com/gdamore/tcell"
 )
 
 type Pipe struct{}
@@ -95,15 +96,14 @@ func (Pipe) Execute(aerc *widgets.Aerc, args []string) error {
 		if err != nil {
 			aerc.PushError(" " + err.Error())
 		} else {
+			color := tcell.ColorDefault
 			if ecmd.ProcessState.ExitCode() != 0 {
-				aerc.PushError(fmt.Sprintf(
-					"%s: completed with status %d", cmd[0],
-					ecmd.ProcessState.ExitCode()))
-			} else {
-				aerc.PushStatus(fmt.Sprintf(
-					"%s: completed with status %d", cmd[0],
-					ecmd.ProcessState.ExitCode()), 10*time.Second)
+				color = tcell.ColorRed
 			}
+			aerc.PushStatus(fmt.Sprintf(
+				"%s: completed with status %d", cmd[0],
+				ecmd.ProcessState.ExitCode()), 10*time.Second).
+				Color(tcell.ColorDefault, color)
 		}
 	}
 
diff --git a/config/aerc.conf.in b/config/aerc.conf.in
index b9381a8..3348efa 100644
--- a/config/aerc.conf.in
+++ b/config/aerc.conf.in
@@ -67,17 +67,6 @@ sort=
 # Default: true
 next-message-on-delete=true
 
-# The directories where the stylesets are stored. It takes a colon-separated
-# list of directories.
-#
-# default: @SHAREDIR@/stylesets/
-stylesets-dirs=@SHAREDIR@/stylesets/
-
-# Sets the styleset to use for the aerc ui elements.
-#
-# Default: default
-styleset-name=default
-
 [viewer]
 #
 # Specifies the pager to use when displaying emails. Note that some filters
diff --git a/config/config.go b/config/config.go
index 9e78c86..00a52ce 100644
--- a/config/config.go
+++ b/config/config.go
@@ -45,9 +45,6 @@ type UIConfig struct {
 	NextMessageOnDelete bool          `ini:"next-message-on-delete"`
 	CompletionDelay     time.Duration `ini:"completion-delay"`
 	CompletionPopovers  bool          `ini:"completion-popovers"`
-	StyleSetDirs        []string      `ini:"stylesets-dirs" delim:":"`
-	StyleSetName        string        `ini:"styleset-name"`
-	style               StyleSet
 }
 
 type ContextType int
@@ -414,19 +411,6 @@ func (config *AercConfig) LoadConfig(file *ini.File) error {
 			}
 		}
 	}
-
-	if err := config.Ui.loadStyleSet(
-		config.Ui.StyleSetDirs); err != nil {
-		return err
-	}
-
-	for idx, _ := range config.ContextualUis {
-		if err := config.ContextualUis[idx].UiConfig.loadStyleSet(
-			config.Ui.StyleSetDirs); err != nil {
-			return err
-		}
-	}
-
 	return nil
 }
 
@@ -487,8 +471,6 @@ func LoadConfigFromFile(root *string, sharedir string) (*AercConfig, error) {
 			NextMessageOnDelete: true,
 			CompletionDelay:     250 * time.Millisecond,
 			CompletionPopovers:  true,
-			StyleSetDirs:        []string{path.Join(sharedir, "stylesets")},
-			StyleSetName:        "default",
 		},
 
 		ContextualUis: []UIConfigContext{},
@@ -518,7 +500,6 @@ func LoadConfigFromFile(root *string, sharedir string) (*AercConfig, error) {
 			Forwards:     "forward_as_body",
 		},
 	}
-
 	// These bindings are not configurable
 	config.Bindings.AccountWizard.ExKey = KeyStroke{
 		Key: tcell.KeyCtrlE,
@@ -529,7 +510,6 @@ func LoadConfigFromFile(root *string, sharedir string) (*AercConfig, error) {
 	if err = config.LoadConfig(file); err != nil {
 		return nil, err
 	}
-
 	if ui, err := file.GetSection("general"); err == nil {
 		if err := ui.MapTo(&config.General); err != nil {
 			return nil, err
@@ -637,18 +617,8 @@ func parseLayout(layout string) [][]string {
 	return l
 }
 
-func (ui *UIConfig) loadStyleSet(styleSetDirs []string) error {
-	ui.style = NewStyleSet()
-	err := ui.style.LoadStyleSet(ui.StyleSetName, styleSetDirs)
-	if err != nil {
-		return fmt.Errorf("Unable to load default styleset: %s", err)
-	}
-
-	return nil
-}
-
-func (config AercConfig) mergeContextualUi(baseUi UIConfig,
-	contextType ContextType, s string) UIConfig {
+func (config *AercConfig) mergeContextualUi(baseUi *UIConfig,
+	contextType ContextType, s string) {
 	for _, contextualUi := range config.ContextualUis {
 		if contextualUi.ContextType != contextType {
 			continue
@@ -658,30 +628,17 @@ func (config AercConfig) mergeContextualUi(baseUi UIConfig,
 			continue
 		}
 
-		mergo.Merge(&baseUi, contextualUi.UiConfig, mergo.WithOverride)
-		if contextualUi.UiConfig.StyleSetName != "" {
-			baseUi.style = contextualUi.UiConfig.style
-		}
-		return baseUi
+		mergo.MergeWithOverwrite(baseUi, contextualUi.UiConfig)
+		return
 	}
-
-	return baseUi
 }
 
-func (config AercConfig) GetUiConfig(params map[ContextType]string) UIConfig {
+func (config *AercConfig) GetUiConfig(params map[ContextType]string) UIConfig {
 	baseUi := config.Ui
 
 	for k, v := range params {
-		baseUi = config.mergeContextualUi(baseUi, k, v)
+		config.mergeContextualUi(&baseUi, k, v)
 	}
 
 	return baseUi
 }
-
-func (uiConfig UIConfig) GetStyle(so StyleObject) tcell.Style {
-	return uiConfig.style.Get(so)
-}
-
-func (uiConfig UIConfig) GetStyleSelected(so StyleObject) tcell.Style {
-	return uiConfig.style.Selected(so)
-}
diff --git a/config/default_styleset b/config/default_styleset
deleted file mode 100644
index fa52f23..0000000
--- a/config/default_styleset
+++ /dev/null
@@ -1,33 +0,0 @@
-# 
-# aerc default styleset
-# 
-# This styleset uses the terminal defaults as its base.
-# More information on how to configure the styleset can be found in
-# the *aerc-styleset.7* manpage. Please read the manual before
-# modifying or creating a styleset.
-
-*.default=true
-*.selected.reverse=toggle
-
-title.reverse=true
-header.bold=true
-
-*error.bold=true
-error.fg=red
-warning.fg=yellow
-success.fg=green
-
-statusline*.default=true
-statusline_default.reverse=true
-statusline_error.fg=red
-statusline_error.reverse=true
-
-msglist_unread.bold=true
-
-completion_pill.reverse=true
-
-tab.reverse=true
-border.reverse = true
-
-selector_focused.reverse=true
-selector_chooser.bold=true
diff --git a/config/style.go b/config/style.go
deleted file mode 100644
index f159be3..0000000
--- a/config/style.go
+++ /dev/null
@@ -1,379 +0,0 @@
-package config
-
-import (
-	"errors"
-	"fmt"
-	"os"
-	"path"
-	"regexp"
-	"strings"
-
-	"github.com/gdamore/tcell"
-	"github.com/go-ini/ini"
-	"github.com/mitchellh/go-homedir"
-)
-
-type StyleObject int32
-
-const (
-	STYLE_DEFAULT StyleObject = iota
-	STYLE_ERROR
-	STYLE_WARNING
-	STYLE_SUCCESS
-
-	STYLE_TITLE
-	STYLE_HEADER
-
-	STYLE_STATUSLINE_DEFAULT
-	STYLE_STATUSLINE_ERROR
-	STYLE_STATUSLINE_SUCCESS
-
-	STYLE_MSGLIST_DEFAULT
-	STYLE_MSGLIST_UNREAD
-	STYLE_MSGLIST_READ
-	STYLE_MSGLIST_DELETED
-	STYLE_MSGLIST_MARKED
-	STYLE_MSGLIST_FLAGGED
-
-	STYLE_DIRLIST_DEFAULT
-
-	STYLE_COMPLETION_DEFAULT
-	STYLE_COMPLETION_GUTTER
-	STYLE_COMPLETION_PILL
-
-	STYLE_TAB
-	STYLE_STACK
-	STYLE_SPINNER
-	STYLE_BORDER
-
-	STYLE_SELECTOR_DEFAULT
-	STYLE_SELECTOR_FOCUSED
-	STYLE_SELECTOR_CHOOSER
-)
-
-var StyleNames = map[string]StyleObject{
-	"default": STYLE_DEFAULT,
-	"error":   STYLE_ERROR,
-	"warning": STYLE_WARNING,
-	"success": STYLE_SUCCESS,
-
-	"title":  STYLE_TITLE,
-	"header": STYLE_HEADER,
-
-	"statusline_default": STYLE_STATUSLINE_DEFAULT,
-	"statusline_error":   STYLE_STATUSLINE_ERROR,
-	"statusline_success": STYLE_STATUSLINE_SUCCESS,
-
-	"msglist_default": STYLE_MSGLIST_DEFAULT,
-	"msglist_unread":  STYLE_MSGLIST_UNREAD,
-	"msglist_read":    STYLE_MSGLIST_READ,
-	"msglist_deleted": STYLE_MSGLIST_DELETED,
-	"msglist_marked":  STYLE_MSGLIST_MARKED,
-	"msglist_flagged": STYLE_MSGLIST_FLAGGED,
-
-	"dirlist_default": STYLE_DIRLIST_DEFAULT,
-
-	"completion_default": STYLE_COMPLETION_DEFAULT,
-	"completion_gutter":  STYLE_COMPLETION_GUTTER,
-	"completion_pill":    STYLE_COMPLETION_PILL,
-
-	"tab":     STYLE_TAB,
-	"stack":   STYLE_STACK,
-	"spinner": STYLE_SPINNER,
-	"border":  STYLE_BORDER,
-
-	"selector_default": STYLE_SELECTOR_DEFAULT,
-	"selector_focused": STYLE_SELECTOR_FOCUSED,
-	"selector_chooser": STYLE_SELECTOR_CHOOSER,
-}
-
-type Style struct {
-	Fg        tcell.Color
-	Bg        tcell.Color
-	Bold      bool
-	Blink     bool
-	Underline bool
-	Reverse   bool
-}
-
-func (s Style) Get() tcell.Style {
-	return tcell.StyleDefault.
-		Foreground(s.Fg).
-		Background(s.Bg).
-		Bold(s.Bold).
-		Blink(s.Blink).
-		Underline(s.Blink).
-		Reverse(s.Reverse)
-}
-
-func (s *Style) Normal() {
-	s.Bold = false
-	s.Blink = false
-	s.Underline = false
-	s.Reverse = false
-}
-
-func (s *Style) Default() *Style {
-	s.Fg = tcell.ColorDefault
-	s.Bg = tcell.ColorDefault
-	return s
-}
-
-func (s *Style) Reset() *Style {
-	s.Default()
-	s.Normal()
-	return s
-}
-
-func boolSwitch(val string, cur_val bool) (bool, error) {
-	switch val {
-	case "true":
-		return true, nil
-	case "false":
-		return false, nil
-	case "toggle":
-		return !cur_val, nil
-	default:
-		return cur_val, errors.New(
-			"Bool Switch attribute must be true, false, or toggle")
-	}
-}
-
-func (s *Style) Set(attr, val string) error {
-	switch attr {
-	case "fg":
-		s.Fg = tcell.GetColor(val)
-	case "bg":
-		s.Bg = tcell.GetColor(val)
-	case "bold":
-		if state, err := boolSwitch(val, s.Bold); err != nil {
-			return err
-		} else {
-			s.Bold = state
-		}
-	case "blink":
-		if state, err := boolSwitch(val, s.Blink); err != nil {
-			return err
-		} else {
-			s.Blink = state
-		}
-	case "underline":
-		if state, err := boolSwitch(val, s.Underline); err != nil {
-			return err
-		} else {
-			s.Underline = state
-		}
-	case "reverse":
-		if state, err := boolSwitch(val, s.Reverse); err != nil {
-			return err
-		} else {
-			s.Reverse = state
-		}
-	case "default":
-		s.Default()
-	case "normal":
-		s.Normal()
-	default:
-		return errors.New("Unknown style attribute: " + attr)
-	}
-
-	return nil
-}
-
-type StyleSet struct {
-	objects  map[StyleObject]*Style
-	selected map[StyleObject]*Style
-}
-
-func NewStyleSet() StyleSet {
-	ss := StyleSet{
-		objects:  make(map[StyleObject]*Style),
-		selected: make(map[StyleObject]*Style),
-	}
-	for _, so := range StyleNames {
-		ss.objects[so] = new(Style)
-		ss.selected[so] = new(Style)
-	}
-
-	return ss
-}
-
-func (ss StyleSet) reset() {
-	for _, so := range StyleNames {
-		ss.objects[so].Reset()
-		ss.selected[so].Reset()
-	}
-}
-
-func (ss StyleSet) Get(so StyleObject) tcell.Style {
-	return ss.objects[so].Get()
-}
-
-func (ss StyleSet) Selected(so StyleObject) tcell.Style {
-	return ss.selected[so].Get()
-}
-
-func findStyleSet(stylesetName string, stylesetsDir []string) (string, error) {
-	for _, dir := range stylesetsDir {
-		stylesetPath, err := homedir.Expand(path.Join(dir, stylesetName))
-		if err != nil {
-			return "", err
-		}
-
-		if _, err := os.Stat(stylesetPath); os.IsNotExist(err) {
-			continue
-		}
-
-		return stylesetPath, nil
-	}
-
-	return "", fmt.Errorf(
-		"Can't find styleset %q in any of %v", stylesetName, stylesetsDir)
-}
-
-func (ss *StyleSet) ParseStyleSet(file *ini.File) error {
-	ss.reset()
-
-	defaultSection, err := file.GetSection(ini.DefaultSection)
-	if err != nil {
-		return err
-	}
-
-	selectedKeys := []string{}
-
-	for _, key := range defaultSection.KeyStrings() {
-		tokens := strings.Split(key, ".")
-		var styleName, attr string
-		switch len(tokens) {
-		case 2:
-			styleName, attr = tokens[0], tokens[1]
-		case 3:
-			if tokens[1] != "selected" {
-				return errors.New("Unknown modifier: " + tokens[1])
-			}
-			selectedKeys = append(selectedKeys, key)
-			continue
-		default:
-			return errors.New("Style parsing error: " + key)
-		}
-		val := defaultSection.KeysHash()[key]
-
-		if strings.ContainsAny(styleName, "*?") {
-			regex := fnmatchToRegex(styleName)
-			for sn, so := range StyleNames {
-				matched, err := regexp.MatchString(regex, sn)
-				if err != nil {
-					return err
-				}
-
-				if !matched {
-					continue
-				}
-
-				if err := ss.objects[so].Set(attr, val); err != nil {
-					return err
-				}
-				if err := ss.selected[so].Set(attr, val); err != nil {
-					return err
-				}
-			}
-		} else {
-			so, ok := StyleNames[styleName]
-			if !ok {
-				return errors.New("Unknown style object: " + styleName)
-			}
-			if err := ss.objects[so].Set(attr, val); err != nil {
-				return err
-			}
-			if err := ss.selected[so].Set(attr, val); err != nil {
-				return err
-			}
-		}
-	}
-
-	for _, key := range selectedKeys {
-		tokens := strings.Split(key, ".")
-		styleName, modifier, attr := tokens[0], tokens[1], tokens[2]
-		if modifier != "selected" {
-			return errors.New("Unknown modifier: " + modifier)
-		}
-
-		val := defaultSection.KeysHash()[key]
-
-		if strings.ContainsAny(styleName, "*?") {
-			regex := fnmatchToRegex(styleName)
-			for sn, so := range StyleNames {
-				matched, err := regexp.MatchString(regex, sn)
-				if err != nil {
-					return err
-				}
-
-				if !matched {
-					continue
-				}
-
-				if err := ss.selected[so].Set(attr, val); err != nil {
-					return err
-				}
-			}
-		} else {
-			so, ok := StyleNames[styleName]
-			if !ok {
-				return errors.New("Unknown style object: " + styleName)
-			}
-			if err := ss.selected[so].Set(attr, val); err != nil {
-				return err
-			}
-		}
-	}
-
-	for _, key := range defaultSection.KeyStrings() {
-		tokens := strings.Split(key, ".")
-		styleName, attr := tokens[0], tokens[1]
-		val := defaultSection.KeysHash()[key]
-
-		if styleName != "selected" {
-			continue
-		}
-
-		for _, so := range StyleNames {
-			if err := ss.selected[so].Set(attr, val); err != nil {
-				return err
-			}
-		}
-	}
-
-	return nil
-}
-
-func (ss *StyleSet) LoadStyleSet(stylesetName string, stylesetDirs []string) error {
-	filepath, err := findStyleSet(stylesetName, stylesetDirs)
-	if err != nil {
-		return err
-	}
-
-	file, err := ini.Load(filepath)
-	if err != nil {
-		return err
-	}
-
-	return ss.ParseStyleSet(file)
-}
-
-func fnmatchToRegex(pattern string) string {
-	n := len(pattern)
-	var regex strings.Builder
-
-	for i := 0; i < n; i++ {
-		switch pattern[i] {
-		case '*':
-			regex.WriteString(".*")
-		case '?':
-			regex.WriteByte('.')
-		default:
-			regex.WriteByte(pattern[i])
-		}
-	}
-
-	return regex.String()
-}
diff --git a/doc/aerc-config.5.scd b/doc/aerc-config.5.scd
index fcd70ec..af64ad6 100644
--- a/doc/aerc-config.5.scd
+++ b/doc/aerc-config.5.scd
@@ -173,20 +173,6 @@ These options are configured in the *[ui]* section of aerc.conf.
 
 	Default: 250ms
 
-*stylesets-dirs*
-	The directories where the stylesets are stored. The config takes a
-	colon-seperated list of dirs.
-
-	Default: "/usr/share/aerc/stylesets"
-
-*styleset-name*
-	The name of the styleset to be used to style the ui elements. The
-	stylesets are stored in the 'stylesets' directory in the config
-	directory.
-
-	Default: default
-
-
 ## Contextual UI Configuration
 
 The UI configuration can be specialized for accounts, specific mail
diff --git a/doc/aerc-stylesets.7.scd b/doc/aerc-stylesets.7.scd
deleted file mode 100644
index 829418e..0000000
--- a/doc/aerc-stylesets.7.scd
+++ /dev/null
@@ -1,189 +0,0 @@
-aerc-stylesets(7)
-
-# Name
-
-aerc-stylesets - styleset file specification for *aerc*(1)
-
-# SYNOPSIS
-
-aerc uses a simple configuration syntax to configure the styleset for
-its ui.
-
-# Styleset Configuration
-
-Aerc uses a simple configuration file to describe a styleset. The
-styleset is described as key, value pairs. In each line, the key
-represents the style object it signifies and the color/atrribute of
-that is modified.
-
-For example, in the line below, the foreground color of the
-style object "msglist_unread" is set to "cornflowerblue"
-```
-msglist_unread.fg=cornflowerblue
-```
-
-The configuration also allows wildcard matching of the style_objects
-to configure multiple style objects at a time.
-
-## Style
-The following options are available to be modified for each of the
-style objects.
-
-*fg*
-	The foreground color of the style object is set.
-
-	Syntax: `<style_object>.fg=<color>`
-
-*bg*
-	The background color of the style object is set.
-
-	Syntax: `<style_object>.bg=<color>`
-
-*bold*
-	The bold attribute of the style object is set/unset.
-
-	Syntax: `<style_object>.bold=<true|false|toggle>`
-
-*blink*
-	The blink attribute of the style object is set/unset.
-	_The terminal needs to support blinking text_
-
-	Syntax: `<style_object>.bold=<true|false|toggle>`
-
-*underline*
-	The underline attribute of the style object is set/unset.
-	_The terminal needs to support underline text_
-
-	Syntax: `<style_object>.underline=<true|false|toggle>`
-
-*reverse*
-	Reverses the color of the style object. Exchanges the foreground
-	and background colors.
-
-	Syntax: `<style_object>.reverse=<true|false|toggle>`
-	_If the value is false, it doesn't change anything_
-
-*normal*
-	All the attributes of the style object are unset.
-
-	Syntax: `<style_object>.normal=<true>`
-	_The value doesn't matter_
-
-*default*
-	Set the style object to the default style of the context. Usually
-	based on the terminal.
-
-	Syntax: `<style_object>.default=<true>`
-	_The value doesn't matter_
-
-## Style Objects
-The style objects represent the various ui elements or ui instances for
-styling.
-
-[[ *Style Object*
-:[ *Description*
-|  default
-:  The default style object used for normal ui elements while not
-   using specialized configuration.
-|  error
-:  The style used to show errors.
-|  warning 
-:  The style used when showing warnings.
-|  success
-:  The style used for success messages.
-|  title
-:  The style object used to style titles in ui elements.
-|  header
-:  The style object used to style headers in ui elements.
-|  statusline_default
-:  The default style applied to the statusline.
-|  statusline_error
-:  The style used for error messages in statusline.
-|  statusline_success
-:  The style used for success messages in statusline.
-|  msglist_default
-:  The default style for messages in a message list.
-|  msglist_unread
-:  Unread messages in a message list.
-|  msglist_read
-:  Read messages in a message list.
-|  msglist_deleted
-:  The messages marked as deleted.
-|  msglist_marked
-:  The messages with the marked flag.
-|  msglist_flagged
-:  The messages with the flagged flag.
-|  dirlist_default
-:  The default style for directories in the directory list.
-|  completion_default
-:  The default style for the completion engine.
-|  completion_gutter
-:  The completion gutter.
-|  completion_pill
-:  The completion pill.
-|  tab
-:  The style for the tab bar.
-|  stack
-:  The style for ui stack element.
-|  spinner
-:  The style for the loading spinner.
-|  border
-:  The style used to draw borders. *Only the background color is used*.
-|  selecter_default
-:  The default style for the selecter ui element.
-|  selecter_focused
-:  The focused item in a selecter ui element.
-|  selecter_chooser
-:  The item chooser in a selecter ui element.
-
-## fnmatch style wildcard matching
-The styleset configuration can be made simpler by using the fnmatch
-style wildcard matching for the style object.
-
-The special characters used in the fnmatch wildcards are:
-[[ *Pattern*
-:[ *Meaning*
-|  \*
-:  Matches everything
-|  \?
-:  Matches any single character
-
-For example, the following wildcards can be made using this syntax.
-[[ *Example*
-:[ Description
-|  \*.fg=blue
-:  Set the foreground color of all style objects to blue.
-|  \*list.bg=hotpink
-:  Set the background color of all style objects that end in list 
-   to hotpink.
-
-## Selected modifier
-Selected modifier can be applied to any style object. The style provided for
-the selected modifier are applied on top of the style object it corresponds to.
-
-If you would like to make sure message that are flagged as read in the msglist
-appear in yellow foreground and black background. You can specify that with
-this.
-
-\tmsglist_default.selected.fg=yellow
-\tmsglist_default.selected.bg=black
-
-If we specify the global style selected modifer using fnmatch as below:
-
-\t\*.selected.reverse=toggle
-
-This toggles the reverse switch for selected version of all the style objects.
-
-## Colors
-The color values are set using the values accepted by the tcell library.
-The values can be one of the following.
-
-	*default*
-		The color is set as per the system or terminal default.
-
-	*<Color name>*
-		Any w3c approved color name is used to set colors for the style.
-
-	*<Hex code>*
-		Hexcode for a color can be used. The format must be "\#XXXXXX"
-
diff --git a/lib/ui/borders.go b/lib/ui/borders.go
index 99d6880..7a75759 100644
--- a/lib/ui/borders.go
+++ b/lib/ui/borders.go
@@ -2,8 +2,6 @@ package ui
 
 import (
 	"github.com/gdamore/tcell"
-
-	"git.sr.ht/~sircmpwn/aerc/config"
 )
 
 const (
@@ -18,15 +16,12 @@ type Bordered struct {
 	borders      uint
 	content      Drawable
 	onInvalidate func(d Drawable)
-	uiConfig     config.UIConfig
 }
 
-func NewBordered(
-	content Drawable, borders uint, uiConfig config.UIConfig) *Bordered {
+func NewBordered(content Drawable, borders uint) *Bordered {
 	b := &Bordered{
-		borders:  borders,
-		content:  content,
-		uiConfig: uiConfig,
+		borders: borders,
+		content: content,
 	}
 	content.OnInvalidate(b.contentInvalidated)
 	return b
@@ -49,7 +44,7 @@ func (bordered *Bordered) Draw(ctx *Context) {
 	y := 0
 	width := ctx.Width()
 	height := ctx.Height()
-	style := bordered.uiConfig.GetStyle(config.STYLE_BORDER)
+	style := tcell.StyleDefault.Reverse(true)
 	if bordered.borders&BORDER_LEFT != 0 {
 		ctx.Fill(0, 0, 1, ctx.Height(), ' ', style)
 		x += 1
diff --git a/lib/ui/stack.go b/lib/ui/stack.go
index c9004a0..690a869 100644
--- a/lib/ui/stack.go
+++ b/lib/ui/stack.go
@@ -3,19 +3,16 @@ package ui
 import (
 	"fmt"
 
-	"git.sr.ht/~sircmpwn/aerc/config"
-
 	"github.com/gdamore/tcell"
 )
 
 type Stack struct {
 	children     []Drawable
 	onInvalidate []func(d Drawable)
-	uiConfig     config.UIConfig
 }
 
-func NewStack(uiConfig config.UIConfig) *Stack {
-	return &Stack{uiConfig: uiConfig}
+func NewStack() *Stack {
+	return &Stack{}
 }
 
 func (stack *Stack) Children() []Drawable {
@@ -36,8 +33,7 @@ func (stack *Stack) Draw(ctx *Context) {
 	if len(stack.children) > 0 {
 		stack.Peek().Draw(ctx)
 	} else {
-		ctx.Fill(0, 0, ctx.Width(), ctx.Height(), ' ',
-			stack.uiConfig.GetStyle(config.STYLE_STACK))
+		ctx.Fill(0, 0, ctx.Width(), ctx.Height(), ' ', tcell.StyleDefault)
 	}
 }
 
diff --git a/lib/ui/tab.go b/lib/ui/tab.go
index cd5f448..4b99e4b 100644
--- a/lib/ui/tab.go
+++ b/lib/ui/tab.go
@@ -283,9 +283,9 @@ func (tabs *Tabs) removeHistory(index int) {
 func (strip *TabStrip) Draw(ctx *Context) {
 	x := 0
 	for i, tab := range strip.Tabs {
-		style := strip.uiConfig.GetStyle(config.STYLE_TAB)
+		style := tcell.StyleDefault.Reverse(true)
 		if strip.Selected == i {
-			style = strip.uiConfig.GetStyleSelected(config.STYLE_TAB)
+			style = tcell.StyleDefault
 		}
 		tabWidth := 32
 		if ctx.Width()-x < tabWidth {
@@ -301,8 +301,8 @@ func (strip *TabStrip) Draw(ctx *Context) {
 			break
 		}
 	}
-	ctx.Fill(x, 0, ctx.Width()-x, 1, ' ',
-		strip.uiConfig.GetStyle(config.STYLE_TAB))
+	style := tcell.StyleDefault.Reverse(true)
+	ctx.Fill(x, 0, ctx.Width()-x, 1, ' ', style)
 }
 
 func (strip *TabStrip) Invalidate() {
@@ -386,8 +386,7 @@ func (content *TabContent) Draw(ctx *Context) {
 	if content.Selected >= len(content.Tabs) {
 		width := ctx.Width()
 		height := ctx.Height()
-		ctx.Fill(0, 0, width, height, ' ',
-			content.uiConfig.GetStyle(config.STYLE_TAB))
+		ctx.Fill(0, 0, width, height, ' ', tcell.StyleDefault)
 	}
 
 	tab := content.Tabs[content.Selected]
diff --git a/lib/ui/text.go b/lib/ui/text.go
index 455c2eb..2b82598 100644
--- a/lib/ui/text.go
+++ b/lib/ui/text.go
@@ -15,13 +15,17 @@ type Text struct {
 	Invalidatable
 	text     string
 	strategy uint
-	style    tcell.Style
+	fg       tcell.Color
+	bg       tcell.Color
+	bold     bool
+	reverse  bool
 }
 
-func NewText(text string, style tcell.Style) *Text {
+func NewText(text string) *Text {
 	return &Text{
-		text:  text,
-		style: style,
+		bg:   tcell.ColorDefault,
+		fg:   tcell.ColorDefault,
+		text: text,
 	}
 }
 
@@ -37,6 +41,25 @@ func (t *Text) Strategy(strategy uint) *Text {
 	return t
 }
 
+func (t *Text) Bold(bold bool) *Text {
+	t.bold = bold
+	t.Invalidate()
+	return t
+}
+
+func (t *Text) Color(fg tcell.Color, bg tcell.Color) *Text {
+	t.fg = fg
+	t.bg = bg
+	t.Invalidate()
+	return t
+}
+
+func (t *Text) Reverse(reverse bool) *Text {
+	t.reverse = reverse
+	t.Invalidate()
+	return t
+}
+
 func (t *Text) Draw(ctx *Context) {
 	size := runewidth.StringWidth(t.text)
 	x := 0
@@ -46,8 +69,15 @@ func (t *Text) Draw(ctx *Context) {
 	if t.strategy == TEXT_RIGHT {
 		x = ctx.Width() - size
 	}
-	ctx.Fill(0, 0, ctx.Width(), ctx.Height(), ' ', t.style)
-	ctx.Printf(x, 0, t.style, "%s", t.text)
+	style := tcell.StyleDefault.Background(t.bg).Foreground(t.fg)
+	if t.bold {
+		style = style.Bold(true)
+	}
+	if t.reverse {
+		style = style.Reverse(true)
+	}
+	ctx.Fill(0, 0, ctx.Width(), ctx.Height(), ' ', style)
+	ctx.Printf(x, 0, style, "%s", t.text)
 }
 
 func (t *Text) Invalidate() {
diff --git a/lib/ui/textinput.go b/lib/ui/textinput.go
index 2445065..f6b0c72 100644
--- a/lib/ui/textinput.go
+++ b/lib/ui/textinput.go
@@ -6,8 +6,6 @@ import (
 
 	"github.com/gdamore/tcell"
 	"github.com/mattn/go-runewidth"
-
-	"git.sr.ht/~sircmpwn/aerc/config"
 )
 
 // TODO: Attach history providers
@@ -29,18 +27,16 @@ type TextInput struct {
 	completeIndex     int
 	completeDelay     time.Duration
 	completeDebouncer *time.Timer
-	uiConfig          config.UIConfig
 }
 
 // Creates a new TextInput. TextInputs will render a "textbox" in the entire
 // context they're given, and process keypresses to build a string from user
 // input.
-func NewTextInput(text string, ui config.UIConfig) *TextInput {
+func NewTextInput(text string) *TextInput {
 	return &TextInput{
-		cells:    -1,
-		text:     []rune(text),
-		index:    len([]rune(text)),
-		uiConfig: ui,
+		cells: -1,
+		text:  []rune(text),
+		index: len([]rune(text)),
 	}
 }
 
@@ -91,18 +87,16 @@ func (ti *TextInput) Draw(ctx *Context) {
 		ti.ensureScroll()
 	}
 	ti.ctx = ctx // gross
-
-	defaultStyle := ti.uiConfig.GetStyle(config.STYLE_DEFAULT)
-	ctx.Fill(0, 0, ctx.Width(), ctx.Height(), ' ', defaultStyle)
+	ctx.Fill(0, 0, ctx.Width(), ctx.Height(), ' ', tcell.StyleDefault)
 
 	text := ti.text[scroll:]
 	sindex := ti.index - scroll
 	if ti.password {
-		x := ctx.Printf(0, 0, defaultStyle, "%s", ti.prompt)
+		x := ctx.Printf(0, 0, tcell.StyleDefault, "%s", ti.prompt)
 		cells := runewidth.StringWidth(string(text))
-		ctx.Fill(x, 0, cells, 1, '*', defaultStyle)
+		ctx.Fill(x, 0, cells, 1, '*', tcell.StyleDefault)
 	} else {
-		ctx.Printf(0, 0, defaultStyle, "%s%s", ti.prompt, string(text))
+		ctx.Printf(0, 0, tcell.StyleDefault, "%s%s", ti.prompt, string(text))
 	}
 	cells := runewidth.StringWidth(string(text[:sindex]) + ti.prompt)
 	if ti.focus {
@@ -132,7 +126,6 @@ func (ti *TextInput) drawPopover(ctx *Context) {
 			ti.Set(stem + ti.StringRight())
 			ti.Invalidate()
 		},
-		uiConfig: ti.uiConfig,
 	}
 	width := maxLen(ti.completions) + 3
 	height := len(ti.completions)
@@ -360,7 +353,6 @@ type completions struct {
 	onSelect   func(int)
 	onExec     func()
 	onStem     func(string)
-	uiConfig   config.UIConfig
 }
 
 func maxLen(ss []string) int {
@@ -375,10 +367,10 @@ func maxLen(ss []string) int {
 }
 
 func (c *completions) Draw(ctx *Context) {
-	bg := c.uiConfig.GetStyle(config.STYLE_COMPLETION_DEFAULT)
-	gutter := c.uiConfig.GetStyle(config.STYLE_COMPLETION_GUTTER)
-	pill := c.uiConfig.GetStyle(config.STYLE_COMPLETION_PILL)
-	sel := c.uiConfig.GetStyleSelected(config.STYLE_COMPLETION_DEFAULT)
+	bg := tcell.StyleDefault
+	sel := tcell.StyleDefault.Reverse(true)
+	gutter := tcell.StyleDefault
+	pill := tcell.StyleDefault.Reverse(true)
 
 	ctx.Fill(0, 0, ctx.Width(), ctx.Height(), ' ', bg)
 
diff --git a/widgets/account-wizard.go b/widgets/account-wizard.go
index ae45bb8..4e51926 100644
--- a/widgets/account-wizard.go
+++ b/widgets/account-wizard.go
@@ -75,21 +75,21 @@ type AccountWizard struct {
 
 func NewAccountWizard(conf *config.AercConfig, aerc *Aerc) *AccountWizard {
 	wizard := &AccountWizard{
-		accountName:  ui.NewTextInput("", conf.Ui).Prompt("> "),
+		accountName:  ui.NewTextInput("").Prompt("> "),
 		aerc:         aerc,
 		conf:         conf,
 		temporary:    false,
 		copySent:     true,
-		email:        ui.NewTextInput("", conf.Ui).Prompt("> "),
-		fullName:     ui.NewTextInput("", conf.Ui).Prompt("> "),
-		imapPassword: ui.NewTextInput("", conf.Ui).Prompt("] ").Password(true),
-		imapServer:   ui.NewTextInput("", conf.Ui).Prompt("> "),
-		imapStr:      ui.NewText("imaps://", conf.Ui.GetStyle(config.STYLE_DEFAULT)),
-		imapUsername: ui.NewTextInput("", conf.Ui).Prompt("> "),
-		smtpPassword: ui.NewTextInput("", conf.Ui).Prompt("] ").Password(true),
-		smtpServer:   ui.NewTextInput("", conf.Ui).Prompt("> "),
-		smtpStr:      ui.NewText("smtps://", conf.Ui.GetStyle(config.STYLE_DEFAULT)),
-		smtpUsername: ui.NewTextInput("", conf.Ui).Prompt("> "),
+		email:        ui.NewTextInput("").Prompt("> "),
+		fullName:     ui.NewTextInput("").Prompt("> "),
+		imapPassword: ui.NewTextInput("").Prompt("] ").Password(true),
+		imapServer:   ui.NewTextInput("").Prompt("> "),
+		imapStr:      ui.NewText("imaps://"),
+		imapUsername: ui.NewTextInput("").Prompt("> "),
+		smtpPassword: ui.NewTextInput("").Prompt("] ").Password(true),
+		smtpServer:   ui.NewTextInput("").Prompt("> "),
+		smtpStr:      ui.NewText("smtps://"),
+		smtpUsername: ui.NewTextInput("").Prompt("> "),
 	}
 
 	// Autofill some stuff for the user
@@ -150,36 +150,33 @@ func NewAccountWizard(conf *config.AercConfig, aerc *Aerc) *AccountWizard {
 		{ui.SIZE_WEIGHT, ui.Const(1)},
 	})
 	basics.AddChild(
-		ui.NewText("\nWelcome to aerc! Let's configure your account.\n\n"+
-			"This wizard supports basic IMAP & SMTP configuration.\n"+
-			"For other configurations, use <Ctrl+q> to exit and read the "+
-			"aerc-config(5) man page.\n"+
-			"Press <Tab> and <Shift+Tab> to cycle between each field in this form, "+
-			"or <Ctrl+j> and <Ctrl+k>.",
-			conf.Ui.GetStyle(config.STYLE_DEFAULT)))
+		ui.NewText("\nWelcome to aerc! Let's configure your account.\n\n" +
+			"This wizard supports basic IMAP & SMTP configuration.\n" +
+			"For other configurations, use <Ctrl+q> to exit and read the " +
+			"aerc-config(5) man page.\n" +
+			"Press <Tab> and <Shift+Tab> to cycle between each field in this form, or <Ctrl+j> and <Ctrl+k>."))
 	basics.AddChild(
-		ui.NewText("Name for this account? (e.g. 'Personal' or 'Work')",
-			conf.Ui.GetStyle(config.STYLE_HEADER))).
+		ui.NewText("Name for this account? (e.g. 'Personal' or 'Work')").
+			Bold(true)).
 		At(1, 0)
 	basics.AddChild(wizard.accountName).
 		At(2, 0)
 	basics.AddChild(ui.NewFill(' ')).
 		At(3, 0)
 	basics.AddChild(
-		ui.NewText("Full name for outgoing emails? (e.g. 'John Doe')",
-			conf.Ui.GetStyle(config.STYLE_HEADER))).
+		ui.NewText("Full name for outgoing emails? (e.g. 'John Doe')").
+			Bold(true)).
 		At(4, 0)
 	basics.AddChild(wizard.fullName).
 		At(5, 0)
 	basics.AddChild(ui.NewFill(' ')).
 		At(6, 0)
 	basics.AddChild(
-		ui.NewText("Your email address? (e.g. 'john@example.org')",
-			conf.Ui.GetStyle(config.STYLE_HEADER))).
+		ui.NewText("Your email address? (e.g. 'john@example.org')").Bold(true)).
 		At(7, 0)
 	basics.AddChild(wizard.email).
 		At(8, 0)
-	selecter := NewSelecter([]string{"Next"}, 0, conf.Ui).
+	selecter := NewSelecter([]string{"Next"}, 0).
 		OnChoose(func(option string) {
 			email := wizard.email.String()
 			if strings.ContainsRune(email, '@') {
@@ -230,19 +227,16 @@ func NewAccountWizard(conf *config.AercConfig, aerc *Aerc) *AccountWizard {
 	}).Columns([]ui.GridSpec{
 		{ui.SIZE_WEIGHT, ui.Const(1)},
 	})
-	incoming.AddChild(ui.NewText("\nConfigure incoming mail (IMAP)",
-		conf.Ui.GetStyle(config.STYLE_DEFAULT)))
+	incoming.AddChild(ui.NewText("\nConfigure incoming mail (IMAP)"))
 	incoming.AddChild(
-		ui.NewText("Username",
-			conf.Ui.GetStyle(config.STYLE_HEADER))).
+		ui.NewText("Username").Bold(true)).
 		At(1, 0)
 	incoming.AddChild(wizard.imapUsername).
 		At(2, 0)
 	incoming.AddChild(ui.NewFill(' ')).
 		At(3, 0)
 	incoming.AddChild(
-		ui.NewText("Password",
-			conf.Ui.GetStyle(config.STYLE_HEADER))).
+		ui.NewText("Password").Bold(true)).
 		At(4, 0)
 	incoming.AddChild(wizard.imapPassword).
 		At(5, 0)
@@ -250,22 +244,20 @@ func NewAccountWizard(conf *config.AercConfig, aerc *Aerc) *AccountWizard {
 		At(6, 0)
 	incoming.AddChild(
 		ui.NewText("Server address "+
-			"(e.g. 'mail.example.org' or 'mail.example.org:1313')",
-			conf.Ui.GetStyle(config.STYLE_HEADER))).
+			"(e.g. 'mail.example.org' or 'mail.example.org:1313')").Bold(true)).
 		At(7, 0)
 	incoming.AddChild(wizard.imapServer).
 		At(8, 0)
 	incoming.AddChild(ui.NewFill(' ')).
 		At(9, 0)
 	incoming.AddChild(
-		ui.NewText("Connection mode",
-			conf.Ui.GetStyle(config.STYLE_HEADER))).
+		ui.NewText("Connection mode").Bold(true)).
 		At(10, 0)
 	imapMode := NewSelecter([]string{
 		"IMAP over SSL/TLS",
 		"IMAP with STARTTLS",
 		"Insecure IMAP",
-	}, 0, conf.Ui).Chooser(true).OnSelect(func(option string) {
+	}, 0).Chooser(true).OnSelect(func(option string) {
 		switch option {
 		case "IMAP over SSL/TLS":
 			wizard.imapMode = IMAP_OVER_TLS
@@ -277,7 +269,7 @@ func NewAccountWizard(conf *config.AercConfig, aerc *Aerc) *AccountWizard {
 		wizard.imapUri()
 	})
 	incoming.AddChild(imapMode).At(11, 0)
-	selecter = NewSelecter([]string{"Previous", "Next"}, 1, conf.Ui).
+	selecter = NewSelecter([]string{"Previous", "Next"}, 1).
 		OnChoose(wizard.advance)
 	incoming.AddChild(ui.NewFill(' ')).At(12, 0)
 	incoming.AddChild(wizard.imapStr).At(13, 0)
@@ -312,19 +304,16 @@ func NewAccountWizard(conf *config.AercConfig, aerc *Aerc) *AccountWizard {
 	}).Columns([]ui.GridSpec{
 		{ui.SIZE_WEIGHT, ui.Const(1)},
 	})
-	outgoing.AddChild(ui.NewText("\nConfigure outgoing mail (SMTP)",
-		conf.Ui.GetStyle(config.STYLE_DEFAULT)))
+	outgoing.AddChild(ui.NewText("\nConfigure outgoing mail (SMTP)"))
 	outgoing.AddChild(
-		ui.NewText("Username",
-			conf.Ui.GetStyle(config.STYLE_HEADER))).
+		ui.NewText("Username").Bold(true)).
 		At(1, 0)
 	outgoing.AddChild(wizard.smtpUsername).
 		At(2, 0)
 	outgoing.AddChild(ui.NewFill(' ')).
 		At(3, 0)
 	outgoing.AddChild(
-		ui.NewText("Password",
-			conf.Ui.GetStyle(config.STYLE_HEADER))).
+		ui.NewText("Password").Bold(true)).
 		At(4, 0)
 	outgoing.AddChild(wizard.smtpPassword).
 		At(5, 0)
@@ -332,22 +321,20 @@ func NewAccountWizard(conf *config.AercConfig, aerc *Aerc) *AccountWizard {
 		At(6, 0)
 	outgoing.AddChild(
 		ui.NewText("Server address "+
-			"(e.g. 'mail.example.org' or 'mail.example.org:1313')",
-			conf.Ui.GetStyle(config.STYLE_HEADER))).
+			"(e.g. 'mail.example.org' or 'mail.example.org:1313')").Bold(true)).
 		At(7, 0)
 	outgoing.AddChild(wizard.smtpServer).
 		At(8, 0)
 	outgoing.AddChild(ui.NewFill(' ')).
 		At(9, 0)
 	outgoing.AddChild(
-		ui.NewText("Connection mode",
-			conf.Ui.GetStyle(config.STYLE_HEADER))).
+		ui.NewText("Connection mode").Bold(true)).
 		At(10, 0)
 	smtpMode := NewSelecter([]string{
 		"SMTP over SSL/TLS",
 		"SMTP with STARTTLS",
 		"Insecure SMTP",
-	}, 0, conf.Ui).Chooser(true).OnSelect(func(option string) {
+	}, 0).Chooser(true).OnSelect(func(option string) {
 		switch option {
 		case "SMTP over SSL/TLS":
 			wizard.smtpMode = SMTP_OVER_TLS
@@ -359,15 +346,15 @@ func NewAccountWizard(conf *config.AercConfig, aerc *Aerc) *AccountWizard {
 		wizard.smtpUri()
 	})
 	outgoing.AddChild(smtpMode).At(11, 0)
-	selecter = NewSelecter([]string{"Previous", "Next"}, 1, conf.Ui).
+	selecter = NewSelecter([]string{"Previous", "Next"}, 1).
 		OnChoose(wizard.advance)
 	outgoing.AddChild(ui.NewFill(' ')).At(12, 0)
 	outgoing.AddChild(wizard.smtpStr).At(13, 0)
 	outgoing.AddChild(ui.NewFill(' ')).At(14, 0)
 	outgoing.AddChild(
-		ui.NewText("Copy sent messages to 'Sent' folder?",
-			conf.Ui.GetStyle(config.STYLE_HEADER))).At(15, 0)
-	copySent := NewSelecter([]string{"Yes", "No"}, 0, conf.Ui).
+		ui.NewText("Copy sent messages to 'Sent' folder?").Bold(true)).
+		At(15, 0)
+	copySent := NewSelecter([]string{"Yes", "No"}, 0).
 		Chooser(true).OnChoose(func(option string) {
 		switch option {
 		case "Yes":
@@ -393,16 +380,15 @@ func NewAccountWizard(conf *config.AercConfig, aerc *Aerc) *AccountWizard {
 		{ui.SIZE_WEIGHT, ui.Const(1)},
 	})
 	complete.AddChild(ui.NewText(
-		"\nConfiguration complete!\n\n"+
-			"You can go back and double check your settings, or choose 'Finish' to\n"+
-			"save your settings to accounts.conf.\n\n"+
-			"To add another account in the future, run ':new-account'.",
-		conf.Ui.GetStyle(config.STYLE_DEFAULT)))
+		"\nConfiguration complete!\n\n" +
+			"You can go back and double check your settings, or choose 'Finish' to\n" +
+			"save your settings to accounts.conf.\n\n" +
+			"To add another account in the future, run ':new-account'."))
 	selecter = NewSelecter([]string{
 		"Previous",
 		"Finish & open tutorial",
 		"Finish",
-	}, 1, conf.Ui).OnChoose(func(option string) {
+	}, 1).OnChoose(func(option string) {
 		switch option {
 		case "Previous":
 			wizard.advance("Previous")
diff --git a/widgets/account.go b/widgets/account.go
index 53c65ba..211f09d 100644
--- a/widgets/account.go
+++ b/widgets/account.go
@@ -64,14 +64,15 @@ func NewAccountView(aerc *Aerc, conf *config.AercConfig, acct *config.AccountCon
 
 	worker, err := worker.NewWorker(acct.Source, logger)
 	if err != nil {
-		host.SetError(fmt.Sprintf("%s: %s", acct.Name, err))
+		host.SetStatus(fmt.Sprintf("%s: %s", acct.Name, err)).
+			Color(tcell.ColorDefault, tcell.ColorRed)
 		return view
 	}
 	view.worker = worker
 
 	view.dirlist = NewDirectoryList(conf, acct, logger, worker)
 	if acctUiConf.SidebarWidth > 0 {
-		view.grid.AddChild(ui.NewBordered(view.dirlist, ui.BORDER_RIGHT, acctUiConf))
+		view.grid.AddChild(ui.NewBordered(view.dirlist, ui.BORDER_RIGHT))
 	}
 
 	view.msglist = NewMessageList(conf, logger, aerc)
diff --git a/widgets/aerc.go b/widgets/aerc.go
index 692e00d..4913be3 100644
--- a/widgets/aerc.go
+++ b/widgets/aerc.go
@@ -51,8 +51,8 @@ func NewAerc(conf *config.AercConfig, logger *log.Logger,
 
 	tabs := ui.NewTabs(&conf.Ui)
 
-	statusbar := ui.NewStack(conf.Ui)
-	statusline := NewStatusLine(conf.Ui)
+	statusbar := ui.NewStack()
+	statusline := NewStatusLine()
 	statusbar.Push(statusline)
 
 	grid := ui.NewGrid().Rows([]ui.GridSpec{
@@ -76,7 +76,7 @@ func NewAerc(conf *config.AercConfig, logger *log.Logger,
 		logger:     logger,
 		statusbar:  statusbar,
 		statusline: statusline,
-		prompts:    ui.NewStack(conf.Ui),
+		prompts:    ui.NewStack(),
 		tabs:       tabs,
 	}
 
@@ -382,20 +382,12 @@ func (aerc *Aerc) SetStatus(status string) *StatusMessage {
 	return aerc.statusline.Set(status)
 }
 
-func (aerc *Aerc) SetError(status string) *StatusMessage {
-	return aerc.statusline.SetError(status)
-}
-
 func (aerc *Aerc) PushStatus(text string, expiry time.Duration) *StatusMessage {
 	return aerc.statusline.Push(text, expiry)
 }
 
-func (aerc *Aerc) PushError(text string) *StatusMessage {
-	return aerc.statusline.PushError(text)
-}
-
-func (aerc *Aerc) PushSuccess(text string) *StatusMessage {
-	return aerc.statusline.PushSuccess(text)
+func (aerc *Aerc) PushError(text string) {
+	aerc.PushStatus(text, 10*time.Second).Color(tcell.ColorDefault, tcell.ColorRed)
 }
 
 func (aerc *Aerc) focus(item ui.Interactive) {
@@ -563,7 +555,7 @@ func (aerc *Aerc) CloseDialog() {
 func (aerc *Aerc) GetPassword(title string, prompt string) (chText chan string, chErr chan error) {
 	chText = make(chan string, 1)
 	chErr = make(chan error, 1)
-	getPasswd := NewGetPasswd(title, prompt, aerc.conf, func(pw string, err error) {
+	getPasswd := NewGetPasswd(title, prompt, func(pw string, err error) {
 		defer func() {
 			close(chErr)
 			close(chText)
diff --git a/widgets/compose.go b/widgets/compose.go
index 03c9175..b68c406 100644
--- a/widgets/compose.go
+++ b/widgets/compose.go
@@ -72,11 +72,10 @@ func NewComposer(aerc *Aerc, acct *AccountView, conf *config.AercConfig,
 
 	templateData := templates.ParseTemplateData(defaults, original)
 	cmpl := completer.New(conf.Compose.AddressBookCmd, func(err error) {
-		aerc.PushError(
-			fmt.Sprintf("could not complete header: %v", err))
+		aerc.PushError(fmt.Sprintf("could not complete header: %v", err))
 		worker.Logger.Printf("could not complete header: %v", err)
 	}, aerc.Logger())
-	layout, editors, focusable := buildComposeHeader(aerc, cmpl, defaults)
+	layout, editors, focusable := buildComposeHeader(conf, cmpl, defaults)
 
 	email, err := ioutil.TempFile("", "aerc-compose-*.eml")
 	if err != nil {
@@ -113,21 +112,21 @@ func NewComposer(aerc *Aerc, acct *AccountView, conf *config.AercConfig,
 	return c, nil
 }
 
-func buildComposeHeader(aerc *Aerc, cmpl *completer.Completer,
+func buildComposeHeader(conf *config.AercConfig, cmpl *completer.Completer,
 	defaults map[string]string) (
 	newLayout HeaderLayout,
 	editors map[string]*headerEditor,
 	focusable []ui.MouseableDrawableInteractive,
 ) {
-	layout := aerc.conf.Compose.HeaderLayout
+	layout := conf.Compose.HeaderLayout
 	editors = make(map[string]*headerEditor)
 	focusable = make([]ui.MouseableDrawableInteractive, 0)
 
 	for _, row := range layout {
 		for _, h := range row {
-			e := newHeaderEditor(h, "", aerc.SelectedAccount().UiConfig())
-			if aerc.conf.Ui.CompletionPopovers {
-				e.input.TabComplete(cmpl.ForHeader(h), aerc.SelectedAccount().UiConfig().CompletionDelay)
+			e := newHeaderEditor(h, "")
+			if conf.Ui.CompletionPopovers {
+				e.input.TabComplete(cmpl.ForHeader(h), conf.Ui.CompletionDelay)
 			}
 			editors[h] = e
 			switch h {
@@ -144,9 +143,9 @@ func buildComposeHeader(aerc *Aerc, cmpl *completer.Completer,
 	for _, h := range []string{"Cc", "Bcc"} {
 		if val, ok := defaults[h]; ok && val != "" {
 			if _, ok := editors[h]; !ok {
-				e := newHeaderEditor(h, "", aerc.SelectedAccount().UiConfig())
-				if aerc.conf.Ui.CompletionPopovers {
-					e.input.TabComplete(cmpl.ForHeader(h), aerc.SelectedAccount().UiConfig().CompletionDelay)
+				e := newHeaderEditor(h, "")
+				if conf.Ui.CompletionPopovers {
+					e.input.TabComplete(cmpl.ForHeader(h), conf.Ui.CompletionDelay)
 				}
 				editors[h] = e
 				focusable = append(focusable, e)
@@ -260,8 +259,7 @@ func (c *Composer) readSignatureFromFile() []byte {
 	}
 	signature, err := ioutil.ReadFile(sigFile)
 	if err != nil {
-		c.aerc.PushError(
-			fmt.Sprintf(" Error loading signature from file: %v", sigFile))
+		c.aerc.PushError(fmt.Sprintf(" Error loading signature from file: %v", sigFile))
 		return nil
 	}
 	return signature
@@ -650,7 +648,7 @@ func (c *Composer) AddEditor(header string, value string, appendHeader bool) {
 		}
 		return
 	}
-	e := newHeaderEditor(header, value, c.aerc.SelectedAccount().UiConfig())
+	e := newHeaderEditor(header, value)
 	if c.config.Ui.CompletionPopovers {
 		e.input.TabComplete(c.completer.ForHeader(header), c.config.Ui.CompletionDelay)
 	}
@@ -706,27 +704,23 @@ func (c *Composer) reloadEmail() error {
 }
 
 type headerEditor struct {
-	name     string
-	focused  bool
-	input    *ui.TextInput
-	uiConfig config.UIConfig
+	name    string
+	focused bool
+	input   *ui.TextInput
 }
 
-func newHeaderEditor(name string, value string, uiConfig config.UIConfig) *headerEditor {
+func newHeaderEditor(name string, value string) *headerEditor {
 	return &headerEditor{
-		input:    ui.NewTextInput(value, uiConfig),
-		name:     name,
-		uiConfig: uiConfig,
+		input: ui.NewTextInput(value),
+		name:  name,
 	}
 }
 
 func (he *headerEditor) Draw(ctx *ui.Context) {
 	name := he.name + " "
 	size := runewidth.StringWidth(name)
-	defaultStyle := he.uiConfig.GetStyle(config.STYLE_DEFAULT)
-	headerStyle := he.uiConfig.GetStyle(config.STYLE_HEADER)
-	ctx.Fill(0, 0, size, ctx.Height(), ' ', defaultStyle)
-	ctx.Printf(0, 0, headerStyle, "%s", name)
+	ctx.Fill(0, 0, size, ctx.Height(), ' ', tcell.StyleDefault)
+	ctx.Printf(0, 0, tcell.StyleDefault.Bold(true), "%s", name)
 	he.input.Draw(ctx.Subcontext(size, 0, ctx.Width()-size, 1))
 }
 
@@ -790,25 +784,21 @@ func newReviewMessage(composer *Composer, err error) *reviewMessage {
 		{ui.SIZE_WEIGHT, ui.Const(1)},
 	})
 
-	uiConfig := composer.config.Ui
-
 	if err != nil {
-		grid.AddChild(ui.NewText(err.Error(), uiConfig.GetStyle(config.STYLE_ERROR)))
-		grid.AddChild(ui.NewText("Press [q] to close this tab.",
-			uiConfig.GetStyle(config.STYLE_DEFAULT))).At(1, 0)
+		grid.AddChild(ui.NewText(err.Error()).
+			Color(tcell.ColorRed, tcell.ColorDefault))
+		grid.AddChild(ui.NewText("Press [q] to close this tab.")).At(1, 0)
 	} else {
 		// TODO: source this from actual keybindings?
-		grid.AddChild(ui.NewText("Send this email? [y]es/[n]o/[e]dit/[a]ttach",
-			uiConfig.GetStyle(config.STYLE_DEFAULT))).At(0, 0)
-		grid.AddChild(ui.NewText("Attachments:",
-			uiConfig.GetStyle(config.STYLE_TITLE))).At(1, 0)
+		grid.AddChild(ui.NewText(
+			"Send this email? [y]es/[n]o/[p]ostpone/[e]dit/[a]ttach")).At(0, 0)
+		grid.AddChild(ui.NewText("Attachments:").
+			Reverse(true)).At(1, 0)
 		if len(composer.attachments) == 0 {
-			grid.AddChild(ui.NewText("(none)",
-				uiConfig.GetStyle(config.STYLE_DEFAULT))).At(2, 0)
+			grid.AddChild(ui.NewText("(none)")).At(2, 0)
 		} else {
 			for i, a := range composer.attachments {
-				grid.AddChild(ui.NewText(a, uiConfig.GetStyle(config.STYLE_DEFAULT))).
-					At(i+2, 0)
+				grid.AddChild(ui.NewText(a)).At(i+2, 0)
 			}
 		}
 	}
diff --git a/widgets/dirlist.go b/widgets/dirlist.go
index 3ed79cc..3711544 100644
--- a/widgets/dirlist.go
+++ b/widgets/dirlist.go
@@ -196,8 +196,7 @@ func (dirlist *DirectoryList) getRUEString(name string) string {
 }
 
 func (dirlist *DirectoryList) Draw(ctx *ui.Context) {
-	ctx.Fill(0, 0, ctx.Width(), ctx.Height(), ' ',
-		dirlist.UiConfig().GetStyle(config.STYLE_DIRLIST_DEFAULT))
+	ctx.Fill(0, 0, ctx.Width(), ctx.Height(), ' ', tcell.StyleDefault)
 
 	if dirlist.spinner.IsRunning() {
 		dirlist.spinner.Draw(ctx)
@@ -205,7 +204,7 @@ func (dirlist *DirectoryList) Draw(ctx *ui.Context) {
 	}
 
 	if len(dirlist.dirs) == 0 {
-		style := dirlist.UiConfig().GetStyle(config.STYLE_DIRLIST_DEFAULT)
+		style := tcell.StyleDefault
 		ctx.Printf(0, 0, style, dirlist.UiConfig().EmptyDirlist)
 		return
 	}
@@ -237,7 +236,10 @@ func (dirlist *DirectoryList) Draw(ctx *ui.Context) {
 
 		style := tcell.StyleDefault
 		if name == dirlist.selected {
-			style = dirlist.UiConfig().GetStyleSelected(config.STYLE_DIRLIST_DEFAULT)
+			style = style.Reverse(true)
+		} else if name == dirlist.selecting {
+			style = style.Reverse(true)
+			style = style.Foreground(tcell.ColorGray)
 		}
 		ctx.Fill(0, row, textWidth, 1, ' ', style)
 
diff --git a/widgets/exline.go b/widgets/exline.go
index 692c8e2..6def938 100644
--- a/widgets/exline.go
+++ b/widgets/exline.go
@@ -15,14 +15,13 @@ type ExLine struct {
 	tabcomplete func(cmd string) []string
 	cmdHistory  lib.History
 	input       *ui.TextInput
-	conf        *config.AercConfig
 }
 
 func NewExLine(conf *config.AercConfig, cmd string, commit func(cmd string), finish func(),
 	tabcomplete func(cmd string) []string,
 	cmdHistory lib.History) *ExLine {
 
-	input := ui.NewTextInput("", conf.Ui).Prompt(":").Set(cmd)
+	input := ui.NewTextInput("").Prompt(":").Set(cmd)
 	if conf.Ui.CompletionPopovers {
 		input.TabComplete(tabcomplete, conf.Ui.CompletionDelay)
 	}
@@ -32,7 +31,6 @@ func NewExLine(conf *config.AercConfig, cmd string, commit func(cmd string), fin
 		tabcomplete: tabcomplete,
 		cmdHistory:  cmdHistory,
 		input:       input,
-		conf:        conf,
 	}
 	input.OnInvalidate(func(d ui.Drawable) {
 		exline.Invalidate()
@@ -43,7 +41,7 @@ func NewExLine(conf *config.AercConfig, cmd string, commit func(cmd string), fin
 func NewPrompt(conf *config.AercConfig, prompt string, commit func(text string),
 	tabcomplete func(cmd string) []string) *ExLine {
 
-	input := ui.NewTextInput("", conf.Ui).Prompt(prompt)
+	input := ui.NewTextInput("").Prompt(prompt)
 	if conf.Ui.CompletionPopovers {
 		input.TabComplete(tabcomplete, conf.Ui.CompletionDelay)
 	}
diff --git a/widgets/getpasswd.go b/widgets/getpasswd.go
index 3cdc5cf..34f8b1f 100644
--- a/widgets/getpasswd.go
+++ b/widgets/getpasswd.go
@@ -5,7 +5,6 @@ import (
 
 	"github.com/gdamore/tcell"
 
-	"git.sr.ht/~sircmpwn/aerc/config"
 	"git.sr.ht/~sircmpwn/aerc/lib/ui"
 )
 
@@ -15,17 +14,14 @@ type GetPasswd struct {
 	title    string
 	prompt   string
 	input    *ui.TextInput
-	conf     *config.AercConfig
 }
 
-func NewGetPasswd(title string, prompt string, conf *config.AercConfig,
-	cb func(string, error)) *GetPasswd {
+func NewGetPasswd(title string, prompt string, cb func(string, error)) *GetPasswd {
 	getpasswd := &GetPasswd{
 		callback: cb,
 		title:    title,
 		prompt:   prompt,
-		conf:     conf,
-		input:    ui.NewTextInput("", conf.Ui).Password(true).Prompt("Password: "),
+		input:    ui.NewTextInput("").Password(true).Prompt("Password: "),
 	}
 	getpasswd.input.OnInvalidate(func(_ ui.Drawable) {
 		getpasswd.Invalidate()
@@ -35,13 +31,10 @@ func NewGetPasswd(title string, prompt string, conf *config.AercConfig,
 }
 
 func (gp *GetPasswd) Draw(ctx *ui.Context) {
-	defaultStyle := gp.conf.Ui.GetStyle(config.STYLE_DEFAULT)
-	titleStyle := gp.conf.Ui.GetStyle(config.STYLE_TITLE)
-
-	ctx.Fill(0, 0, ctx.Width(), ctx.Height(), ' ', defaultStyle)
-	ctx.Fill(0, 0, ctx.Width(), 1, ' ', titleStyle)
-	ctx.Printf(1, 0, titleStyle, "%s", gp.title)
-	ctx.Printf(1, 1, defaultStyle, gp.prompt)
+	ctx.Fill(0, 0, ctx.Width(), ctx.Height(), ' ', tcell.StyleDefault)
+	ctx.Fill(0, 0, ctx.Width(), 1, ' ', tcell.StyleDefault.Reverse(true))
+	ctx.Printf(1, 0, tcell.StyleDefault.Reverse(true), "%s", gp.title)
+	ctx.Printf(1, 1, tcell.StyleDefault, gp.prompt)
 	gp.input.Draw(ctx.Subcontext(1, 3, ctx.Width()-2, 1))
 }
 
diff --git a/widgets/msglist.go b/widgets/msglist.go
index e38dd9e..1ed6bb1 100644
--- a/widgets/msglist.go
+++ b/widgets/msglist.go
@@ -50,8 +50,7 @@ func (ml *MessageList) Invalidate() {
 
 func (ml *MessageList) Draw(ctx *ui.Context) {
 	ml.height = ctx.Height()
-	ctx.Fill(0, 0, ctx.Width(), ctx.Height(), ' ',
-		ml.aerc.SelectedAccount().UiConfig().GetStyle(config.STYLE_MSGLIST_DEFAULT))
+	ctx.Fill(0, 0, ctx.Width(), ctx.Height(), ' ', tcell.StyleDefault)
 
 	store := ml.Store()
 	if store == nil {
@@ -102,50 +101,38 @@ func (ml *MessageList) Draw(ctx *ui.Context) {
 			continue
 		}
 
-		uiConfig := ml.conf.GetUiConfig(map[config.ContextType]string{
-			config.UI_CONTEXT_ACCOUNT: ml.aerc.SelectedAccount().AccountConfig().Name,
-			config.UI_CONTEXT_FOLDER:  ml.aerc.SelectedAccount().Directories().Selected(),
-			config.UI_CONTEXT_SUBJECT: msg.Envelope.Subject,
-		})
-
-		so := config.STYLE_MSGLIST_DEFAULT
+		style := tcell.StyleDefault
 
+		// current row
+		if row == ml.store.SelectedIndex()-ml.scroll {
+			style = style.Reverse(true)
+		}
 		// deleted message
 		if _, ok := store.Deleted[msg.Uid]; ok {
-			so = config.STYLE_MSGLIST_DELETED
+			style = style.Foreground(tcell.ColorGray)
 		}
 		// unread message
 		seen := false
-		flagged := false
 		for _, flag := range msg.Flags {
-			switch flag {
-			case models.SeenFlag:
+			if flag == models.SeenFlag {
 				seen = true
-			case models.FlaggedFlag:
-				flagged = true
 			}
 		}
 		if !seen {
-			so = config.STYLE_MSGLIST_UNREAD
+			style = style.Bold(true)
 		}
 
-		if flagged {
-			so = config.STYLE_MSGLIST_FLAGGED
-		}
+		ctx.Fill(0, row, textWidth, 1, ' ', style)
 
-		// marked message
-		if store.IsMarked(msg.Uid) {
-			so = config.STYLE_MSGLIST_MARKED
+		confParams := map[config.ContextType]string{
+			config.UI_CONTEXT_ACCOUNT: ml.aerc.SelectedAccount().AccountConfig().Name,
+			config.UI_CONTEXT_FOLDER:  ml.aerc.SelectedAccount().Directories().Selected(),
 		}
-
-		style := uiConfig.GetStyle(so)
-
-		// current row
-		if row == ml.store.SelectedIndex()-ml.scroll {
-			style = uiConfig.GetStyleSelected(so)
+		if msg.Envelope != nil {
+			confParams[config.UI_CONTEXT_SUBJECT] = msg.Envelope.Subject
 		}
+		uiConfig := ml.conf.GetUiConfig(confParams)
 
-		ctx.Fill(0, row, ctx.Width(), 1, ' ', style)
 		fmtStr, args, err := format.ParseMessageFormat(
 			ml.aerc.SelectedAccount().acct.From,
 			uiConfig.IndexFormat,
@@ -355,8 +342,7 @@ func (ml *MessageList) ensureScroll() {
 }
 
 func (ml *MessageList) drawEmptyMessage(ctx *ui.Context) {
-	uiConfig := ml.aerc.SelectedAccount().UiConfig()
-	msg := uiConfig.EmptyMessage
+	msg := ml.aerc.SelectedAccount().UiConfig().EmptyMessage
 	ctx.Printf((ctx.Width()/2)-(len(msg)/2), 0,
-		uiConfig.GetStyle(config.STYLE_MSGLIST_DEFAULT), "%s", msg)
+		tcell.StyleDefault, "%s", msg)
 }
diff --git a/widgets/msgviewer.go b/widgets/msgviewer.go
index 30c83f7..107ff59 100644
--- a/widgets/msgviewer.go
+++ b/widgets/msgviewer.go
@@ -33,7 +33,6 @@ type MessageViewer struct {
 	grid     *ui.Grid
 	switcher *PartSwitcher
 	msg      lib.MessageView
-	uiConfig config.UIConfig
 }
 
 type PartSwitcher struct {
@@ -63,11 +62,9 @@ func NewMessageViewer(acct *AccountView,
 	header, headerHeight := layout.grid(
 		func(header string) ui.Drawable {
 			return &HeaderView{
-				conf: conf,
 				Name: header,
 				Value: fmtHeader(msg.MessageInfo(), header,
 					acct.UiConfig().TimestampFormat),
-				uiConfig: acct.UiConfig(),
 			}
 		},
 	)
@@ -97,16 +94,15 @@ func NewMessageViewer(acct *AccountView,
 	err := createSwitcher(acct, switcher, conf, msg)
 	if err != nil {
 		return &MessageViewer{
-			err:      err,
-			grid:     grid,
-			msg:      msg,
-			uiConfig: acct.UiConfig(),
+			err:  err,
+			grid: grid,
+			msg:  msg,
 		}
 	}
 
 	grid.AddChild(header).At(0, 0)
 	if msg.PGPDetails() != nil {
-		grid.AddChild(NewPGPInfo(msg.PGPDetails(), acct.UiConfig())).At(1, 0)
+		grid.AddChild(NewPGPInfo(msg.PGPDetails())).At(1, 0)
 		grid.AddChild(ui.NewFill(' ')).At(2, 0)
 		grid.AddChild(switcher).At(3, 0)
 	} else {
@@ -120,7 +116,6 @@ func NewMessageViewer(acct *AccountView,
 		grid:     grid,
 		msg:      msg,
 		switcher: switcher,
-		uiConfig: acct.UiConfig(),
 	}
 	switcher.mv = mv
 
@@ -229,9 +224,8 @@ func createSwitcher(acct *AccountView, switcher *PartSwitcher,
 
 func (mv *MessageViewer) Draw(ctx *ui.Context) {
 	if mv.err != nil {
-		style := mv.acct.UiConfig().GetStyle(config.STYLE_DEFAULT)
-		ctx.Fill(0, 0, ctx.Width(), ctx.Height(), ' ', style)
-		ctx.Printf(0, 0, style, "%s", mv.err.Error())
+		ctx.Fill(0, 0, ctx.Width(), ctx.Height(), ' ', tcell.StyleDefault)
+		ctx.Printf(0, 0, tcell.StyleDefault, "%s", mv.err.Error())
 		return
 	}
 	mv.grid.Draw(ctx)
@@ -353,10 +347,7 @@ func (ps *PartSwitcher) Draw(ctx *ui.Context) {
 	ps.height = ctx.Height()
 	y := ctx.Height() - height
 	for i, part := range ps.parts {
-		style := ps.mv.uiConfig.GetStyle(config.STYLE_DEFAULT)
-		if ps.selected == i {
-			style = ps.mv.uiConfig.GetStyleSelected(config.STYLE_DEFAULT)
-		}
+		style := tcell.StyleDefault.Reverse(ps.selected == i)
 		ctx.Fill(0, y+i, ctx.Width(), 1, ' ', style)
 		name := fmt.Sprintf("%s/%s",
 			strings.ToLower(part.part.MIMEType),
@@ -445,7 +436,6 @@ func (mv *MessageViewer) Focus(focus bool) {
 
 type PartViewer struct {
 	ui.Invalidatable
-	conf        *config.AercConfig
 	err         error
 	fetched     bool
 	filter      *exec.Cmd
@@ -460,7 +450,6 @@ type PartViewer struct {
 	term        *Terminal
 	selecter    *Selecter
 	grid        *ui.Grid
-	uiConfig    config.UIConfig
 }
 
 func NewPartViewer(acct *AccountView, conf *config.AercConfig,
@@ -530,8 +519,7 @@ func NewPartViewer(acct *AccountView, conf *config.AercConfig,
 		{ui.SIZE_WEIGHT, ui.Const(1)},
 	})
 
-	selecter := NewSelecter([]string{"Save message", "Pipe to command"},
-		0, acct.UiConfig()).
+	selecter := NewSelecter([]string{"Save message", "Pipe to command"}, 0).
 		OnChoose(func(option string) {
 			switch option {
 			case "Save message":
@@ -544,7 +532,6 @@ func NewPartViewer(acct *AccountView, conf *config.AercConfig,
 	grid.AddChild(selecter).At(2, 0)
 
 	pv := &PartViewer{
-		conf:        conf,
 		filter:      filter,
 		index:       index,
 		msg:         msg,
@@ -556,7 +543,6 @@ func NewPartViewer(acct *AccountView, conf *config.AercConfig,
 		term:        term,
 		selecter:    selecter,
 		grid:        grid,
-		uiConfig:    acct.UiConfig(),
 	}
 
 	if term != nil {
@@ -675,16 +661,14 @@ func (pv *PartViewer) Invalidate() {
 }
 
 func (pv *PartViewer) Draw(ctx *ui.Context) {
-	style := pv.uiConfig.GetStyle(config.STYLE_DEFAULT)
-	styleError := pv.uiConfig.GetStyle(config.STYLE_ERROR)
 	if pv.filter == nil {
 		// TODO: Let them download it directly or something
-		ctx.Fill(0, 0, ctx.Width(), ctx.Height(), ' ', style)
-		ctx.Printf(0, 0, styleError,
+		ctx.Fill(0, 0, ctx.Width(), ctx.Height(), ' ', tcell.StyleDefault)
+		ctx.Printf(0, 0, tcell.StyleDefault.Foreground(tcell.ColorRed),
 			"No filter configured for this mimetype ('%s/%s')",
 			pv.part.MIMEType, pv.part.MIMESubType,
 		)
-		ctx.Printf(0, 2, style,
+		ctx.Printf(0, 2, tcell.StyleDefault,
 			"You can still :save the message or :pipe it to an external command")
 		pv.selecter.Focus(true)
 		pv.grid.Draw(ctx)
@@ -695,8 +679,8 @@ func (pv *PartViewer) Draw(ctx *ui.Context) {
 		pv.fetched = true
 	}
 	if pv.err != nil {
-		ctx.Fill(0, 0, ctx.Width(), ctx.Height(), ' ', style)
-		ctx.Printf(0, 0, style, "%s", pv.err.Error())
+		ctx.Fill(0, 0, ctx.Width(), ctx.Height(), ' ', tcell.StyleDefault)
+		ctx.Printf(0, 0, tcell.StyleDefault, "%s", pv.err.Error())
 		return
 	}
 	pv.term.Draw(ctx)
@@ -718,10 +702,8 @@ func (pv *PartViewer) Event(event tcell.Event) bool {
 
 type HeaderView struct {
 	ui.Invalidatable
-	conf     *config.AercConfig
-	Name     string
-	Value    string
-	uiConfig config.UIConfig
+	Name  string
+	Value string
 }
 
 func (hv *HeaderView) Draw(ctx *ui.Context) {
@@ -729,15 +711,18 @@ func (hv *HeaderView) Draw(ctx *ui.Context) {
 	size := runewidth.StringWidth(name)
 	lim := ctx.Width() - size - 1
 	value := runewidth.Truncate(" "+hv.Value, lim, "…")
-
-	vstyle := hv.uiConfig.GetStyle(config.STYLE_DEFAULT)
-	hstyle := hv.uiConfig.GetStyle(config.STYLE_HEADER)
-
+	var (
+		hstyle tcell.Style
+		vstyle tcell.Style
+	)
 	// TODO: Make this more robust and less dumb
 	if hv.Name == "PGP" {
-		vstyle = hv.uiConfig.GetStyle(config.STYLE_SUCCESS)
+		vstyle = tcell.StyleDefault.Foreground(tcell.ColorGreen)
+		hstyle = tcell.StyleDefault.Bold(true)
+	} else {
+		vstyle = tcell.StyleDefault
+		hstyle = tcell.StyleDefault.Bold(true)
 	}
-
 	ctx.Fill(0, 0, ctx.Width(), ctx.Height(), ' ', vstyle)
 	ctx.Printf(0, 0, hstyle, "%s", name)
 	ctx.Printf(size, 0, vstyle, "%s", value)
diff --git a/widgets/pgpinfo.go b/widgets/pgpinfo.go
index 94fb877..5da9141 100644
--- a/widgets/pgpinfo.go
+++ b/widgets/pgpinfo.go
@@ -3,40 +3,40 @@ package widgets
 import (
 	"errors"
 
-	"git.sr.ht/~sircmpwn/aerc/config"
 	"git.sr.ht/~sircmpwn/aerc/lib/ui"
 
+	"github.com/gdamore/tcell"
 	"golang.org/x/crypto/openpgp"
 	pgperrors "golang.org/x/crypto/openpgp/errors"
 )
 
 type PGPInfo struct {
 	ui.Invalidatable
-	details  *openpgp.MessageDetails
-	uiConfig config.UIConfig
+	details *openpgp.MessageDetails
 }
 
-func NewPGPInfo(details *openpgp.MessageDetails, uiConfig config.UIConfig) *PGPInfo {
-	return &PGPInfo{details: details, uiConfig: uiConfig}
+func NewPGPInfo(details *openpgp.MessageDetails) *PGPInfo {
+	return &PGPInfo{details: details}
 }
 
 func (p *PGPInfo) DrawSignature(ctx *ui.Context) {
-	errorStyle := p.uiConfig.GetStyle(config.STYLE_ERROR)
-	warningStyle := p.uiConfig.GetStyle(config.STYLE_WARNING)
-	validStyle := p.uiConfig.GetStyle(config.STYLE_SUCCESS)
-	defaultStyle := p.uiConfig.GetStyle(config.STYLE_DEFAULT)
+	errorStyle := tcell.StyleDefault.Background(tcell.ColorRed).
+		Foreground(tcell.ColorWhite).Bold(true)
+	softErrorStyle := tcell.StyleDefault.Foreground(tcell.ColorYellow).Bold(true)
+	validStyle := tcell.StyleDefault.Foreground(tcell.ColorGreen).Bold(true)
 
 	// TODO: Nicer prompt for TOFU, fetch from keyserver, etc
 	if errors.Is(p.details.SignatureError, pgperrors.ErrUnknownIssuer) ||
 		p.details.SignedBy == nil {
 
-		x := ctx.Printf(0, 0, warningStyle, "*")
-		x += ctx.Printf(x, 0, defaultStyle,
+		x := ctx.Printf(0, 0, softErrorStyle, "*")
+		x += ctx.Printf(x, 0, tcell.StyleDefault,
 			" Signed with unknown key (%8X); authenticity unknown",
 			p.details.SignedByKeyId)
 	} else if p.details.SignatureError != nil {
 		x := ctx.Printf(0, 0, errorStyle, "Invalid signature!")
-		x += ctx.Printf(x, 0, errorStyle,
+		x += ctx.Printf(x, 0, tcell.StyleDefault.
+			Foreground(tcell.ColorRed).Bold(true),
 			" This message may have been tampered with! (%s)",
 			p.details.SignatureError.Error())
 	} else {
@@ -44,26 +44,24 @@ func (p *PGPInfo) DrawSignature(ctx *ui.Context) {
 		ident := entity.PrimaryIdentity()
 
 		x := ctx.Printf(0, 0, validStyle, "✓ Authentic ")
-		x += ctx.Printf(x, 0, defaultStyle,
+		x += ctx.Printf(x, 0, tcell.StyleDefault,
 			"Signature from %s (%8X)",
 			ident.Name, p.details.SignedByKeyId)
 	}
 }
 
 func (p *PGPInfo) DrawEncryption(ctx *ui.Context, y int) {
-	validStyle := p.uiConfig.GetStyle(config.STYLE_SUCCESS)
-	defaultStyle := p.uiConfig.GetStyle(config.STYLE_DEFAULT)
+	validStyle := tcell.StyleDefault.Foreground(tcell.ColorGreen).Bold(true)
 	entity := p.details.DecryptedWith.Entity
 	ident := entity.PrimaryIdentity()
 
 	x := ctx.Printf(0, y, validStyle, "✓ Encrypted ")
-	x += ctx.Printf(x, y, defaultStyle,
+	x += ctx.Printf(x, y, tcell.StyleDefault,
 		"To %s (%8X) ", ident.Name, p.details.DecryptedWith.PublicKey.KeyId)
 }
 
 func (p *PGPInfo) Draw(ctx *ui.Context) {
-	defaultStyle := p.uiConfig.GetStyle(config.STYLE_DEFAULT)
-	ctx.Fill(0, 0, ctx.Width(), ctx.Height(), ' ', defaultStyle)
+	ctx.Fill(0, 0, ctx.Width(), ctx.Height(), ' ', tcell.StyleDefault)
 	if p.details.IsSigned && p.details.IsEncrypted {
 		p.DrawSignature(ctx)
 		p.DrawEncryption(ctx, 1)
diff --git a/widgets/selector.go b/widgets/selecter.go
index d19d38f..7fae9cd 100644
--- a/widgets/selector.go
+++ b/widgets/selecter.go
@@ -3,50 +3,46 @@ package widgets
 import (
 	"github.com/gdamore/tcell"
 
-	"git.sr.ht/~sircmpwn/aerc/config"
 	"git.sr.ht/~sircmpwn/aerc/lib/ui"
 )
 
-type Selector struct {
+type Selecter struct {
 	ui.Invalidatable
-	chooser  bool
-	focused  bool
-	focus    int
-	options  []string
-	uiConfig config.UIConfig
+	chooser bool
+	focused bool
+	focus   int
+	options []string
 
 	onChoose func(option string)
 	onSelect func(option string)
 }
 
-func NewSelector(options []string, focus int, uiConfig config.UIConfig) *Selector {
-	return &Selector{
-		focus:    focus,
-		options:  options,
-		uiConfig: uiConfig,
+func NewSelecter(options []string, focus int) *Selecter {
+	return &Selecter{
+		focus:   focus,
+		options: options,
 	}
 }
 
-func (sel *Selector) Chooser(chooser bool) *Selector {
+func (sel *Selecter) Chooser(chooser bool) *Selecter {
 	sel.chooser = chooser
 	return sel
 }
 
-func (sel *Selector) Invalidate() {
+func (sel *Selecter) Invalidate() {
 	sel.DoInvalidate(sel)
 }
 
-func (sel *Selector) Draw(ctx *ui.Context) {
-	ctx.Fill(0, 0, ctx.Width(), ctx.Height(), ' ',
-		sel.uiConfig.GetStyle(config.STYLE_SELECTOR_DEFAULT))
+func (sel *Selecter) Draw(ctx *ui.Context) {
+	ctx.Fill(0, 0, ctx.Width(), ctx.Height(), ' ', tcell.StyleDefault)
 	x := 2
 	for i, option := range sel.options {
-		style := sel.uiConfig.GetStyle(config.STYLE_SELECTOR_DEFAULT)
+		style := tcell.StyleDefault
 		if sel.focus == i {
 			if sel.focused {
-				style = sel.uiConfig.GetStyle(config.STYLE_SELECTOR_FOCUSED)
+				style = style.Reverse(true)
 			} else if sel.chooser {
-				style = sel.uiConfig.GetStyle(config.STYLE_SELECTOR_CHOOSER)
+				style = style.Bold(true)
 			}
 		}
 		x += ctx.Printf(x, 1, style, "[%s]", option)
@@ -54,26 +50,26 @@ func (sel *Selector) Draw(ctx *ui.Context) {
 	}
 }
 
-func (sel *Selector) OnChoose(fn func(option string)) *Selector {
+func (sel *Selecter) OnChoose(fn func(option string)) *Selecter {
 	sel.onChoose = fn
 	return sel
 }
 
-func (sel *Selector) OnSelect(fn func(option string)) *Selector {
+func (sel *Selecter) OnSelect(fn func(option string)) *Selecter {
 	sel.onSelect = fn
 	return sel
 }
 
-func (sel *Selector) Selected() string {
+func (sel *Selecter) Selected() string {
 	return sel.options[sel.focus]
 }
 
-func (sel *Selector) Focus(focus bool) {
+func (sel *Selecter) Focus(focus bool) {
 	sel.focused = focus
 	sel.Invalidate()
 }
 
-func (sel *Selector) Event(event tcell.Event) bool {
+func (sel *Selecter) Event(event tcell.Event) bool {
 	switch event := event.(type) {
 	case *tcell.EventKey:
 		switch event.Key() {
diff --git a/widgets/spinner.go b/widgets/spinner.go
index 0c72422..51b8c1b 100644
--- a/widgets/spinner.go
+++ b/widgets/spinner.go
@@ -16,7 +16,6 @@ type Spinner struct {
 	frame  int64 // access via atomic
 	frames []string
 	stop   chan struct{}
-	style  tcell.Style
 }
 
 func NewSpinner(uiConf *config.UIConfig) *Spinner {
@@ -24,7 +23,6 @@ func NewSpinner(uiConf *config.UIConfig) *Spinner {
 		stop:   make(chan struct{}),
 		frame:  -1,
 		frames: strings.Split(uiConf.Spinner, uiConf.SpinnerDelimiter),
-		style:  uiConf.GetStyle(config.STYLE_SPINNER),
 	}
 	return &spinner
 }
@@ -72,9 +70,9 @@ func (s *Spinner) Draw(ctx *ui.Context) {
 
 	cur := int(atomic.LoadInt64(&s.frame) % int64(len(s.frames)))
 
-	ctx.Fill(0, 0, ctx.Width(), ctx.Height(), ' ', s.style)
+	ctx.Fill(0, 0, ctx.Width(), ctx.Height(), ' ', tcell.StyleDefault)
 	col := ctx.Width()/2 - len(s.frames[0])/2 + 1
-	ctx.Printf(col, 0, s.style, "%s", s.frames[cur])
+	ctx.Printf(col, 0, tcell.StyleDefault, "%s", s.frames[cur])
 }
 
 func (s *Spinner) Invalidate() {
diff --git a/widgets/status.go b/widgets/status.go
index 122ca5f..6bdeb4f 100644
--- a/widgets/status.go
+++ b/widgets/status.go
@@ -6,7 +6,6 @@ import (
 	"github.com/gdamore/tcell"
 	"github.com/mattn/go-runewidth"
 
-	"git.sr.ht/~sircmpwn/aerc/config"
 	"git.sr.ht/~sircmpwn/aerc/lib/ui"
 )
 
@@ -15,21 +14,21 @@ type StatusLine struct {
 	stack    []*StatusMessage
 	fallback StatusMessage
 	aerc     *Aerc
-	uiConfig config.UIConfig
 }
 
 type StatusMessage struct {
-	style   tcell.Style
+	bg      tcell.Color
+	fg      tcell.Color
 	message string
 }
 
-func NewStatusLine(uiConfig config.UIConfig) *StatusLine {
+func NewStatusLine() *StatusLine {
 	return &StatusLine{
 		fallback: StatusMessage{
-			style:   uiConfig.GetStyle(config.STYLE_STATUSLINE_DEFAULT),
+			bg:      tcell.ColorDefault,
+			fg:      tcell.ColorDefault,
 			message: "Idle",
 		},
-		uiConfig: uiConfig,
 	}
 }
 
@@ -42,7 +41,9 @@ func (status *StatusLine) Draw(ctx *ui.Context) {
 	if len(status.stack) != 0 {
 		line = status.stack[len(status.stack)-1]
 	}
-	ctx.Fill(0, 0, ctx.Width(), ctx.Height(), ' ', line.style)
+	style := tcell.StyleDefault.
+		Background(line.bg).Foreground(line.fg).Reverse(true)
+	ctx.Fill(0, 0, ctx.Width(), ctx.Height(), ' ', style)
 	pendingKeys := ""
 	if status.aerc != nil {
 		for _, pendingKey := range status.aerc.pendingKeys {
@@ -50,21 +51,13 @@ func (status *StatusLine) Draw(ctx *ui.Context) {
 		}
 	}
 	message := runewidth.FillRight(line.message, ctx.Width()-len(pendingKeys)-5)
-	ctx.Printf(0, 0, line.style, "%s%s", message, pendingKeys)
+	ctx.Printf(0, 0, style, "%s%s", message, pendingKeys)
 }
 
 func (status *StatusLine) Set(text string) *StatusMessage {
 	status.fallback = StatusMessage{
-		style:   status.uiConfig.GetStyle(config.STYLE_STATUSLINE_DEFAULT),
-		message: text,
-	}
-	status.Invalidate()
-	return &status.fallback
-}
-
-func (status *StatusLine) SetError(text string) *StatusMessage {
-	status.fallback = StatusMessage{
-		style:   status.uiConfig.GetStyle(config.STYLE_STATUSLINE_ERROR),
+		bg:      tcell.ColorDefault,
+		fg:      tcell.ColorDefault,
 		message: text,
 	}
 	status.Invalidate()
@@ -73,7 +66,8 @@ func (status *StatusLine) SetError(text string) *StatusMessage {
 
 func (status *StatusLine) Push(text string, expiry time.Duration) *StatusMessage {
 	msg := &StatusMessage{
-		style:   status.uiConfig.GetStyle(config.STYLE_STATUSLINE_DEFAULT),
+		bg:      tcell.ColorDefault,
+		fg:      tcell.ColorDefault,
 		message: text,
 	}
 	status.stack = append(status.stack, msg)
@@ -91,18 +85,6 @@ func (status *StatusLine) Push(text string, expiry time.Duration) *StatusMessage
 	return msg
 }
 
-func (status *StatusLine) PushError(text string) *StatusMessage {
-	msg := status.Push(text, 10*time.Second)
-	msg.Color(status.uiConfig.GetStyle(config.STYLE_STATUSLINE_ERROR))
-	return msg
-}
-
-func (status *StatusLine) PushSuccess(text string) *StatusMessage {
-	msg := status.Push(text, 10*time.Second)
-	msg.Color(status.uiConfig.GetStyle(config.STYLE_STATUSLINE_SUCCESS))
-	return msg
-}
-
 func (status *StatusLine) Expire() {
 	status.stack = nil
 }
@@ -111,6 +93,7 @@ func (status *StatusLine) SetAerc(aerc *Aerc) {
 	status.aerc = aerc
 }
 
-func (msg *StatusMessage) Color(style tcell.Style) {
-	msg.style = style
+func (msg *StatusMessage) Color(bg tcell.Color, fg tcell.Color) {
+	msg.bg = bg
+	msg.fg = fg
 }
diff --git a/widgets/tabhost.go b/widgets/tabhost.go
index 28c9be0..0ac67e5 100644
--- a/widgets/tabhost.go
+++ b/widgets/tabhost.go
@@ -7,9 +7,6 @@ import (
 type TabHost interface {
 	BeginExCommand(cmd string)
 	SetStatus(status string) *StatusMessage
-	SetError(err string) *StatusMessage
 	PushStatus(text string, expiry time.Duration) *StatusMessage
-	PushError(text string) *StatusMessage
-	PushSuccess(text string) *StatusMessage
 	Beep()
 }