diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -3,6 +3,18 @@
 `hslua-module-doclayout` uses [PVP Versioning][1].
 The changelog is available [on GitHub][2].
 
+## 1.2.0
+
+Released 2024-09-21.
+
+-   Require doclayout-0.5.
+
+-   Add new styling functions `bold`, `italic`, `underlined`,
+    `strikeout`, `fg`, and `bg`.
+
+-   The `render` function now takes `style` as a third parameter.
+    It must be either 'plain' or 'ansi' when given.
+
 ## 1.1.1.2
 
 Released 2024-06-10.
diff --git a/README.md b/README.md
--- a/README.md
+++ b/README.md
@@ -53,7 +53,7 @@
 
 #### render
 
-`render (doc[, colwidth])`
+`render (doc[, colwidth[, style]])`
 
 Render the `Doc` using the given column width.
 
@@ -64,6 +64,10 @@
 
 `colwidth`
 :   Maximum number of characters per line
+
+`style`
+:   Whether to generate 'plain' or 'ANSI' terminal output. Must be
+    either `'plain'` or `'ansi'`. Defaults to `'plain'`. (string)
 
 ### Doc construction
 
diff --git a/hslua-module-doclayout.cabal b/hslua-module-doclayout.cabal
--- a/hslua-module-doclayout.cabal
+++ b/hslua-module-doclayout.cabal
@@ -1,6 +1,6 @@
 cabal-version:       2.2
 name:                hslua-module-doclayout
-version:             1.1.1.2
+version:             1.2.0
 synopsis:            Lua module wrapping Text.DocLayout.
 description:         Lua module wrapping @Text.DocLayout@.
 homepage:            https://github.com/hslua/hslua-module-doclayout
@@ -31,7 +31,7 @@
 
 common common-options
   build-depends:       base              >= 4.11   && < 5
-                     , doclayout         >= 0.2    && < 0.5
+                     , doclayout         >= 0.5    && < 0.6
                      , hslua             >= 2.3    && < 2.4
                      , text              >= 1.2    && < 2.2
 
diff --git a/src/HsLua/Module/DocLayout.hs b/src/HsLua/Module/DocLayout.hs
--- a/src/HsLua/Module/DocLayout.hs
+++ b/src/HsLua/Module/DocLayout.hs
@@ -63,6 +63,14 @@
   , real_length
   , update_column
 
+  -- * Styling
+  , bold
+  , italic
+  , underlined
+  , strikeout
+  , fg
+  , bg
+
   -- * Marshaling
   , peekDoc
   , pushDoc
@@ -76,6 +84,7 @@
 import Text.DocLayout (Doc, (<+>), ($$), ($+$))
 
 import qualified Data.Text as T
+import qualified Data.Text.Lazy as TL
 import qualified Text.DocLayout as Doc
 
 --
@@ -187,6 +196,13 @@
   , offset
   , real_length
   , update_column
+    -- styling
+  , bold
+  , italic
+  , underlined
+  , strikeout
+  , fg
+  , bg
   ]
 
 typeDoc :: LuaError e => DocumentedType e (Doc Text)
@@ -243,15 +259,31 @@
 -- line length parameter is omitted or nil.
 render :: LuaError e => DocumentedFunction e
 render = defun "render"
-  ### liftPure2 (flip Doc.render)
+  ### (\doc mbcolwidth useAnsi ->
+          if useAnsi == Just True
+          then pure $ TL.toStrict $ Doc.renderANSI mbcolwidth doc
+          else pure $ Doc.renderPlain mbcolwidth doc)
   <#> docParam "doc"
-  <#> opt (integralParam "colwidth" "planned maximum line length")
+  <#> opt (integralParam "colwidth" $
+           "Maximum number of characters per line.\n" <>
+           "A value of `nil`, the default, means that the text " <>
+           "is not reflown.")
+  <#> opt (parameter peekRenderStyle "string" "style" $
+           "Whether to generate plain text or ANSI terminal output.\n" <>
+           "Must be either `'plain'` or `'ansi'`.\n" <>
+           "Defaults to `'plain'`.")
   =#> functionResult pushText "string" "rendered doc"
   #? T.unlines
      [ "Render a [[Doc]]. The text is reflowed on breakable spaces to"
      , "match the given line length. Text is not reflowed if the line"
      , "line length parameter is omitted or nil."
      ]
+ where
+   peekRenderStyle idx = peekByteString idx >>= \case
+     "ansi"  -> pure True
+     "ANSI"  -> pure True
+     "plain" -> pure False
+     style   -> failPeek $ "Unknown rendering style: " <> style
 
 --
 -- Querying
@@ -529,6 +561,54 @@
       "list provided.")
 
 --
