Files
Adam Jeniski dab0a27e4d add comprehensive documentation for external users
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>
2026-01-21 11:37:16 -05:00

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