diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -24,6 +24,19 @@
 
 [KaC]: <https://keepachangelog.com/en/1.0.0/>
 
+## 0.5.0.0 (2023-05-28)
+
+### Breaking
+
+* Add support for `optparse-applicative` `0.18`
+
+### Non-Breaking
+
+* Bump `ansi-terminal` dependency version upper bound
+* Bump `ansi-wl-pprint` dependency version upper bound
+* Adjust dependency constraints to match tested versions
+* Fix typo in CLI error message
+
 ## 0.4.0.0 (2022-03-02)
 
 ### Breaking
diff --git a/LICENSE b/LICENSE
--- a/LICENSE
+++ b/LICENSE
@@ -1,6 +1,6 @@
 The MIT License
 
-Copyright (c) 2020-2022 Travis Cardwell
+Copyright (c) 2020-2023 Travis Cardwell
 
 Permission is hereby granted, free of charge, to any person obtaining a copy
 of this software and associated documentation files (the "Software"), to deal
diff --git a/README.md b/README.md
--- a/README.md
+++ b/README.md
@@ -79,23 +79,43 @@
 
 ### Usage
 
-See the [`redact` man page](doc/redact.1.md) for usage information.
+See the [`redact` man page][] for usage information.
 
+[`redact` man page]: <doc/redact.1.md>
+
 ## Project
 
 ### Links
 
 * Hackage: <https://hackage.haskell.org/package/redact>
 * Stackage: <https://www.stackage.org/package/redact>
+* Flora: <https://flora.pm/packages/@hackage/redact>
 * GitHub: <https://github.com/ExtremaIS/redact-haskell>
 * GitHub Actions CI: <https://github.com/ExtremaIS/redact-haskell/actions>
 
+### Branches
+
+The `main` branch is reserved for releases.  It may be considered stable, and
+`HEAD` is always the latest release.
+
+The `develop` branch is the primary development branch.  It contains changes
+that have not yet been released, and it is not necessarily stable.
+
+[Hackage revisions][] are made for metadata changes, such as relaxation of
+constraints when new versions of dependencies are released.  The
+`redact.cabal` metadata in the `main` branch may therefore not match that of
+Hackage.  The `redact.cabal` metadata in the `develop` branch may match,
+*unless* work is being done on a new release that contains other changes.
+
+[Hackage revisions]: <https://github.com/haskell-infra/hackage-trustees/blob/master/revisions-information.md#hackage-metadata-revisions--what-they-are-how-they-work>
+
 ### Tags
 
 All releases are tagged in the `main` branch.  Release tags are signed using