+-- Styling
+--
+
+bold :: LuaError e => DocumentedFunction e
+bold = defun "bold"
+  ### liftPure Doc.bold
+  <#> docParam "doc"
+  =#> docResult "bolded Doc"
+  #? "Puts a [[Doc]] in boldface."
+
+italic :: LuaError e => DocumentedFunction e
+italic = defun "italic"
+  ### liftPure Doc.italic
+  <#> docParam "doc"
+  =#> docResult "styled Doc"
+  #? "Puts a [[Doc]] in italics."
+
+underlined :: LuaError e => DocumentedFunction e
+underlined = defun "underlined"
+  ### liftPure Doc.underlined
+  <#> docParam "doc"
+  =#> docResult "styled Doc"
+  #? "Underlines a [[Doc]]."
+
+strikeout :: LuaError e => DocumentedFunction e
+strikeout = defun "strikeout"
+  ### liftPure Doc.strikeout
+  <#> docParam "doc"
+  =#> docResult "styled Doc"
+  #? "Puts a line through the [[Doc]]."
+
+fg :: LuaError e => DocumentedFunction e
+fg = defun "fg"
+  ### liftPure2 (flip Doc.fg)
+  <#> docParam "doc"
+  <#> colorParam
+  =#> docResult "styled Doc"
+  #? "Set the foreground color."
+
+bg :: LuaError e => DocumentedFunction e
+bg = defun "bg"
+  ### liftPure2 (flip Doc.bg)
+  <#> docParam "doc"
+  <#> colorParam
+  =#> docResult "styled Doc"
+  #? "Set the background color."
+
+--
 -- Marshaling
 --
 
@@ -560,6 +640,23 @@
 -- | @Doc@ typed function parameter.
 docParam :: LuaError e => Text -> Parameter e (Doc Text)
 docParam name = parameter peekDoc "Doc" name "document"
+
+-- | @Color@ function parameter
+colorParam :: Parameter e Doc.Color
+colorParam = parameter peekColor "string" "color"
+  ("One of 'black', 'red', 'green', 'yellow', 'blue', 'magenta' " <>
+   "'cyan', or 'white'.")
+  where
+    peekColor idx = peekByteString idx >>= \case
+      "black"   -> pure Doc.black
+      "red"     -> pure Doc.red
+      "green"   -> pure Doc.green
+      "yellow"  -> pure Doc.yellow
+      "blue"    -> pure Doc.blue
+      "magenta" -> pure Doc.magenta
+      "cyan"    -> pure Doc.cyan
+      "white"   -> pure Doc.white
+      color -> failPeek $ "Unknown color: " <> color
 
 --
 -- Results
diff --git a/test/test-doclayout.lua b/test/test-doclayout.lua
--- a/test/test-doclayout.lua
+++ b/test/test-doclayout.lua
@@ -8,6 +8,10 @@
 local test = tasty.test_case
 local assert = tasty.assert
 
+local function renderANSI (doc, cols)
+  return doclayout.render(doc, cols, 'ansi')
+end
+
 -- Check existence static fields
 return {
   group 'constructors' {
@@ -242,6 +246,75 @@
       -- assert.are_equal(doclayout.update_column(doclayout.empty, 42), 42)
       assert.are_equal(doclayout.update_column('four', 4), 8)
     end)
+  },
+
+  -- Styling happens per-character, so the output is quite verbose, and
+  -- includes additional commands to reset all attributes.
+  -- We just look for the right escape sequences instead of matching
+  -- the whole output.
+  group 'styling' {
+    test('bold', function ()
+      assert.is_truthy(
+        renderANSI(doclayout.bold('a')):match('\027%[1m')
+      )
+    end),
+
+    test('italics', function ()
+      assert.is_truthy(
+        renderANSI(doclayout.italic('a')):match('\027%[3m')
+      )
+    end),
+
+    test('underlined', function ()
+      assert.is_truthy(
+        renderANSI(doclayout.underlined('a')):match('\027%[4m')
+      )
+    end),
+
+    test('strikeout', function ()
+      assert.is_truthy(
+        renderANSI(doclayout.strikeout('a')):match('\027%[9m')
+      )
+    end),
+
+    test('fg', function ()
+      assert.is_truthy(
+        renderANSI(doclayout.fg('a', 'red')):match('\027%[31m')
+      )
+    end),
+
+    test('bg', function ()
+      assert.is_truthy(
+        renderANSI(doclayout.bg('a', 'red')):match('\027%[41m')
+           )
+    end),
+
+    group 'colors' {
+      test('black', function ()
+        renderANSI(doclayout.bg('a', 'black')):match('\027%[40m')
+      end),
+      test('red', function ()
+        renderANSI(doclayout.bg('a', 'red')):match('\027%[41m')
+      end),
+      test('green', function ()
+        renderANSI(doclayout.bg('a', 'green')):match('\027%[42m')
+      end),
+      test('yellow', function ()
+        renderANSI(doclayout.bg('a', 'yellow')):match('\027%[43m')
+      end),
+      test('blue', function ()
+        renderANSI(doclayout.bg('a', 'blue')):match('\027%[44m')
+      end),
+      test('magenta', function ()
+        renderANSI(doclayout.bg('a', 'magenta')):match('\027%[45m')
+      end),
+      test('cyan', function ()
+        renderANSI(doclayout.bg('a', 'cyan')):match('\027%[46m')
+      end),
+      test('white', function ()
+        renderANSI(doclayout.bg('a', 'white')):match('\027%[47m')
+      end),
+    }
   },
 
   group 'Doc type' {
