Simple, beautiful CLI output for Haskell.
Build declarative and composable sections, trees, tables, dashboards for your Haskell applications. . Zero dependencies, rich text formatting with alignment, underlines, padding, margins. Features lists, trees, tables, charts, banners and more.

layoutz
Simple, beautiful CLI output for Haskell ๐ชถ
Build declarative and composable sections, trees, tables, dashboards for your Haskell applications.
Features
- Zero dependencies, use
Layoutz.hslike a header file - Rich text formatting: alignment, underlines, padding, margins
- Lists, trees, tables, charts, banners...
- Easily create new primitives (no component-library limitations).
Installation
Add Layoutz on Hackage to your project's .cabal file:
build-depends: layoutz
All you need:
import Layoutz
Quickstart
Beautiful, compositional text layouts:
import Layoutz
demo = layout
[ center $ row ["Layoutz", underline' "ห" $ text "DEMO"]
, br
, row
[ statusCard "Users" "1.2K"
, withBorder BorderDouble $ statusCard "API" "UP"
, withBorder BorderThick $ statusCard "CPU" "23%"
, withBorder BorderRound $ table ["Name", "Role", "Status"]
[ ["Alice", "Engineer", "Online"]
, ["Eve", "QA", "Away"]
]
, section "Pugilists" [kv [("Kazushi", "Sakuraba"), ("Jet", "Li")]]
]
]
putStrLn $ render demo
Layoutz DEMO
หหหห
โโโโโโโโโโโ โโโโโโโโโ โโโโโโโโโ โญโโโโโโโโฌโโโโโโโโโโโฌโโโโโโโโโฎ === Pugilists ===
โ Users โ โ API โ โ CPU โ โ Name โ Role โ Status โ Kazushi: Sakuraba
โ 1.2K โ โ UP โ โ 23% โ โโโโโโโโโผโโโโโโโโโโโผโโโโโโโโโค Jet: Li
โโโโโโโโโโโ โโโโโโโโโ โโโโโโโโโ โ Alice โ Engineer โ Online โ
โ Eve โ QA โ Away โ
โฐโโโโโโโโดโโโโโโโโโโโดโโโโโโโโโฏ
Core concepts
- Every piece of content is an
Element - Elements are immutable and composable - build complex layouts by combining simple elements
- A
layoutarranges elements vertically:
layout [elem1, elem2, elem3] -- Joins with "\n"
Call render on any element to get a string
The power comes from uniform composition - since everything has the Element typeclass, everything can be combined.
String Literals
With OverloadedStrings enabled, you can use string literals directly:
layout ["Hello", "World"] -- Instead of layout [text "Hello", text "World"]
Note: When passing to functions that take polymorphic Element a parameters (like underline', center', pad), use text explicitly:
underline' "=" $ text "Title" -- Correct
underline' "=" "Title" -- Ambiguous type error
Elements
Text
text "Simple text"
-- Or with OverloadedStrings:
"Simple text"
Simple text
Line Break
Add line breaks with br:
layout ["Line 1", br, "Line 2"]
Line 1
Line 2
Section: section
section "Config" [kv [("env", "prod")]]
section' "-" "Status" [kv [("health", "ok")]]
section'' "#" "Report" 5 [kv [("items", "42")]]
=== Config ===
env: prod
--- Status ---
health: ok
##### Report #####
items: 42
Layout (vertical): layout
layout ["First", "Second", "Third"]
First
Second
Third
Row (horizontal): row
Arrange elements side-by-side horizontally:
row ["Left", "Middle", "Right"]
Left Middle Right
Multi-line elements are aligned at the top:
row
[ layout ["Left", "Column"]
, layout ["Middle", "Column"]
, layout ["Right", "Column"]
]
Tight Row: tightRow
Like row, but with no spacing between elements (useful for gradients and progress bars):
tightRow [withColor ColorRed $ text "โ", withColor ColorGreen $ text "โ", withColor ColorBlue $ text "โ"]
โโโ
Text alignment: alignLeft, alignRight, alignCenter, justify
Align text within a specified width:
layout
[ alignLeft 40 "Left aligned"
, alignCenter 40 "Centered"
, alignRight 40 "Right aligned"
, justify 40 "This text is justified evenly"
]
Left aligned
Centered
Right aligned
This text is justified evenly
Horizontal rule: hr
hr
hr' "~"
hr'' "-" 10
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
----------
Vertical rule: vr
row [vr, vr' "โ", vr'' "x" 5]
โ โ x
โ โ x
โ โ x
โ โ x
โ โ x
โ โ
โ โ
โ โ
โ โ
โ โ
Key-value pairs: kv
kv [("name", "Alice"), ("role", "admin")]
name: Alice
role: admin
Table: table
Tables automatically handle alignment and borders:
table ["Name", "Age", "City"]
[ ["Alice", "30", "New York"]
, ["Bob", "25", ""]
, ["Charlie", "35", "London"]
]
โโโโโโโโโโโฌโโโโโโฌโโโโโโโโโโ
โ Name โ Age โ City โ
โโโโโโโโโโโผโโโโโโผโโโโโโโโโโค
โ Alice โ 30 โ New Yorkโ
โ Bob โ 25 โ โ
โ Charlie โ 35 โ London โ
โโโโโโโโโโโดโโโโโโดโโโโโโโโโโ
Unordered Lists: ul
Clean unordered lists with automatic nesting:
ul ["Feature A", "Feature B", "Feature C"]
โข Feature A
โข Feature B
โข Feature C
Nested lists with auto-styling:
ul [ "Backend"
, ul ["API", "Database"]
, "Frontend"
, ul ["Components", ul ["Header", ul ["Footer"]]]
]
โข Backend
โฆ API
โฆ Database
โข Frontend
โฆ Components
โช Header
โข Footer
Ordered Lists: ol
Numbered lists with automatic nesting:
ol ["First step", "Second step", "Third step"]
1. First step
2. Second step
3. Third step
Nested ordered lists with automatic style cycling (numbers โ letters โ roman numerals):
ol [ "Setup"
, ol ["Install dependencies", "Configure", ol ["Check version"]]
, "Build"
, "Deploy"
]
1. Setup
a. Install dependencies
b. Configure
i. Check version
2. Build
3. Deploy
Underline: underline
Add underlines to any element:
underline "Important Title"
underline' "=" $ text "Custom" -- Use text for custom underline char
Important Title
โโโโโโโโโโโโโโโ
Custom
โโโโโโ
Box: box
With title:
box "Summary" [kv [("total", "42")]]
โโโSummaryโโโโ
โ total: 42 โ
โโโโโโโโโโโโโโ
Without title:
box "" [kv [("total", "42")]]
โโโโโโโโโโโโโโ
โ total: 42 โ
โโโโโโโโโโโโโโ
Status card: statusCard
statusCard "CPU" "45%"
โโโโโโโโโ
โ CPU โ
โ 45% โ
โโโโโโโโโ
Progress bar: inlineBar
inlineBar "Download" 0.75
Download [โโโโโโโโโโโโโโโโโโโโ] 75%
Tree: tree
tree "Project"
[ branch "src"
[ leaf "main.hs"
, leaf "test.hs"
]
, branch "docs"
[ leaf "README.md"
]
]
Project
โโโ src
โ โโโ main.hs
โ โโโ test.hs
โโโ docs
โโโ README.md
Chart: chart
chart [("Web", 10), ("Mobile", 20), ("API", 15)]
Web โโโโโโโโโโโโโโโโโโโโโ 10
Mobile โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ 20
API โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ 15
Padding: pad
Add uniform padding around any element:
pad 2 $ text "content"
content
Centering: center
Smart auto-centering and manual width:
center "Auto-centered" -- Uses layout context
center' 20 "Manual width" -- Fixed width
Auto-centered
Manual width
Margin: margin
Add prefix margins to elements for compiler-style error messages:
margin "[error]"
[ text "Ooops"
, text ""
, row [ text "result :: Int = "
, underline' "^" $ text "getString"
]
, text "Expected Int, found String"
]
[error] Ooops
[error]
[error] result :: Int = getString
[error] ^^^^^^^^^
[error] Expected Int, found String
Border Styles
Elements like box, table, and statusCard support different border styles:
BorderNormal (default):
box "Title" ["content"]
โโโTitleโโโ
โ content โ
โโโโโโโโโโโ
BorderDouble:
withBorder BorderDouble $ statusCard "API" "UP"
โโโโโโโโโ
โ API โ
โ UP โ
โโโโโโโโโ
BorderThick:
withBorder BorderThick $ table ["Name"] [["Alice"]]
โโโโโโโโโ
โ Name โ
โฃโโโโโโโโซ
โ Alice โ
โโโโโโโโโ
BorderRound:
withBorder BorderRound $ box "Info" ["content"]
โญโโInfoโโโโฎ
โ content โ
โฐโโโโโโโโโโฏ
BorderNone (invisible borders):
withBorder BorderNone $ box "Info" ["content"]
Info
content
Colors (ANSI Support)
Add ANSI colors to any element:
layout[
withColor ColorRed $ text "The quick brown fox...",
withColor ColorBrightCyan $ text "The quick brown fox...",
underlineColored "~" ColorRed $ text "The quick brown fox...",
margin "[INFO]" [withColor ColorCyan $ text "The quick brown fox..."]
]

Standard Colors:
ColorBlack ColorRed ColorGreen ColorYellow ColorBlue ColorMagenta ColorCyan ColorWhiteColorBrightBlack ColorBrightRed ColorBrightGreen ColorBrightYellow ColorBrightBlue ColorBrightMagenta ColorBrightCyan ColorBrightWhiteColorNoColor(for conditional formatting)
Extended Colors:
ColorFull n- 256-color palette (0-255)ColorTrue r g b- 24-bit RGB true color
Color Gradients
Create beautiful gradients with extended colors:
let palette = tightRow $ map (\i -> withColor (ColorFull i) $ text "โ") [16, 18..231]
redToBlue = tightRow $ map (\i -> withColor (ColorTrue i 100 (255 - i)) $ text "โ") [0, 4..255]
greenFade = tightRow $ map (\i -> withColor (ColorTrue 0 (255 - i) i) $ text "โ") [0, 4..255]
rainbow = tightRow $ map colorBlock [0, 4..255]
where
colorBlock i =
let r = if i < 128 then i * 2 else 255
g = if i < 128 then 255 else (255 - i) * 2
b = if i > 128 then (i - 128) * 2 else 0
in withColor (ColorTrue r g b) $ text "โ"
putStrLn $ render $ layout [palette, redToBlue, greenFade, rainbow]

Styles (ANSI Support)
Add ANSI styles to any element:
layout[
withStyle StyleBold $ text "The quick brown fox...",
withColor ColorRed $ withStyle StyleBold $ text "The quick brown fox...",
withStyle StyleReverse $ withStyle StyleItalic $ text "The quick brown fox..."
]

Styles:
StyleBold StyleDim StyleItalic StyleUnderlineStyleBlink StyleReverse StyleHidden StyleStrikethroughStyleNoStyle(for conditional formatting)
Combining Styles:
Use <> to combine multiple styles at once:
layout[
withStyle (StyleBold <> StyleItalic <> StyleUnderline) $ text "The quick brown fox...",
withStyle (StyleBold <> StyleReverse) $ text "The quick brown fox..."
]

You can also combine colors and styles:
withColor ColorBrightYellow $ withStyle (StyleBold <> StyleItalic) $ text "The quick brown fox..."
Custom Components
Create your own components by implementing the Element typeclass
data Square = Square Int
instance Element Square where
renderElement (Square size)
| size < 2 = ""
| otherwise = intercalate "\n" (top : middle ++ [bottom])
where
w = size * 2 - 2
top = "โ" ++ replicate w 'โ' ++ "โ"
middle = replicate (size - 2) ("โ" ++ replicate w ' ' ++ "โ")
bottom = "โ" ++ replicate w 'โ' ++ "โ"
-- Helper to avoid wrapping with L
square :: Int -> L
square n = L (Square n)
-- Use it like any other element
putStrLn $ render $ row
[ square 3
, square 5
, square 7
]
โโโโโโ โโโโโโโโโโ โโโโโโโโโโโโโโ
โ โ โ โ โ โ
โโโโโโ โ โ โ โ
โ โ โ โ
โโโโโโโโโโ โ โ
โ โ
โโโโโโโโโโโโโโ
REPL
Drop into GHCi to experiment:
cabal repl
ฮป> :set -XOverloadedStrings
ฮป> import Layoutz
ฮป> putStrLn $ render $ center $ box "Hello" ["World!"]
โโโHelloโโโ
โ World! โ
โโโโโโโโโโโ
ฮป> putStrLn $ render $ table ["A", "B"] [["1", "2"]]
โโโโโฌโโโโ
โ A โ B โ
โโโโโผโโโโค
โ 1 โ 2 โ
โโโโโดโโโโ
Inspiration
- Original Scala layoutz.