feat(tui): add WCAG-compliant light theme

Amolith created

Add --light flag to enable light theme with accessible colors.

Make theme colors WCAG AA compliant (4.5:1 contrast minimum). Add
theme-aware markdown and diff colors to support both dark and light
backgrounds.

Assisted-by: Claude Opus 4.5 via Crush

Change summary

internal/cmd/root.go             |  10 ++
internal/tui/styles/charmtone.go |  45 ++++++++++
internal/tui/styles/light.go     | 121 +++++++++++++++++++++++++++
internal/tui/styles/theme.go     | 151 ++++++++++++++++++++++-----------
4 files changed, 278 insertions(+), 49 deletions(-)

Detailed changes

internal/cmd/root.go 🔗

@@ -21,6 +21,7 @@ import (
 	"git.secluded.site/crush/internal/projects"
 	"git.secluded.site/crush/internal/stringext"
 	"git.secluded.site/crush/internal/tui"
+	"git.secluded.site/crush/internal/tui/styles"
 	"git.secluded.site/crush/internal/version"
 	"github.com/charmbracelet/colorprofile"
 	"github.com/charmbracelet/fang"
@@ -35,6 +36,7 @@ func init() {
 	rootCmd.PersistentFlags().StringP("cwd", "c", "", "Current working directory")
 	rootCmd.PersistentFlags().StringP("data-dir", "D", "", "Custom crush data directory")
 	rootCmd.PersistentFlags().BoolP("debug", "d", false, "Debug")
+	rootCmd.PersistentFlags().BoolP("light", "l", false, "Use light theme")
 	rootCmd.Flags().BoolP("help", "h", false, "Help")
 	rootCmd.Flags().BoolP("yolo", "y", false, "Automatically accept all permissions (dangerous mode)")
 
@@ -177,9 +179,17 @@ func setupAppWithProgressBar(cmd *cobra.Command) (*app.App, error) {
 func setupApp(cmd *cobra.Command) (*app.App, error) {
 	debug, _ := cmd.Flags().GetBool("debug")
 	yolo, _ := cmd.Flags().GetBool("yolo")
+	light, _ := cmd.Flags().GetBool("light")
 	dataDir, _ := cmd.Flags().GetString("data-dir")
 	ctx := cmd.Context()
 
+	// Set light theme if requested.
+	if light {
+		if err := styles.DefaultManager().SetTheme("light"); err != nil {
+			slog.Warn("Failed to set light theme", "error", err)
+		}
+	}
+
 	cwd, err := ResolveCwd(cmd)
 	if err != nil {
 		return nil, err

internal/tui/styles/charmtone.go 🔗

@@ -56,6 +56,51 @@ func NewCharmtoneTheme() *Theme {
 		RedDark:  charmtone.Sriracha,
 		RedLight: charmtone.Salmon,
 		Cherry:   charmtone.Cherry,
+
+		// Markdown colors (dark theme).
+		MdText:          charmtone.Smoke,
+		MdHeading:       charmtone.Malibu,
+		MdH6:            charmtone.Guac,
+		MdHRule:         charmtone.Charcoal,
+		MdLink:          charmtone.Zinc,
+		MdLinkText:      charmtone.Guac,
+		MdImage:         charmtone.Cheeky,
+		MdImageText:     charmtone.Squid,
+		MdCodeFg:        charmtone.Coral,
+		MdCodeBg:        charmtone.Charcoal,
+		MdCodeBlockFg:   charmtone.Charcoal,
+		MdCodeBlockBg:   charmtone.Charcoal,
+		MdComment:       charmtone.Oyster,
+		MdKeyword:       charmtone.Malibu,
+		MdKeywordAlt:    charmtone.Pony,
+		MdKeywordType:   charmtone.Guppy,
+		MdOperator:      charmtone.Salmon,
+		MdPunctuation:   charmtone.Zest,
+		MdName:          charmtone.Smoke,
+		MdNameBuiltin:   charmtone.Cheeky,
+		MdNameTag:       charmtone.Mauve,
+		MdNameAttr:      charmtone.Hazy,
+		MdNameClass:     charmtone.Salt,
+		MdNameDecorator: charmtone.Citron,
+		MdNameFunc:      charmtone.Guac,
+		MdNumber:        charmtone.Julep,
+		MdString:        charmtone.Cumin,
+		MdStringEscape:  charmtone.Bok,
+		MdDeleted:       charmtone.Coral,
+		MdInserted:      charmtone.Guac,
+		MdSubheading:    charmtone.Squid,
+		MdError:         charmtone.Butter,
+		MdErrorBg:       charmtone.Sriracha,
+
+		// Diff colors (dark theme).
+		DiffInsertFg:     lipgloss.Color("#629657"),
+		DiffInsertBg:     lipgloss.Color("#2b322a"),
+		DiffInsertSymBg:  lipgloss.Color("#323931"),
+		DiffInsertCodeBg: lipgloss.Color("#323931"),
+		DiffDeleteFg:     lipgloss.Color("#a45c59"),
+		DiffDeleteBg:     lipgloss.Color("#312929"),
+		DiffDeleteSymBg:  lipgloss.Color("#383030"),
+		DiffDeleteCodeBg: lipgloss.Color("#383030"),
 	}
 
 	// Text selection.

internal/tui/styles/light.go 🔗

@@ -0,0 +1,121 @@
+package styles
+
+import (
+	"charm.land/lipgloss/v2"
+	"github.com/charmbracelet/x/exp/charmtone"
+)
+
+func NewLightTheme() *Theme {
+	t := &Theme{
+		Name:   "light",
+		IsDark: false,
+
+		Primary:   charmtone.Charple,
+		Secondary: charmtone.Dolly,
+		Tertiary:  charmtone.Bok,
+		Accent:    charmtone.Zest,
+
+		// Backgrounds (improved for accessibility)
+		BgBase:        charmtone.Salt,            // #F1EFEF - very light background
+		BgBaseLighter: lipgloss.Color("#FFFFFF"), // Pure white for areas needing more contrast
+		BgSubtle:      charmtone.Ash,             // #DFDBDD - light gray for subtle areas
+		BgOverlay:     charmtone.Oyster,          // #605F6B - darker overlay, better contrast
+
+		// Foregrounds (darker for better accessibility on light backgrounds)
+		FgBase:      charmtone.Pepper,   // #201F26 - darkest, good contrast
+		FgMuted:     charmtone.Charcoal, // #3A3943 - still quite dark
+		FgHalfMuted: charmtone.Iron,     // #4D4C57 - medium dark
+		FgSubtle:    charmtone.Oyster,   // #605F6B - much better than Squid
+		FgSelected:  charmtone.Pepper,   // #201F26 - same as base for consistency
+
+		// Borders (improved contrast)
+		Border:      charmtone.Oyster,  // #605F6B - better contrast than previous
+		BorderFocus: charmtone.Charple, // #6B50FF - same purple for focus
+
+		// Status (same as dark, these work on both)
+		Success: charmtone.Turtle,
+		Error:   charmtone.Cherry,
+		Warning: charmtone.Zest,
+		Info:    charmtone.Sapphire,
+
+		// Colors
+		White: charmtone.Salt,
+
+		BlueLight: charmtone.Malibu,
+		BlueDark:  charmtone.Sapphire,
+		Blue:      charmtone.Guppy,
+
+		Yellow: charmtone.Mustard,
+		Citron: charmtone.Citron,
+
+		Green:      charmtone.Turtle,
+		GreenDark:  charmtone.Guac,
+		GreenLight: charmtone.Bok,
+
+		Red:      charmtone.Coral,
+		RedDark:  charmtone.Cherry,
+		RedLight: charmtone.Salmon,
+		Cherry:   charmtone.Cherry,
+
+		// Markdown colors (light theme - accessible on Salt background).
+		MdText:          charmtone.Pepper,          // 14.26:1 contrast
+		MdHeading:       lipgloss.Color("#0066CC"), // Dark blue, 4.86:1
+		MdH6:            lipgloss.Color("#006644"), // Forest green, 6.14:1
+		MdHRule:         charmtone.Oyster,          // 5.48:1
+		MdLink:          lipgloss.Color("#007777"), // Dark teal, 4.69:1
+		MdLinkText:      lipgloss.Color("#006644"), // Forest green, 6.14:1
+		MdImage:         lipgloss.Color("#CC2255"), // Dark red, 4.67:1
+		MdImageText:     charmtone.Iron,            // 7.37:1
+		MdCodeFg:        charmtone.Charcoal,        // 9.93:1 on Ash
+		MdCodeBg:        charmtone.Ash,             // Light gray background
+		MdCodeBlockFg:   charmtone.Pepper,          // Dark text
+		MdCodeBlockBg:   charmtone.Ash,             // Light gray background
+		MdComment:       charmtone.Oyster,          // 5.48:1
+		MdKeyword:       lipgloss.Color("#0066CC"), // Dark blue, 4.86:1
+		MdKeywordAlt:    lipgloss.Color("#5533CC"), // Dark purple, 6.71:1
+		MdKeywordType:   charmtone.Sapphire,        // 4.98:1
+		MdOperator:      lipgloss.Color("#CC2255"), // Dark red, 4.67:1
+		MdPunctuation:   charmtone.Charcoal,        // 9.93:1 (can't use bright yellow)
+		MdName:          charmtone.Charcoal,        // 9.93:1
+		MdNameBuiltin:   lipgloss.Color("#CC2255"), // Dark red, 4.67:1
+		MdNameTag:       lipgloss.Color("#8B008B"), // Dark magenta, 7.42:1
+		MdNameAttr:      lipgloss.Color("#4B0082"), // Indigo, 11.31:1
+		MdNameClass:     charmtone.Pepper,          // Darkest, bold
+		MdNameDecorator: lipgloss.Color("#B7410E"), // Rust, 4.86:1
+		MdNameFunc:      lipgloss.Color("#006644"), // Forest green, 6.14:1
+		MdNumber:        lipgloss.Color("#800080"), // Purple, 8.22:1
+		MdString:        lipgloss.Color("#8B4513"), // Saddle brown, 6.20:1
+		MdStringEscape:  lipgloss.Color("#006644"), // Forest green, 6.14:1
+		MdDeleted:       lipgloss.Color("#CC2255"), // Dark red, 4.67:1
+		MdInserted:      lipgloss.Color("#006644"), // Forest green, 6.14:1
+		MdSubheading:    charmtone.Iron,            // 7.37:1
+		MdError:         charmtone.Pepper,          // Dark text
+		MdErrorBg:       lipgloss.Color("#FFCCCC"), // Light red background
+
+		// Diff colors (light theme - light backgrounds with dark text).
+		DiffInsertFg:     lipgloss.Color("#006644"), // Dark green text
+		DiffInsertBg:     lipgloss.Color("#D4EDDA"), // Light green bg
+		DiffInsertSymBg:  lipgloss.Color("#C3E6CB"), // Slightly darker green
+		DiffInsertCodeBg: lipgloss.Color("#D4EDDA"), // Light green bg
+		DiffDeleteFg:     lipgloss.Color("#CC2255"), // Dark red text
+		DiffDeleteBg:     lipgloss.Color("#F8D7DA"), // Light red bg
+		DiffDeleteSymBg:  lipgloss.Color("#F5C6CB"), // Slightly darker red
+		DiffDeleteCodeBg: lipgloss.Color("#F8D7DA"), // Light red bg
+	}
+
+	// Text selection.
+	t.TextSelection = lipgloss.NewStyle().Foreground(charmtone.Salt).Background(charmtone.Charple)
+
+	// LSP and MCP status (improved accessibility).
+	t.ItemOfflineIcon = lipgloss.NewStyle().Foreground(charmtone.Oyster).SetString("●")
+	t.ItemBusyIcon = t.ItemOfflineIcon.Foreground(charmtone.Citron)
+	t.ItemErrorIcon = t.ItemOfflineIcon.Foreground(charmtone.Cherry)
+	t.ItemOnlineIcon = t.ItemOfflineIcon.Foreground(charmtone.Turtle)
+
+	t.YoloIconFocused = lipgloss.NewStyle().Foreground(charmtone.Pepper).Background(charmtone.Citron).Bold(true).SetString(" ! ")
+	t.YoloIconBlurred = t.YoloIconFocused.Foreground(charmtone.Salt).Background(charmtone.Iron)
+	t.YoloDotsFocused = lipgloss.NewStyle().Foreground(charmtone.Zest).SetString(":::")
+	t.YoloDotsBlurred = t.YoloDotsFocused.Foreground(charmtone.Oyster)
+
+	return t
+}

internal/tui/styles/theme.go 🔗

@@ -76,6 +76,51 @@ type Theme struct {
 	RedLight color.Color
 	Cherry   color.Color
 
+	// Markdown colors (theme-aware for accessibility).
+	MdText          color.Color // Document text
+	MdHeading       color.Color // Heading color
+	MdH6            color.Color // H6 color
+	MdHRule         color.Color // Horizontal rule
+	MdLink          color.Color // Link URL
+	MdLinkText      color.Color // Link text
+	MdImage         color.Color // Image URL
+	MdImageText     color.Color // Image alt text
+	MdCodeFg        color.Color // Inline code foreground
+	MdCodeBg        color.Color // Inline code background
+	MdCodeBlockFg   color.Color // Code block text
+	MdCodeBlockBg   color.Color // Code block background
+	MdComment       color.Color // Code comment
+	MdKeyword       color.Color // Keyword
+	MdKeywordAlt    color.Color // Keyword reserved/namespace
+	MdKeywordType   color.Color // Type keyword
+	MdOperator      color.Color // Operator
+	MdPunctuation   color.Color // Punctuation
+	MdName          color.Color // Name/identifier
+	MdNameBuiltin   color.Color // Builtin name
+	MdNameTag       color.Color // Tag name
+	MdNameAttr      color.Color // Attribute name
+	MdNameClass     color.Color // Class name
+	MdNameDecorator color.Color // Decorator
+	MdNameFunc      color.Color // Function name
+	MdNumber        color.Color // Literal number
+	MdString        color.Color // Literal string
+	MdStringEscape  color.Color // String escape
+	MdDeleted       color.Color // Deleted text
+	MdInserted      color.Color // Inserted text
+	MdSubheading    color.Color // Subheading
+	MdError         color.Color // Error foreground
+	MdErrorBg       color.Color // Error background
+
+	// Diff colors (theme-aware).
+	DiffInsertFg     color.Color // Insert line number fg
+	DiffInsertBg     color.Color // Insert line number bg
+	DiffInsertSymBg  color.Color // Insert symbol bg
+	DiffInsertCodeBg color.Color // Insert code bg
+	DiffDeleteFg     color.Color // Delete line number fg
+	DiffDeleteBg     color.Color // Delete line number bg
+	DiffDeleteSymBg  color.Color // Delete symbol bg
+	DiffDeleteCodeBg color.Color // Delete code bg
+
 	// Text selection.
 	TextSelection lipgloss.Style
 
@@ -221,7 +266,7 @@ func (t *Theme) buildStyles() *Styles {
 				StylePrimitive: ansi.StylePrimitive{
 					// BlockPrefix: "\n",
 					// BlockSuffix: "\n",
-					Color: stringPtr(charmtone.Smoke.Hex()),
+					Color: stringPtr(ToHex(t.MdText)),
 				},
 				// Margin: uintPtr(defaultMargin),
 			},
@@ -236,7 +281,7 @@ func (t *Theme) buildStyles() *Styles {
 			Heading: ansi.StyleBlock{
 				StylePrimitive: ansi.StylePrimitive{
 					BlockSuffix: "\n",
-					Color:       stringPtr(charmtone.Malibu.Hex()),
+					Color:       stringPtr(ToHex(t.MdHeading)),
 					Bold:        boolPtr(true),
 				},
 			},
@@ -272,7 +317,7 @@ func (t *Theme) buildStyles() *Styles {
 			H6: ansi.StyleBlock{
 				StylePrimitive: ansi.StylePrimitive{
 					Prefix: "###### ",
-					Color:  stringPtr(charmtone.Guac.Hex()),
+					Color:  stringPtr(ToHex(t.MdH6)),
 					Bold:   boolPtr(false),
 				},
 			},
@@ -286,7 +331,7 @@ func (t *Theme) buildStyles() *Styles {
 				Bold: boolPtr(true),
 			},
 			HorizontalRule: ansi.StylePrimitive{
-				Color:  stringPtr(charmtone.Charcoal.Hex()),
+				Color:  stringPtr(ToHex(t.MdHRule)),
 				Format: "\n--------\n",
 			},
 			Item: ansi.StylePrimitive{
@@ -301,117 +346,117 @@ func (t *Theme) buildStyles() *Styles {
 				Unticked:       "[ ] ",
 			},
 			Link: ansi.StylePrimitive{
-				Color:     stringPtr(charmtone.Zinc.Hex()),
+				Color:     stringPtr(ToHex(t.MdLink)),
 				Underline: boolPtr(true),
 			},
 			LinkText: ansi.StylePrimitive{
-				Color: stringPtr(charmtone.Guac.Hex()),
+				Color: stringPtr(ToHex(t.MdLinkText)),
 				Bold:  boolPtr(true),
 			},
 			Image: ansi.StylePrimitive{
-				Color:     stringPtr(charmtone.Cheeky.Hex()),
+				Color:     stringPtr(ToHex(t.MdImage)),
 				Underline: boolPtr(true),
 			},
 			ImageText: ansi.StylePrimitive{
-				Color:  stringPtr(charmtone.Squid.Hex()),
+				Color:  stringPtr(ToHex(t.MdImageText)),
 				Format: "Image: {{.text}} →",
 			},
 			Code: ansi.StyleBlock{
 				StylePrimitive: ansi.StylePrimitive{
 					Prefix:          " ",
 					Suffix:          " ",
-					Color:           stringPtr(charmtone.Coral.Hex()),
-					BackgroundColor: stringPtr(charmtone.Charcoal.Hex()),
+					Color:           stringPtr(ToHex(t.MdCodeFg)),
+					BackgroundColor: stringPtr(ToHex(t.MdCodeBg)),
 				},
 			},
 			CodeBlock: ansi.StyleCodeBlock{
 				StyleBlock: ansi.StyleBlock{
 					StylePrimitive: ansi.StylePrimitive{
-						Color: stringPtr(charmtone.Charcoal.Hex()),
+						Color: stringPtr(ToHex(t.MdCodeBlockFg)),
 					},
 					Margin: uintPtr(defaultMargin),
 				},
 				Chroma: &ansi.Chroma{
 					Text: ansi.StylePrimitive{
-						Color: stringPtr(charmtone.Smoke.Hex()),
+						Color: stringPtr(ToHex(t.MdText)),
 					},
 					Error: ansi.StylePrimitive{
-						Color:           stringPtr(charmtone.Butter.Hex()),
-						BackgroundColor: stringPtr(charmtone.Sriracha.Hex()),
+						Color:           stringPtr(ToHex(t.MdError)),
+						BackgroundColor: stringPtr(ToHex(t.MdErrorBg)),
 					},
 					Comment: ansi.StylePrimitive{
-						Color: stringPtr(charmtone.Oyster.Hex()),
+						Color: stringPtr(ToHex(t.MdComment)),
 					},
 					CommentPreproc: ansi.StylePrimitive{
-						Color: stringPtr(charmtone.Bengal.Hex()),
+						Color: stringPtr(ToHex(t.MdComment)),
 					},
 					Keyword: ansi.StylePrimitive{
-						Color: stringPtr(charmtone.Malibu.Hex()),
+						Color: stringPtr(ToHex(t.MdKeyword)),
 					},
 					KeywordReserved: ansi.StylePrimitive{
-						Color: stringPtr(charmtone.Pony.Hex()),
+						Color: stringPtr(ToHex(t.MdKeywordAlt)),
 					},
 					KeywordNamespace: ansi.StylePrimitive{
-						Color: stringPtr(charmtone.Pony.Hex()),
+						Color: stringPtr(ToHex(t.MdKeywordAlt)),
 					},
 					KeywordType: ansi.StylePrimitive{
-						Color: stringPtr(charmtone.Guppy.Hex()),
+						Color: stringPtr(ToHex(t.MdKeywordType)),
 					},
 					Operator: ansi.StylePrimitive{
-						Color: stringPtr(charmtone.Salmon.Hex()),
+						Color: stringPtr(ToHex(t.MdOperator)),
 					},
 					Punctuation: ansi.StylePrimitive{
-						Color: stringPtr(charmtone.Zest.Hex()),
+						Color: stringPtr(ToHex(t.MdPunctuation)),
 					},
 					Name: ansi.StylePrimitive{
-						Color: stringPtr(charmtone.Smoke.Hex()),
+						Color: stringPtr(ToHex(t.MdName)),
 					},
 					NameBuiltin: ansi.StylePrimitive{
-						Color: stringPtr(charmtone.Cheeky.Hex()),
+						Color: stringPtr(ToHex(t.MdNameBuiltin)),
 					},
 					NameTag: ansi.StylePrimitive{
-						Color: stringPtr(charmtone.Mauve.Hex()),
+						Color: stringPtr(ToHex(t.MdNameTag)),
 					},
 					NameAttribute: ansi.StylePrimitive{
-						Color: stringPtr(charmtone.Hazy.Hex()),
+						Color: stringPtr(ToHex(t.MdNameAttr)),
 					},
 					NameClass: ansi.StylePrimitive{
-						Color:     stringPtr(charmtone.Salt.Hex()),
+						Color:     stringPtr(ToHex(t.MdNameClass)),
 						Underline: boolPtr(true),
 						Bold:      boolPtr(true),
 					},
 					NameDecorator: ansi.StylePrimitive{
-						Color: stringPtr(charmtone.Citron.Hex()),
+						Color: stringPtr(ToHex(t.MdNameDecorator)),
 					},
 					NameFunction: ansi.StylePrimitive{
-						Color: stringPtr(charmtone.Guac.Hex()),
+						Color: stringPtr(ToHex(t.MdNameFunc)),
 					},
 					LiteralNumber: ansi.StylePrimitive{
-						Color: stringPtr(charmtone.Julep.Hex()),
+						Color: stringPtr(ToHex(t.MdNumber)),
 					},
 					LiteralString: ansi.StylePrimitive{
-						Color: stringPtr(charmtone.Cumin.Hex()),
+						Color: stringPtr(ToHex(t.MdString)),
 					},
 					LiteralStringEscape: ansi.StylePrimitive{
-						Color: stringPtr(charmtone.Bok.Hex()),
+						Color: stringPtr(ToHex(t.MdStringEscape)),
 					},
 					GenericDeleted: ansi.StylePrimitive{
-						Color: stringPtr(charmtone.Coral.Hex()),
+						Color: stringPtr(ToHex(t.MdDeleted)),
 					},
 					GenericEmph: ansi.StylePrimitive{
 						Italic: boolPtr(true),
 					},
 					GenericInserted: ansi.StylePrimitive{
-						Color: stringPtr(charmtone.Guac.Hex()),
+						Color: stringPtr(ToHex(t.MdInserted)),
 					},
 					GenericStrong: ansi.StylePrimitive{
 						Bold: boolPtr(true),
 					},
 					GenericSubheading: ansi.StylePrimitive{
-						Color: stringPtr(charmtone.Squid.Hex()),
+						Color: stringPtr(ToHex(t.MdSubheading)),
 					},
 					Background: ansi.StylePrimitive{
-						BackgroundColor: stringPtr(charmtone.Charcoal.Hex()),
+						BackgroundColor: stringPtr(ToHex(t.MdCodeBlockBg)),
 					},
 				},
 			},
@@ -460,23 +505,23 @@ func (t *Theme) buildStyles() *Styles {
 			},
 			InsertLine: diffview.LineStyle{
 				LineNumber: lipgloss.NewStyle().
-					Foreground(lipgloss.Color("#629657")).
-					Background(lipgloss.Color("#2b322a")),
+					Foreground(t.DiffInsertFg).
+					Background(t.DiffInsertBg),
 				Symbol: lipgloss.NewStyle().
-					Foreground(lipgloss.Color("#629657")).
-					Background(lipgloss.Color("#323931")),
+					Foreground(t.DiffInsertFg).
+					Background(t.DiffInsertSymBg),
 				Code: lipgloss.NewStyle().
-					Background(lipgloss.Color("#323931")),
+					Background(t.DiffInsertCodeBg),
 			},
 			DeleteLine: diffview.LineStyle{
 				LineNumber: lipgloss.NewStyle().
-					Foreground(lipgloss.Color("#a45c59")).
-					Background(lipgloss.Color("#312929")),
+					Foreground(t.DiffDeleteFg).
+					Background(t.DiffDeleteBg),
 				Symbol: lipgloss.NewStyle().
-					Foreground(lipgloss.Color("#a45c59")).
-					Background(lipgloss.Color("#383030")),
+					Foreground(t.DiffDeleteFg).
+					Background(t.DiffDeleteSymBg),
 				Code: lipgloss.NewStyle().
-					Background(lipgloss.Color("#383030")),
+					Background(t.DiffDeleteCodeBg),
 			},
 		},
 		FilePicker: filepicker.Styles{
@@ -525,9 +570,11 @@ func NewManager() *Manager {
 		themes: make(map[string]*Theme),
 	}
 
-	t := NewCharmtoneTheme() // default theme
-	m.Register(t)
-	m.current = m.themes[t.Name]
+	dark := NewCharmtoneTheme()
+	light := NewLightTheme()
+	m.Register(dark)
+	m.Register(light)
+	m.current = dark // default to dark theme
 
 	return m
 }
@@ -563,6 +610,12 @@ func ParseHex(hex string) color.Color {
 	return color.RGBA{R: r, G: g, B: b, A: 255}
 }
 
+// ToHex converts a color.Color to hex string.
+func ToHex(c color.Color) string {
+	r, g, b, _ := c.RGBA()
+	return fmt.Sprintf("#%02X%02X%02X", r>>8, g>>8, b>>8)
+}
+
 // Alpha returns a color with transparency
 func Alpha(c color.Color, alpha uint8) color.Color {
 	r, g, b, _ := c.RGBA()