design-system.md

  1# Design System from Crush
  2
  3The visual language of Crush, extracted from `internal/ui/styles/styles.go` and component rendering code.
  4
  5## Table of Contents
  6
  71. [Charmtone Color Palette](#charmtone-color-palette)
  82. [Semantic Color Roles](#semantic-color-roles)
  93. [The Styles Struct](#the-styles-struct)
 104. [Icon System](#icon-system)
 115. [Typography Patterns](#typography-patterns)
 126. [Layout Constants](#layout-constants)
 137. [Gradient Effects](#gradient-effects)
 14
 15## Charmtone Color Palette
 16
 17**Source:** `github.com/charmbracelet/x/exp/charmtone`
 18
 19Crush uses Charm's proprietary color palette "Charmtone" with food-themed names. These are true-color values designed to look good on dark terminal backgrounds.
 20
 21| Role | Charmtone Name | Usage |
 22|------|---------------|-------|
 23| Primary | `Charple` | Focus borders, highlights, accent |
 24| Secondary | `Dolly` | Cursor color, secondary highlights |
 25| Tertiary | `Bok` | Editor prompt, tertiary accent |
 26| Background | `Pepper` | Main background |
 27| Background lighter | `BBQ` | Slightly lighter bg for contrast |
 28| Background subtle | `Charcoal` | Borders, separators |
 29| Background overlay | `Iron` | Dialog overlays |
 30| Foreground | `Ash` | Primary text |
 31| Foreground muted | `Squid` | Secondary text |
 32| Foreground half-muted | `Smoke` | Between muted and base |
 33| Foreground subtle | `Oyster` | Placeholders, hints |
 34| Error | `Sriracha` | Error states |
 35| Warning | `Zest` | Warning states |
 36| Info | `Malibu` | Info states, blue accents |
 37| White | `Butter` | High-contrast text |
 38| Blue | `Malibu` | Tool names, links |
 39| Blue light | `Sardine` | Light blue accents |
 40| Blue dark | `Damson` | Dark blue accents |
 41| Green | `Julep` | Success states |
 42| Green light | `Bok` | Light green accents |
 43| Green dark | `Guac` | Pending job icons |
 44| Red | `Coral` | Error text |
 45| Red dark | `Sriracha` | Error backgrounds |
 46| Yellow | `Mustard` | Warning text, note tags |
 47
 48## Semantic Color Roles
 49
 50**Source:** `internal/ui/styles/styles.go`
 51
 52Colors are organized by semantic role, not by hue:
 53
 54```go
 55var (
 56    primary   = charmtone.Charple   // "brand" color
 57    secondary = charmtone.Dolly     // cursor, secondary accent
 58    tertiary  = charmtone.Bok       // editor prompt
 59
 60    bgBase        = charmtone.Pepper    // app background
 61    bgBaseLighter = charmtone.BBQ       // content blocks
 62    bgSubtle      = charmtone.Charcoal  // borders
 63    bgOverlay     = charmtone.Iron      // dialog bg
 64
 65    fgBase      = charmtone.Ash     // text
 66    fgMuted     = charmtone.Squid   // secondary text
 67    fgHalfMuted = charmtone.Smoke   // in-between
 68    fgSubtle    = charmtone.Oyster  // hints
 69
 70    border      = charmtone.Charcoal  // unfocused borders
 71    borderFocus = charmtone.Charple   // focused borders
 72
 73    error   = charmtone.Sriracha
 74    warning = charmtone.Zest
 75    info    = charmtone.Malibu
 76)
 77```
 78
 79Every component references these semantic colors rather than hard-coding values. This makes theming possible by swapping the palette.
 80
 81## The Styles Struct
 82
 83**Source:** `internal/ui/styles/styles.go`
 84
 85The `Styles` struct is a massive (~500 fields) centralized style registry. It's organized in nested groups:
 86
 87```go
 88type Styles struct {
 89    // Base text
 90    Base, Muted, HalfMuted, Subtle lipgloss.Style
 91
 92    // Tags
 93    TagBase, TagError, TagInfo lipgloss.Style
 94
 95    // Semantic colors (as color.Color for components that need raw colors)
 96    Primary, Secondary, Tertiary color.Color
 97    BgBase, BgSubtle, BgOverlay  color.Color
 98    FgBase, FgMuted, FgSubtle    color.Color
 99    Error, Warning, Info         color.Color
100
101    // Nested groups
102    Header struct { Charm, Diagonals, Percentage, Keystroke, WorkingDir lipgloss.Style }
103    Chat   struct { Message struct { UserFocused, AssistantFocused, Thinking... } }
104    Tool   struct { IconPending, IconSuccess, NameNormal, ContentLine, Body... }
105    Dialog struct { Title, View, Help... }
106    Pills  struct { ... }
107
108    // Component-specific styles
109    TextInput textinput.Styles
110    TextArea  textarea.Styles
111    Help      help.Styles
112    Diff      diffview.Style
113    FilePicker filepicker.Styles
114
115    // Markdown rendering config
116    Markdown      ansi.StyleConfig
117    PlainMarkdown ansi.StyleConfig
118}
119```
120
121Styles are created once in `DefaultStyles()` and passed via `common.Common`:
122
123```go
124type Common struct {
125    App    *app.App
126    Styles *styles.Styles
127}
128```
129
130Every component receives `*common.Common` at creation time.
131
132## Icon System
133
134**Source:** `internal/ui/styles/styles.go`
135
136Crush uses Unicode characters as icons, defined as constants:
137
138```go
139const (
140    CheckIcon   = "✓"
141    SpinnerIcon = "⋯"
142    LoadingIcon = "⟳"
143    ModelIcon   = "◇"
144    ArrowRightIcon = "→"
145
146    // Tool status
147    ToolPending = "●"
148    ToolSuccess = "✓"
149    ToolError   = "×"
150
151    // Radio buttons
152    RadioOn  = "◉"
153    RadioOff = "○"
154
155    // Borders
156    BorderThin  = "│"
157    BorderThick = "▌"
158
159    // Separators
160    SectionSeparator = "─"
161
162    // Todos
163    TodoCompletedIcon  = "✓"
164    TodoPendingIcon    = "•"
165    TodoInProgressIcon = "→"
166
167    // Attachments
168    ImageIcon = "■"
169    TextIcon  = "≡"
170
171    // Scrollbar
172    ScrollbarThumb = "┃"
173    ScrollbarTrack = "│"
174
175    // LSP diagnostics
176    LSPErrorIcon   = "E"
177    LSPWarningIcon = "W"
178    LSPInfoIcon    = "I"
179    LSPHintIcon    = "H"
180)
181```
182
183Icons are styled using the corresponding `lipgloss.Style` from the Styles struct. For example:
184
185```go
186s.Tool.IconPending = lipgloss.NewStyle().Foreground(blue)
187s.Tool.IconSuccess = lipgloss.NewStyle().Foreground(green)
188s.Tool.IconError   = lipgloss.NewStyle().Foreground(red)
189```
190
191## Typography Patterns
192
193### Text Hierarchy
194
195| Level | Style | Usage |
196|-------|-------|-------|
197| Primary | `Base` (fgBase/Ash) | Main content, user messages |
198| Secondary | `Muted` (fgMuted/Squid) | Metadata, timestamps, secondary info |
199| Tertiary | `HalfMuted` (fgHalfMuted/Smoke) | Less important metadata |
200| Hint | `Subtle` (fgSubtle/Oyster) | Placeholders, keystroke hints |
201
202### Message Rendering
203
204User messages and assistant messages have distinct focused/blurred styles:
205
206```go
207Chat.Message.UserFocused     // highlight border on left
208Chat.Message.UserBlurred     // muted border on left
209Chat.Message.AssistantFocused
210Chat.Message.AssistantBlurred
211```
212
213Tool calls get three states: focused, blurred, and compact (nested tools):
214
215```go
216Chat.Message.ToolCallFocused  // full detail
217Chat.Message.ToolCallBlurred  // muted
218Chat.Message.ToolCallCompact  // minimal, for nested agent tools
219```
220
221### Tags
222
223Small colored labels for status indicators:
224
225```go
226TagBase  // default tag
227TagError // red background
228TagInfo  // blue/info background
229Tool.ErrorTag    // "ERROR" label
230Tool.NoteTag     // "NOTE" label (yellow bg)
231Tool.AgentTaskTag // "TASK" label (blue bg)
232```
233
234### Section Headers
235
236Used to separate logical sections within tool output:
237
238```go
239Chat.Message.SectionHeader  // styled divider text
240Section.Title               // section title style
241Section.Line                // horizontal line style
242```
243
244## Layout Constants
245
246```go
247// Global
248const defaultMargin     = 2
249const defaultListIndent = 2
250
251// Compact mode breakpoints
252const compactModeWidthBreakpoint  = 120
253const compactModeHeightBreakpoint = 30
254
255// Text
256const maxTextWidth = 120  // max readable width for chat messages
257const MessageLeftPaddingTotal = 2  // border + padding
258
259// Dialogs
260const defaultDialogMaxWidth = 70
261const defaultDialogHeight   = 20
262
263// Permissions dialog
264const diffMaxWidth      = 180
265const simpleMaxWidth    = 100
266const splitModeMinWidth = 140
267
268// Sidebar
269const sidebarWidth = 30
270
271// Editor
272const editorHeight = 5
273
274// Paste threshold
275const pasteLinesThreshold = 10  // lines before treating paste as attachment
276```
277
278## Gradient Effects
279
280**Source:** `internal/ui/styles/grad.go`
281
282Dialog titles use color gradients rendered character-by-character:
283
284```go
285type RenderContext struct {
286    TitleGradientFromColor color.Color  // defaults to Primary
287    TitleGradientToColor   color.Color  // defaults to Secondary
288}
289```
290
291The title text is rendered with a smooth gradient from the primary color (Charple) to the secondary color (Dolly), giving dialogs their distinctive polished look.
292
293The logo also uses gradient colors:
294
295```go
296LogoTitleColorA  color.Color  // gradient start
297LogoTitleColorB  color.Color  // gradient end
298LogoCharmColor   color.Color  // "Charm" text
299LogoVersionColor color.Color  // version number
300```
301
302This creates the signature Charm aesthetic - dark backgrounds with warm, saturated accent gradients.