Includes getting started guide, hiccup views reference, full API documentation, and annotated example walkthroughs with ASCII output examples. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
11 KiB
Hiccup Views
Clojure TUI uses Hiccup-style syntax for declarative UI definitions. Views are pure functions that return nested data structures representing the UI.
Basic Syntax
Views use vectors with keywords as element tags:
[:element-type {attributes} children...]
Examples:
[:text "Hello"] ;; Simple text
[:text {:fg :red} "Error"] ;; Text with attributes
[:col [:text "Line 1"] [:text "Line 2"]] ;; Nested elements
Elements
:text - Styled Text
The basic building block for displaying text.
;; Simple text
[:text "Hello, World!"]
;; Styled text
[:text {:fg :cyan :bold true} "Important"]
;; Multiple style attributes
[:text {:fg :white :bg :red :bold true :underline true} "Alert!"]
Output:
Hello, World!
Important
Alert!
Attributes:
| Attribute | Type | Description |
|---|---|---|
:fg |
keyword/int | Foreground color |
:bg |
keyword/int | Background color |
:bold |
boolean | Bold text |
:dim |
boolean | Dimmed text |
:italic |
boolean | Italic text |
:underline |
boolean | Underlined text |
:inverse |
boolean | Swap fg/bg colors |
:strike |
boolean | Strikethrough text |
:row - Horizontal Layout
Arranges children horizontally (left to right).
;; Basic row
[:row "Left" "Middle" "Right"]
;; Row with gap
[:row {:gap 2} "A" "B" "C"]
;; Nested elements in row
[:row
[:text {:fg :green} "Status:"]
[:text {:bold true} "OK"]]
Output:
LeftMiddleRight
A B C
Status:OK
Attributes:
| Attribute | Type | Description |
|---|---|---|
:gap |
integer | Spaces between children (default: 0) |
:col - Vertical Layout
Arranges children vertically (top to bottom).
;; Basic column
[:col "Line 1" "Line 2" "Line 3"]
;; Column with gap
[:col {:gap 1}
[:text "Section 1"]
[:text "Section 2"]]
;; Nested layouts
[:col
[:text {:bold true} "Header"]
[:row "Col A" "Col B" "Col C"]
[:text {:fg :gray} "Footer"]]
Output:
Line 1
Line 2
Line 3
Section 1
Section 2
Header
Col ACol BCol C
Footer
Attributes:
| Attribute | Type | Description |
|---|---|---|
:gap |
integer | Blank lines between children (default: 0) |
:box - Bordered Container
Wraps content in a bordered box.
;; Simple box
[:box "Content"]
;; Box with title
[:box {:title "Settings"} "Options go here"]
;; Box with padding
[:box {:padding 1} "Padded content"]
;; Box with custom border style
[:box {:border :double} "Important!"]
Output:
╭─────────╮
│Content │
╰─────────╯
╭─Settings─╮
│Options go here│
╰──────────╯
╭──────────────────╮
│ │
│ Padded content │
│ │
╰──────────────────╯
╔═══════════╗
║Important! ║
╚═══════════╝
Attributes:
| Attribute | Type | Description |
|---|---|---|
:border |
keyword | Border style (see below) |
:title |
string | Title in top border |
:padding |
int/vec | Inner padding (see below) |
:width |
integer | Fixed width |
Border Styles:
| Style | Characters | Example |
|---|---|---|
:rounded |
╭╮╰╯─│ |
╭───╮ (default) |
:single |
┌┐└┘─│ |
┌───┐ |
:double |
╔╗╚╝═║ |
╔═══╗ |
:heavy |
┏┓┗┛━┃ |
┏━━━┓ |
:ascii |
++--| |
+---+ |
Padding:
;; All sides
[:box {:padding 2} "Content"]
;; Vertical and horizontal [v h]
[:box {:padding [1 2]} "Content"]
;; Individual [top right bottom left]
[:box {:padding [1 2 1 2]} "Content"]
:space - Empty Space
Creates empty space for layout purposes.
;; Default 1x1 space
[:space]
;; Horizontal space
[:row "Left" [:space {:width 10}] "Right"]
;; Vertical space
[:col "Top" [:space {:height 3}] "Bottom"]
Output:
Left Right
Top
Bottom
Attributes:
| Attribute | Type | Description |
|---|---|---|
:width |
integer | Width in characters (default: 1) |
:height |
integer | Height in lines (default: 1) |
Colors
Named Colors
Basic 16-color palette supported by all terminals:
| Color | Keyword | Bright Version |
|---|---|---|
| Black | :black |
:bright-black |
| Red | :red |
:bright-red |
| Green | :green |
:bright-green |
| Yellow | :yellow |
:bright-yellow |
| Blue | :blue |
:bright-blue |
| Magenta | :magenta |
:bright-magenta |
| Cyan | :cyan |
:bright-cyan |
| White | :white |
:bright-white |
| Default | :default |
- |
Aliases: :gray and :grey map to :bright-black
[:text {:fg :red} "Error"]
[:text {:fg :bright-green} "Success"]
[:text {:bg :blue :fg :white} "Highlighted"]
256 Colors
Use integers 0-255 for extended color support:
[:text {:fg 208} "Orange (256-color)"]
[:text {:bg 236} "Dark gray background"]
Color ranges:
- 0-7: Standard colors
- 8-15: Bright colors
- 16-231: 6x6x6 color cube
- 232-255: Grayscale (dark to light)
True Color (24-bit)
For true color, use the tui.ansi namespace directly:
(require '[tui.ansi :as ansi])
[:text (ansi/fg-rgb 255 128 0 "Orange text")]
[:text (ansi/bg-rgb 30 30 30 "Dark background")]
Text Styles
Combine multiple styles:
[:text {:bold true :underline true} "Bold and underlined"]
[:text {:fg :red :bold true :inverse true} "Inverted error"]
[:text {:dim true :italic true} "Subtle italic"]
Available styles:
| Style | Attribute | Description |
|---|---|---|
| Bold | :bold true |
Heavier font weight |
| Dim | :dim true |
Lighter/faded text |
| Italic | :italic true |
Slanted text |
| Underline | :underline true |
Line under text |
| Inverse | :inverse true |
Swap foreground/background |
| Strikethrough | :strike true |
Line through text |
Layout Examples
Two-Column Layout
[:row {:gap 4}
[:col
[:text {:bold true} "Left Column"]
[:text "Item 1"]
[:text "Item 2"]]
[:col
[:text {:bold true} "Right Column"]
[:text "Item A"]
[:text "Item B"]]]
Output:
Left Column Right Column
Item 1 Item A
Item 2 Item B
Nested Boxes
[:box {:title "Outer" :padding 1}
[:row {:gap 2}
[:box {:border :single :title "Box A"}
[:text "Content A"]]
[:box {:border :single :title "Box B"}
[:text "Content B"]]]]
Output:
╭─Outer────────────────────────────╮
│ │
│ ┌─Box A─────┐ ┌─Box B─────┐ │
│ │Content A │ │Content B │ │
│ └───────────┘ └───────────┘ │
│ │
╰──────────────────────────────────╯
Status Bar
[:col
[:box {:border :rounded :padding [0 1]}
[:text {:bold true} "My Application"]]
[:space {:height 1}]
[:text "Main content here..."]
[:space {:height 1}]
[:row {:gap 2}
[:text {:fg :gray} "Status: Ready"]
[:text {:fg :gray} "|"]
[:text {:fg :gray} "Press q to quit"]]]
Output:
╭──────────────────╮
│ My Application │
╰──────────────────╯
Main content here...
Status: Ready | Press q to quit
Menu with Selection
(defn menu-item [label selected?]
[:row
[:text (if selected? "> " " ")]
[:text {:fg (if selected? :cyan :white)
:bold selected?}
label]])
(defn view [{:keys [items cursor]}]
[:col
[:text {:bold true} "Select an option:"]
[:space {:height 1}]
[:col {:gap 0}
(for [[idx item] (map-indexed vector items)]
(menu-item item (= idx cursor)))]])
Output (cursor on second item):
Select an option:
First Option
> Second Option
Third Option
Progress Indicator
(defn progress-bar [percent width]
(let [filled (int (* width (/ percent 100)))
empty (- width filled)]
[:row
[:text "["]
[:text {:fg :green} (apply str (repeat filled "="))]
[:text {:fg :gray} (apply str (repeat empty "-"))]
[:text "]"]
[:text " "]
[:text (str percent "%")]]))
(defn view [{:keys [progress]}]
[:col
[:text "Downloading..."]
(progress-bar progress 20)])
Output (at 65%):
Downloading...
[=============-------] 65%
Helper Functions
The tui.render namespace provides helper functions:
(require '[tui.render :refer [text row col box]])
;; These are equivalent:
[:text {:fg :red} "Error"]
(text {:fg :red} "Error")
[:row {:gap 2} "A" "B"]
(row {:gap 2} "A" "B")
[:col [:text "Line 1"] [:text "Line 2"]]
(col (text "Line 1") (text "Line 2"))
[:box {:title "Info"} "Content"]
(box {:title "Info"} "Content")
Conditional Rendering
Use standard Clojure conditionals:
(defn view [{:keys [loading? error data]}]
[:col
[:text {:bold true} "Status"]
(cond
loading?
[:text {:fg :yellow} "Loading..."]
error
[:text {:fg :red} (str "Error: " error)]
:else
[:text {:fg :green} (str "Data: " data)])])
Dynamic Views with for
Generate repeated elements:
(defn view [{:keys [items selected]}]
[:col
(for [[idx item] (map-indexed vector items)]
[:row
[:text (if (= idx selected) "> " " ")]
[:text {:fg (if (= idx selected) :cyan :white)} item]])])
String Shortcuts
Plain strings are automatically wrapped in :text:
;; These are equivalent:
[:col "Line 1" "Line 2"]
[:col [:text "Line 1"] [:text "Line 2"]]
;; In rows too:
[:row "A" "B" "C"]
[:row [:text "A"] [:text "B"] [:text "C"]]
Common Patterns
Styled Labels
(defn label [text]
[:text {:fg :gray} (str text ": ")])
(defn value [text]
[:text {:fg :white :bold true} text])
[:row (label "Name") (value "John")]
Conditional Styling
(defn status-text [status]
[:text {:fg (case status
:ok :green
:warning :yellow
:error :red
:white)
:bold (= status :error)}
(name status)])
Reusable Components
(defn card [{:keys [title]} & children]
[:box {:border :rounded :title title :padding [0 1]}
(into [:col] children)])
;; Usage
(card {:title "User Info"}
[:row (label "Name") (value "Alice")]
[:row (label "Email") (value "alice@example.com")])
Next Steps
- API Reference - Complete API documentation
- Examples - Full example applications