diff --git a/examples/pass.1 b/examples/pass.1
new file mode 100644
--- /dev/null
+++ b/examples/pass.1
@@ -0,0 +1,137 @@
+.\" vim: ft=nroff.mustache
+.Dd $Mdocdate: September 20 2026 $
+.Dt PASS 1
+.Os
+.Sh NAME
+.Nm pass
+.Nd stores, retrieves, generates, and synchronizes passwords securely
+.Sh SYNOPSIS
+.Nm
+.Bk -words
+.Ar command
+.Ek
+.Pp
+.Nm
+.Cm init
+.Bk -words
+.Op Fl p Ar sub\-folder
+.Ar gpg\-id
+.Ek
+.Pp
+.Nm
+.Cm ls
+.Bk -words
+.Ar subfolder
+.Ek
+.Pp
+.Nm
+.Cm grep
+.Bk -words
+.Op Ar GREPOPTIONS
+.Ar search\-string
+.Ek
+.Pp
+.Nm
+.Cm find
+.Bk -words
+.Ar pass\-names
+.Op Ar pass\-names ...
+.Ek
+.Pp
+.Nm
+.Cm show
+.Bk -words
+.Op Fl c Ar line\-number
+.Op Fl q Ar line\-number
+.Ar pass\-name
+.Ek
+.Pp
+.Nm
+.Cm help
+.Pp
+.Nm
+.Cm version
+.Sh DESCRIPTION
+.Nm
+is a very simple password store that keeps passwords inside
+.Xr gpg2 1
+encrypted files inside a simple directory tree residing at
+.Pa ~/.password-store .
+The pass utility provides a series of commands for manipulating the password
+store, allowing the user to add, remove, edit, synchronize, generate, and
+manipulate passwords.
+.Pp
+If no COMMAND is specified, COMMAND defaults to either
+.Cm show
+or
+.Cm ls ,
+depending on the type of specifier in ARGS. Alternatively, if
+.Ar PASSWORD_STORE_ENABLE_EXTENSIONS
+is set to "true", and the file
+.Pa .extensions/COMMAND.bash
+exists inside the password store and is executable, then it is sourced into the
+environment, passing any arguments and environment variables. Extensions
+existing in a system- wide directory, only installable by the administrator, are
+always enabled.
+.Pp
+Otherwise COMMAND must be one of the valid commands listed below.
+.Pp
+Several of the commands below rely on or provide additional functionality if the
+password store directory is also a git repository. If the password store
+directory is a git repository, all password store modification commands will
+cause a corresponding git commit. Sub- directories may be separate nested git
+repositories, and pass will use the inner-most directory relative to the current
+password. See the
+.Ar EXTENDED GIT EXAMPLE
+section for a detailed description using init and
+.Xr git 1
+.Pp
+The
+.Cm init
+command must be run before other commands in order to initialize the password
+store with the correct gpg key id. Passwords are encrypted using the gpg key set
+with
+.Cm init .
+.Pp
+There is a corresponding bash completion script for use with tab completing
+password names in
+.Xr bash 1 .
+.Ss COMMANDS
+.Bl -tag -width indent
+.It Cm init
+Initialize new password storage and use gpg\-id for encryption. Multiple
+gpg\-ids may be specified, in order to encrypt each password with multiple ids.
+This command must be run first before a password store can be used. If the
+specified gpg\-id is different from the key used in any existing files, these
+files will be reencrypted to use the new id. Note that use of gpg\-agent(1) is
+recommended so that the batch decryption does not require as much user
+intervention. If \-\-path or \-p is specified, along with an argument, a
+specific gpg\-id or set of gpg\-ids is assigned for that specific sub folder of
+the password store. If only one gpg\-id is given, and it is an empty string,
+then the current .gpg\-id file for the specified sub\-folder (or root if
+unspecified) is removed.
+.It Cm ls
+List names of passwords inside the tree at subfolder by using the tree(1)
+program. This command is alternatively named list.
+.It Cm grep
+Searches inside each decrypted password file for search\-string, and displays
+line containing matched string along with filename. Uses grep(1) for matching.
+GREPOPTIONS are passed to grep(1) as\-is. (Note: the GREP_OPTIONS environment
+variable functions as well.)
+.It Cm find
+List names of passwords inside the tree that match pass\-names by using the
+tree(1) program. This command is alternatively named search.
+.It Cm show
+Decrypt and print a password named pass\-name. If \-\-clip or \-c is specified,
+do not print the password but instead copy the first (or otherwise specified)
+line to the clipboard using xclip(1) or wl\-clipboard(1) and then restore the
+clipboard after 45 (or PASSWORD_STORE_CLIP_TIME) seconds. If \-\-qrcode or \-q
+is specified, do not print the password but instead display a QR code using
+qrencode(1) either to the terminal or graphically if supported.
+.It Cm help
+Show usage message.
+.It Cm version
+Show version information.
+.El
+.Sh EXIT STATUS
+.Ex -std
diff --git a/mdoc.cabal b/mdoc.cabal
--- a/mdoc.cabal
+++ b/mdoc.cabal
@@ -1,6 +1,6 @@
 cabal-version:      1.18
 name:               mdoc