-the
-[`security@extrema.is` GPG key](http://keys.gnupg.net/pks/lookup?op=vindex&fingerprint=on&search=0x1D484E4B4705FADF).
+the [`security@extrema.is` GPG key][].
 
+[`security@extrema.is` GPG key]: <https://keyserver.ubuntu.com/pks/lookup?search=0x1D484E4B4705FADF&fingerprint=on&op=index>
+
 ### Contribution
 
 Issues and feature requests are tracked on GitHub:
@@ -105,6 +125,8 @@
 
 ### License
 
-This project is released under the
-[MIT License](https://opensource.org/licenses/MIT) as specified in the
-[`LICENSE`](LICENSE) file.
+This project is released under the [MIT License][] as specified in the
+[`LICENSE`][] file.
+
+[MIT License]: <https://opensource.org/licenses/MIT>
+[`LICENSE`]: <LICENSE>
diff --git a/app/LibOA.hs b/app/LibOA.hs
--- a/app/LibOA.hs
+++ b/app/LibOA.hs
@@ -2,22 +2,28 @@
 -- |
 -- Module      : LibOA
 -- Description : supplementary functions for optparse-applicative
--- Copyright   : Copyright (c) 2019-2022 Travis Cardwell
+-- Copyright   : Copyright (c) 2019-2023 Travis Cardwell
 -- License     : MIT
 --
 -- This is a collection of functions that I often use with
 -- @optparse-applicative@.  I do not feel that it is worth maintaining yet
 -- another helper package on Hackage, so I just copy the code to different
 -- projects as required.  If the library grows to a substantial size or others
--- with to use it, I will reconsider.
+-- want to use it, I will reconsider.
 --
--- Revision: 2022-01-02
+-- Revision: 2023-05-26
 ------------------------------------------------------------------------------
 
 {-# LANGUAGE CPP #-}
 {-# LANGUAGE LambdaCase #-}
 {-# LANGUAGE TupleSections #-}
 
+#if defined(MIN_VERSION_ansi_wl_pprint)
+#if MIN_VERSION_ansi_wl_pprint (1,0,2)
+{-# OPTIONS_GHC -Wno-warnings-deprecations #-}
+#endif
+#endif
+
 module LibOA
   ( -- * Options
     -- $Options
@@ -28,14 +34,23 @@
     -- * Help
   , (<||>)
   , section
+  , section'
   , table
   , table_
   , vspace
+    -- ** Compatibility
+  , Doc
+  , (<$$>)
+  , empty
+  , string
+  , vcat
   ) where
 
 -- https://hackage.haskell.org/package/ansi-wl-pprint
+#if !MIN_VERSION_optparse_applicative (0,18,0)
 import qualified Text.PrettyPrint.ANSI.Leijen as Doc
 import Text.PrettyPrint.ANSI.Leijen (Doc)
+#endif
 
 -- https://hackage.haskell.org/package/base
 import Data.List (intersperse, transpose)
@@ -50,8 +65,16 @@
 import qualified Options.Applicative.Builder.Internal as OABI
 #endif
 import qualified Options.Applicative.Common as OAC
+#if MIN_VERSION_optparse_applicative (0,18,0)
+import Options.Applicative.Help.Pretty (Doc)
+#endif
 import qualified Options.Applicative.Types as OAT
 
+-- https://hackage.haskell.org/package/prettyprinter
+#if MIN_VERSION_optparse_applicative (0,18,0)
+import qualified Prettyprinter as Doc
+#endif
+
 ------------------------------------------------------------------------------
 -- $Options
 --
@@ -103,8 +126,12 @@
 commands :: OA.Parser a -> [String]
 commands =
     let go _ opt = case OAT.optMain opt of
+#if MIN_VERSION_optparse_applicative (0,18,0)
+           OAT.CmdReader _ cmdPs -> reverse $ fst <$> cmdPs
+#else
            OAT.CmdReader _ cmds _ -> reverse cmds
-           _otherReader           -> []
+#endif
+           _otherReader -> []
     in  concat . OAC.mapParser go
 
 ------------------------------------------------------------------------------
@@ -115,22 +142,26 @@
 d1 <||> d2 = d1 <> Doc.line <> Doc.line <> d2
 infixr 5 <||>
 
--- | Create a section with a title and indented body
+-- | Create a section with a title and body indented by 2 spaces
 section :: String -> Doc -> Doc
-section title = (Doc.text title Doc.<$$>) . Doc.indent 2
+section = section' 2
 
+-- | Create a section with a title and body indented by specified spaces
+section' :: Int -> String -> Doc -> Doc
+section' numSpaces title = (string title <$$>) . Doc.indent numSpaces
+
 -- | Create a table, with formatting
 table :: Int -> [[(String, Doc -> Doc)]] -> Doc
 table sep rows = Doc.vcat $
-    map (fromMaybe Doc.empty . foldr go Nothing . zip lengths) rows
+    map (fromMaybe empty . foldr go Nothing . zip lengths) rows
   where
     lengths :: [Int]
     lengths = map ((+) sep . maximum . map (length . fst)) $ transpose rows
 
     go :: (Int, (String, Doc -> Doc)) -> Maybe Doc -> Maybe Doc
     go (len, (s, f)) = Just . \case
-      Just doc -> Doc.fill len (f $ Doc.string s) <> doc
-      Nothing  -> f $ Doc.string s
+      Just doc -> Doc.fill len (f $ string s) <> doc
+      Nothing  -> f $ string s
 
 -- | Create a table, without formatting
 table_ :: Int -> [[String]] -> Doc
@@ -139,3 +170,30 @@
 -- | Vertically space documents with blank lines between them
 vspace :: [Doc] -> Doc
 vspace = mconcat . intersperse (Doc.line <> Doc.line)
+
+------------------------------------------------------------------------------
+-- $Compatibility
+
+(<$$>) :: Doc -> Doc -> Doc
+#if MIN_VERSION_optparse_applicative (0,18,0)
+l <$$> r = l <> Doc.line <> r
+#else
+(<$$>) = (Doc.<$$>)
+#endif
+
+empty :: Doc
+#if MIN_VERSION_optparse_applicative (0,18,0)
+empty = Doc.emptyDoc
+#else
+empty = Doc.empty
+#endif
+
+string :: String -> Doc
+#if MIN_VERSION_optparse_applicative (0,18,0)
+string = Doc.pretty
+#else
+string = Doc.string
+#endif
+
+vcat :: [Doc] -> Doc
+vcat = Doc.vcat
diff --git a/app/Main.hs b/app/Main.hs
--- a/app/Main.hs
+++ b/app/Main.hs
@@ -2,24 +2,27 @@
 -- |
 -- Module      : Main
 -- Description : redact: hide secret text on the terminal
--- Copyright   : Copyright (c) 2020-2022 Travis Cardwell
+-- Copyright   : Copyright (c) 2020-2023 Travis Cardwell
 -- License     : MIT
 --
 -- See the README for details.
 ------------------------------------------------------------------------------
 
+{-# LANGUAGE CPP #-}
 {-# LANGUAGE OverloadedStrings #-}
 {-# LANGUAGE RecordWildCards #-}
 
+#if defined(MIN_VERSION_ansi_wl_pprint)
+#if MIN_VERSION_ansi_wl_pprint (1,0,2)
+{-# OPTIONS_GHC -Wno-warnings-deprecations #-}
+#endif
+#endif
+
 module Main (main) where
 
 -- https://hackage.haskell.org/package/ansi-terminal
 import qualified System.Console.ANSI as Term
 
--- https://hackage.haskell.org/package/ansi-wl-pprint
-import qualified Text.PrettyPrint.ANSI.Leijen as Doc
-import Text.PrettyPrint.ANSI.Leijen (Doc)
-
 -- https://hackage.haskell.org/package/base
 import Control.Applicative ((<|>), optional)
 import Control.Exception (onException)
@@ -81,7 +84,7 @@
   :: String
   -> Either String Term.Color
 parseColor s
-    = maybe (Left $ "uknown color: " ++ s) Right
+    = maybe (Left $ "unknown color: " ++ s) Right
     $ lookup (toLower <$> s) colorMap
 
 ------------------------------------------------------------------------------
@@ -238,7 +241,7 @@
       , OA.help "redact test text using the configured color"
       ]
 
-footer :: Doc
+footer :: LibOA.Doc
 footer = LibOA.vspace
     [ colorsHelp
     , intensitiesHelp
@@ -246,48 +249,44 @@
     , settingsHelp
     ]
   where
-    colorsHelp :: Doc
-    colorsHelp = LibOA.section "COLOR values:" . Doc.text $
+    colorsHelp :: LibOA.Doc
+    colorsHelp = LibOA.section "COLOR values:" . LibOA.string $
       intercalate ", " (fst <$> colorMap)
 
-    intensitiesHelp :: Doc
-    intensitiesHelp = LibOA.section "INTENSITY values:" . Doc.text $
+    intensitiesHelp :: LibOA.Doc
+    intensitiesHelp = LibOA.section "INTENSITY values:" . LibOA.string $
       intercalate ", " (fst <$> intensityMap)
 
-    lenientHelp :: Doc
-    lenientHelp = LibOA.section "LENIENT values:" $ Doc.text "true, false"
+    lenientHelp :: LibOA.Doc
+    lenientHelp =
+      LibOA.section "LENIENT values:" $ LibOA.string "true, false"
 
-    settingsHelp :: Doc
-    settingsHelp = LibOA.section "Settings priority:" . Doc.vcat $
-      [ Doc.text "1. command-line options" Doc.<$$> Doc.indent 5
-          ( Doc.vcat
-              [ Doc.text "--color"
-              , Doc.text "--intensity"
-              , Doc.text "--lenient"
-              ]
-          )
-      , Doc.text "2. environment variables" Doc.<$$> Doc.indent 5
-          ( Doc.vcat
-              [ Doc.text $ envColorName ++ "=COLOR"
-              , Doc.text $ envIntensityName ++ "=INTENSITY"
-              , Doc.text $ envLenientName ++ "=LENIENT"
-              ]
-          )
-      , Doc.text ("3. settings file (" ++ configFileName ++ ")") Doc.<$$>
-          Doc.indent 5
-            ( Doc.vcat
-                [ Doc.text "color=COLOR"
-                , Doc.text "intensity=INTENSITY"
-                , Doc.text "lenient=LENIENT"
-                ]
-            )
-      , Doc.text "4. defaults" Doc.<$$> Doc.indent 5
-          ( LibOA.table_ 2
-              [ ["color:", toLower <$> show defaultColor]
-              , ["intensity:", toLower <$> show defaultIntensity]
-              , ["lenient:", toLower <$> show defaultLenient]
-              ]
-          )
+    settingsHelp :: LibOA.Doc
+    settingsHelp = LibOA.section "Settings priority:" . LibOA.vcat $
+      [ LibOA.section' 5 "1. command-line options" $
+          LibOA.vcat
+            [ LibOA.string "--color"
+            , LibOA.string "--intensity"
+            , LibOA.string "--lenient"
+            ]
+      , LibOA.section' 5 "2. environment variables" $
+          LibOA.vcat
+            [ LibOA.string $ envColorName ++ "=COLOR"
+            , LibOA.string $ envIntensityName ++ "=INTENSITY"
+            , LibOA.string $ envLenientName ++ "=LENIENT"
+            ]
+      , LibOA.section' 5 ("3. settings file (" ++ configFileName ++ ")") $
+          LibOA.vcat
+            [ LibOA.string "color=COLOR"
+            , LibOA.string "intensity=INTENSITY"
+            , LibOA.string "lenient=LENIENT"
+            ]
+      , LibOA.section' 5 "4. defaults" $
+          LibOA.table_ 2
+            [ ["color:", toLower <$> show defaultColor]
+            , ["intensity:", toLower <$> show defaultIntensity]
+            , ["lenient:", toLower <$> show defaultLenient]
+            ]
       ]
 
 ------------------------------------------------------------------------------
diff --git a/redact.cabal b/redact.cabal
--- a/redact.cabal
+++ b/redact.cabal
@@ -1,5 +1,5 @@
 name:           redact
-version:        0.4.0.0
+version:        0.5.0.0
 category:       Utils
 synopsis:       hide secret text on the terminal
 description:
@@ -11,7 +11,7 @@
 bug-reports:    https://github.com/ExtremaIS/redact-haskell/issues
 author:         Travis Cardwell <travis.cardwell@extrema.is>
 maintainer:     Travis Cardwell <travis.cardwell@extrema.is>
-copyright:      Copyright (c) 2020-2022 Travis Cardwell
+copyright:      Copyright (c) 2020-2023 Travis Cardwell
 license:        MIT
 license-file:   LICENSE
 
@@ -24,7 +24,9 @@
    || ==8.8.4
    || ==8.10.7
    || ==9.0.2
-   || ==9.2.1
+   || ==9.2.8
+   || ==9.4.5
+   || ==9.6.2
 
 extra-source-files:
   CHANGELOG.md
@@ -34,9 +36,11 @@
   type: git
   location: https://github.com/ExtremaIS/redact-haskell.git
 
-flag write-hie
-  description: write .hie files
+-- This flag is referenced in the Stack build-constraints.yaml configuration.
+flag optparse-applicative_ge_0_18
+  description: Use optparse-applicative 0.18 or newer
   default: False
+  manual: False
 
 library
   hs-source-dirs: src
@@ -50,16 +54,13 @@
   other-modules:
       Paths_redact
   build-depends:
-      ansi-terminal >=0.8 && <0.12
-    , base >=4.7 && <5
+      ansi-terminal >=0.8.0.4 && <1.1
+    , base >=4.10.1 && <4.19
     , text >=1.2.3 && <2.1
   default-language: Haskell2010
   default-extensions:
       OverloadedStrings
-  if flag(write-hie)
-    ghc-options: -Wall -fwrite-ide-info -hiedir=.hie
-  else
-    ghc-options: -Wall
+  ghc-options: -Wall
 
 executable redact
   hs-source-dirs: app
@@ -68,16 +69,19 @@
       LibOA
   build-depends:
       ansi-terminal
-    , ansi-wl-pprint >=0.6 && <0.7
     , base
-    , directory >=1.3 && <1.4
-    , optparse-applicative >=0.14 && <0.18
+    , directory >=1.3.0.2 && <1.4
     , redact
-  default-language: Haskell2010
-  if flag(write-hie)
-    ghc-options: -Wall -fwrite-ide-info -hiedir=.hie
+  if flag(optparse-applicative_ge_0_18)
+    build-depends:
+        optparse-applicative >=0.18 && <0.19
+      , prettyprinter >=1.7.1 && <1.8
   else
-    ghc-options: -Wall
+    build-depends:
+        ansi-wl-pprint >=0.6.8.2 && <1.1
+      , optparse-applicative >=0.13 && <0.18
+  default-language: Haskell2010
+  ghc-options: -Wall
 
 test-suite redact-test
   type: exitcode-stdio-1.0
@@ -88,8 +92,8 @@
   build-depends:
       base
     , redact
-    , tasty >=1.0 && <1.5
-    , tasty-hunit >=0.10 && <0.11
+    , tasty >=0.12 && <1.5
+    , tasty-hunit >=0.8 && <0.11
   if impl(ghc >= 8.8.1)
     other-modules:
         Redact.Markdown.Mock
diff --git a/src/Redact.hs b/src/Redact.hs
--- a/src/Redact.hs
+++ b/src/Redact.hs
@@ -2,7 +2,7 @@
 -- |
 -- Module      : Redact
 -- Description : metadata
--- Copyright   : Copyright (c) 2020-2022 Travis Cardwell
+-- Copyright   : Copyright (c) 2020-2023 Travis Cardwell
 -- License     : MIT
 ------------------------------------------------------------------------------
 
diff --git a/src/Redact/Internal.hs b/src/Redact/Internal.hs
--- a/src/Redact/Internal.hs
+++ b/src/Redact/Internal.hs
@@ -2,7 +2,7 @@
 -- |
 -- Module      : Redact.Internal
 -- Description : internal functions
--- Copyright   : Copyright (c) 2020-2022 Travis Cardwell
+-- Copyright   : Copyright (c) 2020-2023 Travis Cardwell
 -- License     : MIT
 ------------------------------------------------------------------------------
 
diff --git a/src/Redact/Markdown.hs b/src/Redact/Markdown.hs
--- a/src/Redact/Markdown.hs
+++ b/src/Redact/Markdown.hs
@@ -2,7 +2,7 @@
 -- |
 -- Module      : Redact.Markdown
 -- Description : Markdown redaction
--- Copyright   : Copyright (c) 2020-2022 Travis Cardwell
+-- Copyright   : Copyright (c) 2020-2023 Travis Cardwell
 -- License     : MIT
 --
 -- Markdown inline code (text enclosed in backticks) is redacted.  This
diff --git a/src/Redact/Monad/Handle.hs b/src/Redact/Monad/Handle.hs
--- a/src/Redact/Monad/Handle.hs
+++ b/src/Redact/Monad/Handle.hs
@@ -2,7 +2,7 @@
 -- |
 -- Module      : Redact.Monad.Handle
 -- Description : handle I/O
--- Copyright   : Copyright (c) 2020-2022 Travis Cardwell
+-- Copyright   : Copyright (c) 2020-2023 Travis Cardwell
 -- License     : MIT
 ------------------------------------------------------------------------------
 
diff --git a/src/Redact/Monad/Terminal.hs b/src/Redact/Monad/Terminal.hs
--- a/src/Redact/Monad/Terminal.hs
+++ b/src/Redact/Monad/Terminal.hs
@@ -2,7 +2,7 @@
 -- |
 -- Module      : Redact.Monad.Terminal
 -- Description : terminal output
--- Copyright   : Copyright (c) 2020-2022 Travis Cardwell
+-- Copyright   : Copyright (c) 2020-2023 Travis Cardwell
 -- License     : MIT
 ------------------------------------------------------------------------------
 
diff --git a/src/Redact/Types.hs b/src/Redact/Types.hs
--- a/src/Redact/Types.hs
+++ b/src/Redact/Types.hs
@@ -2,7 +2,7 @@
 -- |
 -- Module      : Redact.Types
 -- Description : types representing redacted text
--- Copyright   : Copyright (c) 2020-2022 Travis Cardwell
+-- Copyright   : Copyright (c) 2020-2023 Travis Cardwell
 -- License     : MIT
 ------------------------------------------------------------------------------
 
