1---2title: "UX and visual bar"3sidebar_position: 44---56# UX and visual bar78Features don't merge unless they meet this bar. Visual quality is not a9follow-up task.1011## Principles12131. **Inside the grid, behave like Google Sheets.** Assume users know14 Sheets, not 1-2-3. Typing replaces a cell, `=` starts a formula, Enter15 commits and moves down (back to the starting column after Tabs), Tab16 moves right, Shift+arrows and the mouse select, Ctrl+arrows jump to the17 data edge, and Sheets shortcuts do what Sheets does. When Sheets and18 1-2-3 disagree, Sheets wins. 1-2-3 lives on as the visual identity:19 the control panel doubling as the formula bar, the mode indicator, the20 crisp character grid.2122 Both are the seed and the design pattern, not a spec. Match them23 where users' habits depend on it (keys, entry, formula syntax and24 results people rely on); elsewhere do what serves a terminal25 spreadsheet best, and say so in the docs where 012 differs on purpose.26 "Sheets does it differently" is a reason to look, not a bug by itself.272. **Familiar, but native to the terminal.** Sheets tells us which jobs28 users expect to get done and which keys they reach for, not what the29 screen must look like. This is a Bubble Tea program: prefer patterns30 that feel at home in a terminal when they serve the same need as well31 or better. Bars and prompts on the context line or status line over32 floating dialogs, incremental search that highlights as you type,33 toggles shown as key chips, pickers in the style of fzf or lazygit, and34 no fake GUI chrome (drop shadows, faux buttons) unless it earns its35 place. Overlays are fine when content genuinely needs room (menus,36 long lists), and they stay keyboard-first.373. **Everything is discoverable.** Every action is a registered command38 (`internal/ui/commands.go`) with a title and one-line description. It is39 reachable from the menu where Sheets would put it, from key bindings in40 `keymap`, and from help (generated from the registry). The command41 palette lists every registered command with its keys.424. **The screen always says what's going on.** The mode indicator is always43 correct. The control panel is the menu bar (with the mode indicator),44 the formula bar, and the context line, which holds prompts, small45 confirmations and the keys that apply in any non-READY state. While a46 menu, the palette or the shortcuts are open, the status line says what47 the highlighted item does and which keys apply.485. **Never lose work.** Destructive actions warn when there are unsaved49 changes and are undoable once undo exists. Errors are specific50 ("expected , or )" with the cursor on the spot), never generic.516. **Esc backs out one level; Enter confirms.** Everywhere, no exceptions.5253## Visual rules5455The palette over a sheet shows most of them at once: bars that fill56the width, an overlay framed in light lines with its title in the top57border and a count in the bottom one, keys drawn as chips, and the58status line saying what the highlighted item does.5960616263- Style only through `theme` roles (`internal/ui/theme`). No inline64 `lipgloss.NewStyle()` in views. Need a new role? Add it to `theme` with a65 comment saying what it's for, in both the dark and light variants.66- Use the 16 ANSI colors so the user's terminal palette applies. No67 hard-coded RGB. Color schemes ([Themes](../terminal/themes.md)) are data: they68 map the same roles to their colors and correct contrast, and69 `TestEveryThemeReadable` must pass for a new role.70- Bars (menu bar, formula bar, context line, column headers, status line)71 get their background from their row role and must fill the full width,72 at odd widths and under overlays; draw them with `theme.Fill`.73- Text on a colored background needs about 4.5:1 contrast against both74 reference palettes (`e2e/palette_test.go`). Check it in the gallery.75- Never rely on color alone: every state also shows as text, a glyph or76 an attribute, listed in [Reading without color](#reading-without-color).77- Numbers right-aligned, text left, booleans and errors centered, one78 column of padding, text overflowing into empty neighbors: as Sheets.79 Headers centered. Menu bar titles separated by two spaces. Keys shown80 as `F2`, `Ctrl+Z`, `Del` (see `theme.KeyLabel`), drawn as key chips81 (`Theme.Chip`) in hints, menus, the palette and the shortcuts.82- Menus, the palette and the shortcuts are overlays: boxes framed with83 light box-drawing lines (`Theme.Frame`), composited over the grid without84 moving it, with a title in the top border and a position or count in85 the bottom one. Small questions (quit with unsaved changes) go on the86 context line as a choice bar, not in a box.87- The active cell is always distinct from the rest of a selection, and88 the headers of selected rows and columns are highlighted.89- Layout works from 60x16 up to very wide terminals. Nothing jumps90 position between frames; overlays don't shift the grid.91- Render only what's visible. A keystroke on a 10,000-cell sheet must feel92 instant.9394## Reading without color9596Each state has a cue that survives a terminal without color, or a reader97who can't tell the colors apart. `internal/ui/monochrome_test.go` draws98each with `theme.Monochrome` (every role in one gray) and checks the cue99is in the cell's text or attributes; a new state gets a cue and a case100there.101102| State | Cue without color |103|---|---|104| The pointer (active cell), the selection, their row and column headers | Reverse video; the active cell's headers are also bold, and the name box names the cell or range |105| Errors | The error's text (`#DIV/0!`) with a curly underline, explained on the context line |106| Warnings | Text on the context line or status line (`Invalid: ...`, `Circular reference`) |107| Entries failing validation | A dotted underline, and `Invalid:` with the rule on the context line |108| Values an array spilled | Italic, and `Spilled from B2` on the context line |109| Linked files' rows, following, paused or failing | Italic; `●`, `‖` or `!` beside the tab's name and on the context line, with the state in words (`Following`, `Paused`, the error) |110| A linked source's tab, its scrollbar, rows on their way | `▦` before the tab's name and on the context line, `!` after it when the file can't be read; the scrollbar's thumb `┃` on a track of `│`; `…` in place of a row being read |111| A notebook's cells and outputs | Marks and words, listed in [Notebooks](../nushell/notebooks.md#reading-a-cell): the bar left of the active cell, a heavy box while it's edited, the prompt's and the box's words for its state, `×` before an error, `OUTPUT` in an output's grid, an output sent to a sheet in italic |112| A table's header row | Bold and underlined, when the table styles its header; `Table Sales, column Amount` on the context line |113| Pivot table results | Their headings (`SUM of Units`, `Grand Total`), and a note in words when an edit is refused |114| Protected ranges | A question in words before an edit (`A1:B2 is protected.`) |115| Notes | A `▝` in the cell's top-right corner |116| Others in a shared workbook (012 serve) | Their pointer's cell double-underlined, their initial on its row's header, their name on the status line, `▘` in a cell they changed in the last 30 seconds, and `Changed by`, `is here` or their warnings in words on the context line |117| Agents in the workbook (live mode) | A `◆` for their initial on the row header and before their name on the status line, `agent` in Who's here; a `◇` in a cell their suggestion would set, what they suggest in words on the context line, and the count on the status line |118| Macro recording | `REC` beside the mode indicator |119| Mode | The mode indicator's word (`READY`, `ENTER`, `POINT`, `MENU`, on a notebook `NOTEBOOK`, `EDIT` or `OUTPUT`, and on a linked source's tab `SOURCE`) |120| Search matches, traced cells | Reverse video, with a count or the list on the context line; dependents also bold |121| Evaluate formula | The part computed next underlined and bold, values in its place italic, and `Next` and `Value` in words |122| The copied range | A dashed underline |123| Dropdowns, checkboxes, active filters | `▾`, or a chip in reverse video between `▐` and `▌`; `[ ]` and `[✓]`; `▼` instead of `▾` |124| Data bars, icon sets | Eighth blocks as long as the number, and reverse video under the text they run beneath; the icon's glyph (`↑`, `◑`, `✓`, `▆`) |125| Borders (of any color), wrapped text, merged cells, tall rows, vertical alignment | Characters: box-drawing lines, the text's own lines, one value across the merge, the row's number on its last line, the value on the line it's aligned to; the pointer on a merged cell reverses all of it |126127The reverse-video roles (`Theme.standouts`) keep their look in color:128their colors are stored swapped, so SGR 7 swaps them back. Conditional129formats, color scales and border colors are colors the user chose, and stay130colors.131132## Required with every user-facing change133134- [ ] Registered command(s) with title and description; key binding in135 `keymap` if Sheets has one; menu entry where Sheets would have it.136- [ ] Unit tests in `internal/ui` driving `Update`, plus engine tests.137- [ ] An e2e test in `e2e/` for the main flow.138- [ ] Golden screen(s) in `e2e/screenlist_test.go` for every new visual state,139 with a `-light` variant if it introduces new colors.140- [ ] `make screens`, then look at `e2e/testdata/screens/gallery.html`141 (serve it over HTTP and screenshot it, or open it in a browser).142 Fix anything misaligned, low-contrast, cramped or clipped before143 committing. Screens are drawn from libghostty's cell grid, so they144 match what a terminal shows.145- [ ] The feature's doc updated in place, where a reader looks for it,146 and its keys in [Keys and mouse](../reference/keys.md) (tests check every bound key is147 there); a headline feature gets a tape in `demos/` and a place in148 the README ([Testing](testing.md#demo-recordings)).149150## Review checklist for screenshots151152- Is the mode indicator right? Does line 3 explain the keys?153- Is anything clipped at 60 columns?154- Can every piece of text be read in both palettes?155- Does the new element look like it belongs: same spacing, same roles,156 same capitalization as the rest?