-version:            0.2.0.0
+version:            0.3.0.0
 license:            AGPL-3
 license-file:       COPYING
 maintainer:         Pat Brisbin
@@ -37,6 +37,7 @@
     examples/grep.1
     examples/mdoc-dump.1
     examples/mdoc-dump.1.html
+    examples/pass.1
     examples/person.5
     examples/style.css
 
@@ -54,6 +55,7 @@
         Env.Mdoc
         Mdoc
         Mdoc.Data.Argument
+        Mdoc.Data.Command
         Mdoc.Data.Config
         Mdoc.Data.Described
         Mdoc.Data.EnvVar
@@ -72,6 +74,7 @@
         Mdoc.Dump.Options
         Mdoc.Examples.Grep
         Mdoc.Examples.OptEnvConf
+        Mdoc.Examples.Pass
         Mdoc.Examples.Person
         Mdoc.Input
         Mdoc.Optics
diff --git a/src/Mdoc/Data/Argument.hs b/src/Mdoc/Data/Argument.hs
--- a/src/Mdoc/Data/Argument.hs
+++ b/src/Mdoc/Data/Argument.hs
@@ -37,8 +37,8 @@
 
 instance Pretty Argument where
   pretty Argument {schema, optionality} = case optionality of
-    Required -> "Ar" <+> pretty schema
-    _ -> "Op Ar" <+> pretty schema
+    Required -> "Ar" <+> pretty (esc $ pack schema)
+    _ -> "Op Ar" <+> pretty (esc $ pack schema)
 
 data ShortArg = ShortArg
   { name :: Char
@@ -49,7 +49,7 @@
   pretty (ShortArg {name, argument}) =
     prefix (pretty $ esc $ T.singleton name)
       <+> pretty Ar
-      <+> pretty argument.schema
+      <+> pretty (esc $ pack argument.schema)
    where
     prefix :: Doc ann -> Doc ann
     prefix = case argument.optionality of
@@ -66,7 +66,7 @@
     prefix (pretty (esc $ pack name))
       <+> pretty Ns
       <+> pretty Ar
-      <+> pretty argument.schema
+      <+> pretty (esc $ pack argument.schema)
    where
     prefix :: Doc ann -> Doc ann
     prefix = case argument.optionality of
diff --git a/src/Mdoc/Data/Command.hs b/src/Mdoc/Data/Command.hs
new file mode 100644
--- /dev/null
+++ b/src/Mdoc/Data/Command.hs
@@ -0,0 +1,20 @@
+module Mdoc.Data.Command
+  ( Command (..)
+  ) where
+
+import Mdoc.Prelude
+
+import Mdoc.Data.Synopsis (Synopsis)
+import Mdoc.Syntax.Mdoc
+
+data Command = Command
+  { index :: Int
+  , name :: String
+  , synopsis :: Synopsis
+  , description :: Maybe Mdoc
+  }
+  deriving stock (Eq, Generic, Show)
+  deriving anyclass (ToJSON)
+
+instance Ord Command where
+  compare = comparing (.index)
diff --git a/src/Mdoc/Data/Described.hs b/src/Mdoc/Data/Described.hs
--- a/src/Mdoc/Data/Described.hs
+++ b/src/Mdoc/Data/Described.hs
@@ -18,7 +18,6 @@
 
 import Data.Aeson (object, (.=))
 import Data.Function (on)
-import Data.Text qualified as T
 import Mdoc.Data.Optionality
 import Mdoc.Optics
 import Mdoc.Pretty
@@ -77,14 +76,3 @@
 -- | Set a 'Described's 'helpLines' from a 'Text'
 setHelpText :: Text -> Described a -> Described a
 setHelpText = maybe id setHelp . textToMdoc
-
-textToMdoc :: Text -> Maybe Mdoc
-textToMdoc =
-  fmap
-    ( Mdoc
-        . intersperse (MacroLine Pp [])
-        . map (TextLine . esc)
-        . toList
-    )
-    . nonEmpty
-    . T.lines
diff --git a/src/Mdoc/Data/List.hs b/src/Mdoc/Data/List.hs
--- a/src/Mdoc/Data/List.hs
+++ b/src/Mdoc/Data/List.hs
@@ -19,17 +19,16 @@
 
 import Mdoc.Prelude
 
-import Data.Aeson (object, (.=))
+import Data.Aeson (Value (..), object, (.=))
+import Data.Aeson.KeyMap qualified as KeyMap
 import Data.Kind (Constraint, Type)
 import Data.List.NonEmpty qualified as NE
 import Data.Text qualified as T
-import Mdoc.Data.Described
-import Mdoc.Pretty
 import Mdoc.Syntax
 
 type List :: forall {k}. k -> Type -> Type
 newtype List t a = List
-  { items :: NonEmpty (Described a)
+  { items :: NonEmpty a
   }
   deriving stock (Eq, Generic, Show)
   deriving (Semigroup) via Generically (List t a)
@@ -41,14 +40,14 @@
       , "items" .= items
       ]
    where
-    items :: NonEmpty (Described a)
+    items :: NonEmpty a
     items = NE.sort $ NE.nub l.items
 
-fromNonEmpty :: NonEmpty (Described a) -> List t a
+fromNonEmpty :: NonEmpty a -> List t a
 fromNonEmpty = foldMap1 singleton
 
-singleton :: Described a -> List t a
-singleton described = List {items = pure described}
+singleton :: a -> List t a
+singleton a = List {items = pure a}
 
 data Indent
 
@@ -56,21 +55,27 @@
 
 type Width :: forall {k}. k -> Type -> Constraint
 class Width t a where
-  renderedWidth :: Proxy t -> NonEmpty (Described a) -> MacroArg
+  renderedWidth :: Proxy t -> NonEmpty a -> MacroArg
 
 instance Width Indent a where
   renderedWidth _ _ = "indent"
 
-instance Pretty a => Width ByItem a where
+instance ToJSON a => Width ByItem a where
   renderedWidth _ =
     Quoted
       . esc
       . maximumBy (comparing T.length)
       . fmap headWidth
 
-headWidth :: Pretty a => Described a -> Text
-headWidth = go . T.words . renderPlain . pretty . (.item)
+headWidth :: ToJSON a => a -> Text
+headWidth = go . T.words . getHead . toJSON
  where
+  getHead :: Value -> Text
+  getHead v = fromMaybe "" $ do
+    Object km <- pure v
+    String t <- KeyMap.lookup "head" km
+    pure t
+
   go :: [Text] -> Text
   go = \case
     [] -> ""
diff --git a/src/Mdoc/Data/Page.hs b/src/Mdoc/Data/Page.hs
--- a/src/Mdoc/Data/Page.hs
+++ b/src/Mdoc/Data/Page.hs
@@ -13,12 +13,15 @@
   , addSwitch
   , addOption
   , addArgument
+  , addCommand
+  , addCommands
   , addConfig
 
     -- * @ENVIRONMENT@
   , addEnvVar
 
-    -- * Other, rarel-used elements
+    -- * Other, rarely-used elements
+  , getSynopsis
   , setSynopsis
   , setPrologue
   , setEpilogue
@@ -27,6 +30,7 @@
 import Mdoc.Prelude
 
 import Mdoc.Data.Argument
+import Mdoc.Data.Command
 import Mdoc.Data.Config
 import Mdoc.Data.Described
 import Mdoc.Data.EnvVar
@@ -41,9 +45,10 @@
 data Page = Page
   { synopsis :: Maybe Synopsis
   , prologue :: Maybe Mdoc
-  , options :: Maybe (List Indent Positional)
-  , configs :: Maybe (List Indent Config)
-  , environment :: Maybe (List ByItem EnvVar)
+  , options :: Maybe (List Indent (Described Positional))
+  , commands :: Maybe (List Indent Command)
+  , configs :: Maybe (List Indent (Described Config))
+  , environment :: Maybe (List ByItem (Described EnvVar))
   , epilogue :: Maybe Mdoc
   }
   deriving stock (Eq, Generic, Show)
@@ -65,6 +70,12 @@
     & field @"synopsis" <?>~ Synopsis.singleton described
     & field @"options" <?>~ List.singleton described
 
+addCommand :: Command -> Page -> Page
+addCommand c m = m & field @"commands" <?>~ List.singleton c
+
+addCommands :: [Command] -> Page -> Page
+addCommands = appEndo . foldMap (Endo . addCommand)
+
 addConfig :: Described Config -> Page -> Page
 addConfig described m =
   m & field @"configs" <?>~ List.singleton described
@@ -72,6 +83,9 @@
 addEnvVar :: Described EnvVar -> Page -> Page
 addEnvVar described m =
   m & field @"environment" <?>~ List.singleton described
+
+getSynopsis :: Page -> Synopsis
+getSynopsis p = fromMaybe mempty p.synopsis
 
 setSynopsis :: Mdoc -> Page -> Page
 setSynopsis s = field @"synopsis" ?~ SynopsisCustom s
diff --git a/src/Mdoc/Data/Synopsis.hs b/src/Mdoc/Data/Synopsis.hs
--- a/src/Mdoc/Data/Synopsis.hs
+++ b/src/Mdoc/Data/Synopsis.hs
@@ -64,11 +64,8 @@
     unAnnotate
       $ vsep
       $ catMaybes
-        [ Just ".Nm"
-        , Just ".Bk -words"
-        , (".Op Fl" <+>) . pretty . esc . pack . toList <$> nonEmpty u.chars
+        [ (".Op Fl" <+>) . pretty . esc . pack . toList <$> nonEmpty u.chars
         , vsep . toList <$> nonEmpty (u.shorts <> u.longs <> u.args)
-        , Just ".Ek"
         ]
 
 buildUsage :: [Described Positional] -> Usage ann
diff --git a/src/Mdoc/Examples/Pass.hs b/src/Mdoc/Examples/Pass.hs
new file mode 100644
--- /dev/null
+++ b/src/Mdoc/Examples/Pass.hs
@@ -0,0 +1,100 @@
+-- |
+--
+-- Module      : Mdoc.Examples.Pass
+-- Copyright   : (c) 2026 Patrick Brisbin
+-- License     : AGPL-3
+-- Maintainer  : pbrisbin@gmail.com
+-- Stability   : experimental
+-- Portability : POSIX
+module Mdoc.Examples.Pass
+  ( pass1
+  ) where
+
+import Mdoc.Prelude
+
+import Mdoc.Data.Named
+import Mdoc.Data.Page (setPrologue)
+import Mdoc.Syntax
+import Options.Applicative
+import Options.Applicative.Mdoc
+
+pass1 :: Named
+pass1 =
+  mempty
+    & (<> getPage passOpt)
+    & setPrologue
+      ( Mdoc
+          [ MacroLine Nm []
+          , TextLine "is a very simple password store that keeps passwords inside"
+          , MacroLine Xr ["gpg2", "1"]
+          , TextLine "encrypted files inside a simple directory tree residing at"
+          , MacroLine Pa ["~/.password-store", "."]
+          , TextLine
+              "The pass utility provides a series of commands for manipulating the password store, allowing the user to add, remove, edit, synchronize, generate, and manipulate passwords."
+          , MacroLine Pp []
+          , TextLine "If no COMMAND is specified, COMMAND defaults to either"
+          , MacroLine Cm ["show"]
+          , TextLine "or"
+          , MacroLine Cm ["ls", ","]
+          , TextLine "depending on the type of specifier in ARGS. Alternatively, if"
+          , MacroLine Ar ["PASSWORD_STORE_ENABLE_EXTENSIONS"]
+          , TextLine "is set to \"true\", and the file"
+          , MacroLine Pa [".extensions/COMMAND.bash"]
+          , TextLine
+              "exists inside the password store and is executable, then it is sourced into the environment, passing any arguments and environment variables. Extensions existing in a system- wide directory, only installable by the administrator, are always enabled."
+          , MacroLine Pp []
+          , TextLine "Otherwise COMMAND must be one of the valid commands listed below."
+          , MacroLine Pp []
+          , TextLine
+              "Several of the commands below rely on or provide additional functionality if the password store directory is also a git repository. If the password store directory is a git repository, all password store modification commands will cause a corresponding git commit. Sub- directories may be separate nested git repositories, and pass will use the inner-most directory relative to the current password. See the"
+          , MacroLine Ar ["EXTENDED", "GIT", "EXAMPLE"]
+          , TextLine "section for a detailed description using init and"
+          , MacroLine Xr ["git", "1"]
+          , MacroLine Pp []
+          , TextLine "The"
+          , MacroLine Cm ["init"]
+          , TextLine
+              "command must be run before other commands in order to initialize the password store with the correct gpg key id. Passwords are encrypted using the gpg key set with"
+          , MacroLine Cm ["init", "."]
+          , MacroLine Pp []
+          , TextLine
+              "There is a corresponding bash completion script for use with tab completing password names in"
+          , MacroLine Xr ["bash", "1", "."]
+          ]
+      )
+    & name "pass" "stores, retrieves, generates, and synchronizes passwords securely"
+
+{- FOURMOLU_DISABLE -}
+passOpt :: Parser ()
+passOpt = void $ subparser (mconcat
+  [ command "init"    $ info initOpt   $ progDesc "Initialize new password storage and use gpg-id for encryption. Multiple gpg-ids may be specified, in order to encrypt each password with multiple ids. This command must be run first before a password store can be used. If the specified gpg-id is different from the key used in any existing files, these files will be reencrypted to use the new id. Note that use of gpg-agent(1) is recommended so that the batch decryption does not require as much user intervention. If --path or -p is specified, along with an argument, a specific gpg-id or set of gpg-ids is assigned for that specific sub folder of the password store. If only one gpg-id is given, and it is an empty string, then the current .gpg-id file for the specified sub-folder (or root if unspecified) is removed."
+  , command "ls"      $ info lsOpt     $ progDesc "List names of passwords inside the tree at subfolder by using the tree(1) program. This command is alternatively named list."
+  , command "grep"    $ info grepOpt   $ progDesc "Searches inside each decrypted password file for search-string, and displays line containing matched string along with filename. Uses grep(1) for matching. GREPOPTIONS are passed to grep(1) as-is. (Note: the GREP_OPTIONS environment variable functions as well.)"
+  , command "find"    $ info findOpt   $ progDesc "List names of passwords inside the tree that match pass-names by using the tree(1) program. This command is alternatively named search."
+  , command "show"    $ info showOpt   $ progDesc "Decrypt and print a password named pass-name. If --clip or -c is specified, do not print the password but instead copy the first (or otherwise specified) line to the clipboard using xclip(1) or wl-clipboard(1) and then restore the clipboard after 45 (or PASSWORD_STORE_CLIP_TIME) seconds. If --qrcode or -q is specified, do not print the password but instead display a QR code using qrencode(1) either to the terminal or graphically if supported."
+  , command "help"    $ info (pure ()) $ progDesc "Show usage message."
+  , command "version" $ info (pure ()) $ progDesc "Show version information."
+  ])
+
+initOpt :: Parser ()
+initOpt = void $ (,)
+  <$> optional (option (str @Text) (short 'p' <> long "path" <> metavar "sub-folder"))
+  <*> argument (str @Text) (metavar "gpg-id")
+
+lsOpt :: Parser ()
+lsOpt = void $ argument (str @Text) (metavar "subfolder") -- sic
+
+grepOpt :: Parser ()
+grepOpt = void $ (,)
+  <$> optional (argument (str @Text) (metavar "GREPOPTIONS"))
+  <*> argument (str @Text) (metavar "search-string")
+
+findOpt :: Parser ()
+findOpt = void $ some1 $ argument (str @Text) (metavar "pass-names")
+
+showOpt :: Parser ()
+showOpt = void $ (,,)
+  <$> optional (option (str @Text) (short 'c' <> long "clip" <> metavar "line-number"))
+  <*> optional (option (str @Text) (short 'q' <> long "qrcode" <> metavar "line-number"))
+  <*> argument (str @Text) (metavar "pass-name")
+{- FOURMOLU_ENABLE -}
diff --git a/src/Mdoc/Prelude.hs b/src/Mdoc/Prelude.hs
--- a/src/Mdoc/Prelude.hs
+++ b/src/Mdoc/Prelude.hs
@@ -24,7 +24,7 @@
 import Data.Aeson as X (ToJSON (..))
 import Data.Bifunctor as X (bimap, first, second)
 import Data.Foldable as X (for_, toList, traverse_)
-import Data.Foldable1 as X (foldMap1, maximumBy)
+import Data.Foldable1 as X (Foldable1, foldMap1, maximumBy, toNonEmpty)
 import Data.Function as X ((&))
 import Data.Functor as X ((<&>))
 import Data.Functor.Identity as X (Identity)
diff --git a/src/Mdoc/Syntax/Mdoc.hs b/src/Mdoc/Syntax/Mdoc.hs
--- a/src/Mdoc/Syntax/Mdoc.hs
+++ b/src/Mdoc/Syntax/Mdoc.hs
@@ -11,11 +11,16 @@
 
     -- * Pretty
   , prettyMdoc
+
+    -- * Util
+  , textToMdoc
   ) where
 
 import Mdoc.Prelude
 
+import Data.Text qualified as T
 import Mdoc.Pretty
+import Mdoc.Syntax.MacroName
 import Mdoc.Syntax.MdocLine
 
 newtype Mdoc = Mdoc
@@ -30,3 +35,14 @@
 
 instance Pretty Mdoc where
   pretty = unAnnotate . prettyMdoc
+
+textToMdoc :: Text -> Maybe Mdoc
+textToMdoc =
+  fmap
+    ( Mdoc
+        . intersperse (MacroLine Pp [])
+        . map (TextLine . esc)
+        . toList
+    )
+    . nonEmpty
+    . T.lines
diff --git a/src/Options/Applicative/Mdoc.hs b/src/Options/Applicative/Mdoc.hs
--- a/src/Options/Applicative/Mdoc.hs
+++ b/src/Options/Applicative/Mdoc.hs
@@ -16,8 +16,11 @@
 
 import Mdoc.Prelude
 
+import Control.Monad (foldM)
+import Control.Monad.State (MonadState (..), evalState, modify)
 import Data.List (sort)
 import Mdoc.Data.Argument
+import Mdoc.Data.Command
 import Mdoc.Data.Described
 import Mdoc.Data.Flag
 import Mdoc.Data.Option
@@ -32,8 +35,24 @@
 import Prettyprinter.Render.Text qualified as Pretty
 
 getPage :: Parser a -> Page
-getPage p = foldOptTree 0 mempty optionToMan1 $ treeMapParser (const void) p
+getPage p = foldOptTree optionToMan1 $ treeMapParser (const void) p
 
+getCommand :: Int -> String -> O.ParserInfo x -> Command
+getCommand index name pinfo =
+  Command
+    { index
+    , name
+    , description = textToMdoc =<< docToText doc
+    , synopsis = getSynopsis $ getPage $ O.infoParser pinfo
+    }
+ where
+  doc =
+    mconcat
+      [ O.infoProgDesc pinfo
+      , O.infoHeader pinfo
+      , O.infoFooter pinfo
+      ]
+
 optionToMan1
   :: Int
   -> Page
@@ -54,7 +73,12 @@
     schema <- metavar
     let argument = Argument {index, schema, optionality = Required}
     pure $ addArgument (argument <$ d) acc
-  O.CmdReader {} -> acc -- TODO
+  O.CmdReader _mGroup cmds ->
+    let
+      commands :: [Command]
+      commands = zipWith (uncurry . getCommand) [1 ..] $ reverse cmds
+    in
+      addCommands commands acc
  where
   o = d.item
   metavar = guarded (not . null) $ O.optMetaVar o
@@ -73,30 +97,44 @@
     O.OptLong x -> GNUFlag x xs
 
 foldOptTree
-  :: Int
-  -> Page
-  -> (Int -> Page -> Described (O.Option x) -> Page)
+  :: (Int -> Page -> Described (O.Option x) -> Page)
   -> O.OptTree (O.Option x)
   -> Page
-foldOptTree index acc f = \case
+foldOptTree f = flip evalState 0 . foldOptTreeM f mempty
+
+foldOptTreeM
+  :: MonadState Int m
+  => (Int -> Page -> Described (O.Option x) -> Page)
+  -> Page
+  -> O.OptTree (O.Option x)
+  -> m Page
+foldOptTreeM f acc = \case
   O.Leaf o ->
     case O.optVisibility o of
-      O.Visible ->
-        f index acc
+      O.Visible -> do
+        index <- get
+        modify (+ 1)
+        pure
+          $ f index acc
           $ Described
             { item = o
             , optionality = maybe Required Defaulted (O.optShowDefault o)
             , multiple = False
             , help = textToMdoc =<< docToText (O.optHelp o)
             }
-      O.Internal -> acc
-      O.Hidden -> acc
+      O.Internal -> pure acc
+      O.Hidden -> pure acc
   O.MultNode ts -> recur f ts
   O.AltNode O.MarkDefault ts -> recur fAsOptional ts
   O.AltNode O.NoDefault ts -> recur fAsRequired ts
-  O.BindNode t -> foldOptTree (index + 1) acc fAsMultiple t
+  O.BindNode t -> foldOptTreeM fAsMultiple acc t
  where
-  recur g = maybe acc (foldMap1 $ foldOptTree (index + 1) acc g) . nonEmpty
+  recur
+    :: MonadState Int m
+    => (Int -> Page -> Described (O.Option x) -> Page)
+    -> [O.OptTree (O.Option x)]
+    -> m Page
+  recur g = foldM (foldOptTreeM g) acc
 
   fAsOptional i m d = f i m $ d {optionality = Optional}
   fAsRequired i m d = f i m $ d {optionality = Required}
diff --git a/test/Mdoc/Data/SynopsisSpec.hs b/test/Mdoc/Data/SynopsisSpec.hs
--- a/test/Mdoc/Data/SynopsisSpec.hs
+++ b/test/Mdoc/Data/SynopsisSpec.hs
@@ -21,8 +21,4 @@
   it "renders an empty synopsis" $ do
     let synopsis = Synopsis.fromList []
 
-    synopsis
-      `shouldRender` [ ".Nm"
-                     , ".Bk -words"
-                     , ".Ek"
-                     ]
+    synopsis `shouldRender` [""]
diff --git a/test/Mdoc/ExamplesSpec.hs b/test/Mdoc/ExamplesSpec.hs
--- a/test/Mdoc/ExamplesSpec.hs
+++ b/test/Mdoc/ExamplesSpec.hs
@@ -21,6 +21,7 @@
 import Mdoc.Data.Named
 import Mdoc.Examples.Grep
 import Mdoc.Examples.OptEnvConf
+import Mdoc.Examples.Pass
 import Mdoc.Examples.Person
 import Mdoc.Input (parseMdocBytes)
 import Mdoc.Parse (errorBundlePretty, exitParseError)
@@ -36,6 +37,7 @@
 spec = do
   it "grep.1" $ exampleGolden man1 grep1 1
   it "example.1" $ exampleGolden man1 example1 1
+  it "pass.1" $ exampleGolden man1 pass1 1
   it "conf.5" $ exampleGolden man5 conf5 5
   it "example.5" $ exampleGolden man5 example5 5
   it "person.5" $ exampleGolden man5 person5 5
