clj-string-layout turns rows of strings into aligned text — Markdown
tables, box-drawing terminal output, CSV, HTML, psql, Org mode, and
anything you can build out of the underlying layout DSL.
The library is a stack. Pick where you start; drop a level when the one above doesn't reach.
┌─ HIGH ────────────────────────────────────────────────────────────────┐
│ clj-string-layout.table │
│ (table/table {:format :box :columns [...] :rows [...]}) │
│ → map rows, column specs, formatters, headers, footers, captions │
├───────────────────────────────────────────────────────────────────────┤
│ clj-string-layout.core/layout │
│ (layout rows {:layout {:cols ["[L] [R]"]}}) │
│ → the layout DSL itself. │
│ Pre-canned configs live in clj-string-layout.presets. │
└─ LOW ─────────────────────────────────────────────────────────────────┘
clj-string-layout.table — the high-level table API. Named
formats (:markdown, :box, :csv, :html, …) with column specs,
per-cell formatters, headers, footers, captions, overflow policies,
and per-format escaping. Most callers start (and stop) here. See
the table API guide and
examples gallery.clj-string-layout.core/layout — the layout DSL itself. Column
markers ([L] [C] [R] [V]), fill regions (f), repeat
groups ({…}), virtual row layouts. Drop down here when you need
shapes the table API doesn't cover. See the
layout language reference and
recipe book.clj-string-layout.presets is a catalog of ready-made layout
configs (Markdown, box-drawing, CSV, HTML, ASCII grid, psql, Org,
RST, …) you can hand straight to layout without writing your own
config map. They're shortcuts into the DSL layer, not a separate
abstraction. See the preset catalog.Cross-cutting docs: CLI guide, errors reference.
;; deps.edn
{:deps {io.github.mbjarland/clj-string-layout {:mvn/version "2.0.2"}}}
The library has no third-party Clojure runtime dependencies. A Babashka script can require it directly without a pod and without JVM startup cost:
#!/usr/bin/env bb
(require '[babashka.deps :as deps])
(deps/add-deps '{:deps {io.github.mbjarland/clj-string-layout
{:mvn/version "2.0.2"}}})
(require '[clj-string-layout.table :as table])
(println (table/table-str {:format :box
:headers ["Name" "Qty"]
:rows [["apple" 12] ["pear" 4]]}))
Tested on Java 11, 17, and 21. Versions before 1.0.4 used the older
com.github.mbjarland Maven group; use io.github.mbjarland for new
installs.
Describe the data once:
(require '[clj-string-layout.table :as table])
(def items
[{:item "apple" :qty 12 :price 1.50}
{:item "pear" :qty 4 :price 2.00}
{:item "kiwi" :qty 8 :price 0.75}])
(def cols
[:item
{:from :qty :as "Qty" :align :right}
{:from :price :as "Price" :align :right
:formatter #(format "$%.2f" %)}])
Render it any way you need.
(table/table-str {:format :box :columns cols :rows items})
┌───────┬─────┬───────┐
│ item │ Qty │ Price │
├───────┼─────┼───────┤
│ apple │ 12 │ $1.50 │
├───────┼─────┼───────┤
│ pear │ 4 │ $2.00 │
├───────┼─────┼───────┤
│ kiwi │ 8 │ $0.75 │
└───────┴─────┴───────┘
(table/table-str {:format :markdown :columns cols :rows items})
| item | Qty | Price |
|:----- | ---:| -----:|
| apple | 12 | $1.50 |
| pear | 4 | $2.00 |
| kiwi | 8 | $0.75 |
(table/table-str {:format :html :columns cols :rows items})
<table>
<tr><th>item</th><th>Qty</th><th>Price</th></tr>
<tr><td>apple</td><td>12</td><td>$1.50</td></tr>
<tr><td>pear</td><td>4</td><td>$2.00</td></tr>
<tr><td>kiwi</td><td>8</td><td>$0.75</td></tr>
</table>
(table/table-str {:format :csv :columns cols :rows items})
item,Qty,Price
apple,12,$1.50
pear,4,$2.00
kiwi,8,$0.75
The same cols and items also render as :double-box, :ascii-grid,
:psql, :org, :rst, :tsv, :pipe, :plain, and the three
alignment-specific :markdown-* variants. The
examples gallery shows every named format
side by side with the same data.
Four moving parts.
A vector of maps:
:rows [{:item "apple" :qty 12} {:item "pear" :qty 4}]
…or a vector of positional vectors:
:rows [["apple" 12] ["pear" 4]]
Two shapes:
:qty ;; defaults
{:from :qty :as "Qty" :align :right ;; explicit
:formatter #(format "%s units" %)
:width 10 :overflow :ellipsis}
Map keys:
| Key | Means |
|---|---|
:from | Row map key. Omit for vector rows — the column's source is its position. |
:as | Header label. Defaults to the keyword name of :from. |
:align | :left, :center, :right, or :verbatim. |
:formatter | One-arg function applied to the cell value before rendering. |
:width | Maximum cell width. |
:overflow | :none, :clip, :ellipsis, :wrap, :error. |
A keyword. One of:
:plain · :markdown · :markdown-left · :markdown-center ·
:markdown-right · :box · :double-box · :ascii-grid · :csv ·
:tsv · :pipe · :psql · :org · :rst · :html.
| Key | Adds |
|---|---|
:title | Centered caption above text formats; <caption> for HTML. |
:footers | Trailing rows that share column treatment. |
:cell-fn | Per-cell decoration callback — pair with :display-width for ANSI styling. |
:width + :fill? | Expand the rendered table toward a target width. |
:raw? | Return vectors of pieces instead of joined strings. |
See the table API guide for the full surface.
When the named formats don't reach, drop down to the layout DSL. The
column layout is a string made of column markers ([L] [C] [R]
[V]), fill regions (f), and optional repeat groups ({…}):
(require '[clj-string-layout.core :refer [layout]])
(layout [["left" "right"]]
{:width 30
:fill-char \.
:layout {:cols ["[L]f[R]"]}})
;; => ["left.....................right"]
The string above has two column markers ([L] and [R]) and one fill
region (f) between them — extra width is absorbed by the fill so the
right-hand value sits flush against the target width. The DSL also
handles virtual row layouts (top/middle/bottom rules), repeat groups
for variable column counts, raw-piece output for cell decoration, and
streaming via layout-seq. See the
layout language reference for the grammar
and the recipe book for paste-able examples.
clojure -M:cli -- --input csv --format markdown --headers data.csv
bb bb-format --input tsv --format box < data.tsv
bb bb-format runs natively under Babashka in ~50 ms instead of the
JVM path's ~700 ms. See the CLI guide for the full
option list and the programmatic cli/render entry point.
Local checks, code expectations, and the release process live in CONTRIBUTING.md. Documentation is rebuilt on every release at cljdoc.org.
Copyright © 2017-2026 Matias Bjarland · Eclipse Public License 1.0
Can you improve this documentation?Edit on GitHub
cljdoc builds & hosts documentation for Clojure/Script libraries
| Ctrl+k | Jump to recent docs |
| ← | Move to previous article |
| → | Move to next article |
| Ctrl+/ | Jump to the search field |