layoutz
Simple, beautiful CLI output for Haskell ๐ชถ
Build declarative and composable sections, trees, tables, dashboards for your Haskell applications.
Features
- Zero dependencies, use
Layoutz.hs like 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
layout arranges 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 ColorWhite
ColorBrightBlack ColorBrightRed ColorBrightGreen ColorBrightYellow ColorBrightBlue ColorBrightMagenta ColorBrightCyan ColorBrightWhite
ColorNoColor (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 StyleUnderline
StyleBlink StyleReverse StyleHidden StyleStrikethrough
StyleNoStyle (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