diff --git a/docs/source/cli.rst b/docs/source/cli.rst
index fbf898fedeba5f962efd1928f7224cef1aba290e..eb3907b61c5937b7d75ef29d4847c589ec46bf16 100644
--- a/docs/source/cli.rst
+++ b/docs/source/cli.rst
@@ -8,7 +8,7 @@ All features are then provided as sub-commands as documented here.
 .. note::
    You can get help for each (sub-)command with the option ``-h`` or ``--help``.
 
-.. argparse::
-   :ref: mhm_tools._cli._main._get_parser
-   :prog: mhm-tools
-   :nodefaultconst:
+.. sphinx_argparse_cli::
+  :module: mhm_tools._cli._main
+  :func: _get_parser
+  :prog: mhm-tools
diff --git a/docs/source/conf.py b/docs/source/conf.py
index 754f7384eb86b9a10b1b83a22bdbebbab0901c90..0a29666eaab1d719a1e21573f92dcc7c6d38a0da 100644
--- a/docs/source/conf.py
+++ b/docs/source/conf.py
@@ -25,7 +25,7 @@ extensions = [
     "sphinx.ext.viewcode",
     "sphinx.ext.napoleon",  # parameters look better than with numpydoc only
     "numpydoc",
-    "sphinxarg.ext",  # documentation of the CLI
+    "sphinx_argparse_cli",  # documentation of the CLI
 ]
 
 # autosummaries from source-files
@@ -75,8 +75,8 @@ html_logo = "_static/logo_large.png"
 html_favicon = "_static/logo.png"
 
 html_theme_options = {
-    "page_sidebar_items": ["page-toc"],
-    "footer_items": ["copyright"],
+    "secondary_sidebar_items": ["page-toc"],
+    "footer_start": ["copyright"],
     "show_nav_level": 2,
     "show_toc_level": 2,
     "icon_links": [
diff --git a/pyproject.toml b/pyproject.toml
index 61652ace5961ff8fc018626f80e08e2e3ef6e8aa..245819d35a366d3e29124aa5996c8bb42a06e211 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -38,6 +38,7 @@ dependencies = [
   "numpy>=1.17.3",
   "netCDF4",
   "xarray",
+  "pandas<2; python_version=='3.8'",
 ]
 
 [project.urls]
@@ -50,14 +51,12 @@ Changelog = "https://git.ufz.de/mhm/mhm/-/blob/main/CHANGELOG.md"
 [project.optional-dependencies]
 doc = [
   "sphinx>=5",
-  "pydata-sphinx-theme==0.11",
+  "pydata-sphinx-theme>=0.13",
   "numpydoc>=1.1",
   "sphinx-design>=0.3",
   "myst-parser>=0.18",
-  "sphinxcontrib-mermaid>=0.7",
-  "ablog>=0.10",
   "docutils>=0.18", # mdinclude with myst
-  "sphinx-argparse>=0.4.0", # CLI doc
+  "sphinx-argparse-cli>=1.11.0", # CLI doc
 ]
 test = ["pytest-cov>=3"]
 check = [
@@ -108,7 +107,7 @@ omit = [
   "*docs*",
   "*examples*",
   "*tests*",
-  "*_cli*",
+  "*/_cli/*",
 ]
 
 [tool.coverage.report]
diff --git a/src/mhm_tools/_cli/_bankfull.py b/src/mhm_tools/_cli/_bankfull.py
index 305dcbb49761f4c68c6f246da767302d6817c531..52263033f3ac6cfb1f2253aa805d9162ba23d0bd 100644
--- a/src/mhm_tools/_cli/_bankfull.py
+++ b/src/mhm_tools/_cli/_bankfull.py
@@ -1,5 +1,10 @@
-"""Calculate the river discharge at bankfull conditions and the bankfull width."""
-from ..post.bankfull_discharge import gen_bankfull_discharge
+"""
+Calculate the river discharge at bankfull conditions and the bankfull width.
+
+Bankfull discharge is determined as the yearly peak flow from monthly average discharge
+with a recurrence interval given by ``return_period``, which is 1.5 years by default.
+"""
+from ..post.bankfull import bankfull_discharge
 
 
 def add_args(parser):
@@ -11,11 +16,11 @@ def add_args(parser):
         the main argument parser
     """
     parser.add_argument(
-        "-p",
+        "-r",
         "--return_period",
         type=float,
         default=1.5,
-        help="The return period of the flood.",
+        help="The return period of the flood in years.",
     )
     parser.add_argument(
         "-w",
@@ -31,7 +36,7 @@ def add_args(parser):
         default="Qrouted",
         help="Variable name for routed streamflow in the NetCDF file",
     )
-    required_args = parser.add_argument_group("Required Named Arguments")
+    required_args = parser.add_argument_group("required arguments")
     required_args.add_argument(
         "-i",
         "--input",
@@ -48,7 +53,7 @@ def add_args(parser):
     )
 
 
-def bankfull(args):
+def run(args):
     """Calculate the bankfull discharge.
 
     Parameters
@@ -56,7 +61,7 @@ def bankfull(args):
     args : argparse.Namespace
         parsed command line arguments
     """
-    gen_bankfull_discharge(
+    bankfull_discharge(
         ncin_path=args.in_file,
         ncout_path=args.out_file,
         return_period=args.return_period,
diff --git a/src/mhm_tools/_cli/_main.py b/src/mhm_tools/_cli/_main.py
index cb84e6ce5054971ff3c2912229dc55089516f5e6..d35843c7a85e4a02c6005a841999dcddade322a7 100644
--- a/src/mhm_tools/_cli/_main.py
+++ b/src/mhm_tools/_cli/_main.py
@@ -5,16 +5,39 @@ from .. import __version__
 from . import _bankfull
 
 
-class CustomFormatter(
+class Formatter(
     argparse.ArgumentDefaultsHelpFormatter, argparse.RawDescriptionHelpFormatter
 ):
     """Custom formatter for argparse with help and raw text."""
 
 
+def add_command_from_module(subparsers, name, module):
+    """
+    Add a subcommand from a given module.
+
+    Parameters
+    ----------
+    subparsers : subparsers
+        Subparser to add the command to.
+    name : str
+        Name of the command to add.
+    module : module
+        Module containing the `add_args` and `run` functions defining the command.
+    """
+    desc = module.__doc__
+    kwargs = {"description": desc}
+    if desc:
+        kwargs["help"] = desc.splitlines()[0]
+    parser = subparsers.add_parser(name, formatter_class=Formatter, **kwargs)
+    module.add_args(parser)
+    parser.set_defaults(func=module.run)
+
+
 def _get_parser():
     parent_parser = argparse.ArgumentParser(
+        prog="mhm-tools",
         description=__doc__,
-        formatter_class=CustomFormatter,
+        formatter_class=Formatter,
     )
 
     parent_parser.add_argument(
@@ -22,7 +45,7 @@ def _get_parser():
         "--version",
         action="version",
         version=__version__,
-        help="display version information",
+        help="Display version information.",
     )
 
     sub_help = (
@@ -35,14 +58,9 @@ def _get_parser():
 
     # all sub-parsers should be added here
     # documentation taken from docstring of respective cli module (first line summary)
+    # module needs two functions: add_args and run
 
-    desc = _bankfull.__doc__
-    help = desc.splitlines()[0]
-    parser = subparsers.add_parser(
-        "bankfull", description=desc, help=help, formatter_class=CustomFormatter
-    )
-    _bankfull.add_args(parser)
-    parser.set_defaults(func=_bankfull.bankfull)
+    add_command_from_module(subparsers, "bankfull", _bankfull)
 
     # return the parser
     return parent_parser
diff --git a/src/mhm_tools/post/__init__.py b/src/mhm_tools/post/__init__.py
index 7cdd1bdca5bc42fa48596cc1170ae248df5ab393..97ba5d375d6b2393b998136bab38792eddfafde7 100644
--- a/src/mhm_tools/post/__init__.py
+++ b/src/mhm_tools/post/__init__.py
@@ -6,11 +6,11 @@ Bankfull discharge
 .. autosummary::
    :toctree:
 
-    gen_bankfull_discharge
+    bankfull_discharge
 """
 
-from . import bankfull_discharge
-from .bankfull_discharge import gen_bankfull_discharge
+from . import bankfull
+from .bankfull import bankfull_discharge
 
-__all__ = ["bankfull_discharge"]
-__all__ += ["gen_bankfull_discharge"]
+__all__ = ["bankfull"]
+__all__ += ["bankfull_discharge"]
diff --git a/src/mhm_tools/post/bankfull_discharge.py b/src/mhm_tools/post/bankfull.py
similarity index 97%
rename from src/mhm_tools/post/bankfull_discharge.py
rename to src/mhm_tools/post/bankfull.py
index fe93fb19b09754fc591485abb5d7509e3548a36c..b56ff9735544a55b9d37b6268541f1ecf9828f39 100644
--- a/src/mhm_tools/post/bankfull_discharge.py
+++ b/src/mhm_tools/post/bankfull.py
@@ -89,7 +89,7 @@ def process_grid(q_monthly, return_period):
     return q_bkfl
 
 
-def gen_bankfull_discharge(
+def bankfull_discharge(
     ncin_path, ncout_path, return_period=1.5, peri_bkfl=False, var="Qrouted"
 ):
     """Calculate bankfull discharge [1]_ [2]_.
@@ -131,8 +131,9 @@ def gen_bankfull_discharge(
     ds["Q_bkfl"] = q_bkfl
     # perimeter
     if peri_bkfl:
+        # "4.8" from Savenije, H. H. G.:
+        # The width of a bankfull channel; Lacey's formula explained
         p_bkfl_data = np.copy(q_bkfl_data)
-        # "4.8" from Savenije, H. H. G.: The width of a bankfull channel; Lacey's formula explained
         p_bkfl_data[q_bkfl_data > 0] = 4.8 * np.sqrt(q_bkfl_data[q_bkfl_data > 0])
         p_bkfl = q_bkfl.copy(data=p_bkfl_data)
         p_bkfl.attrs["long_name"] = "Perimeter at bankfull conditions"
diff --git a/tests/test_bankfull.py b/tests/test_bankfull.py
index 5adea39166e1e9c7d08992f26ab874eb00953d30..35e09a65e58dc4ab9c1a9e1d1ce579f0723a57b4 100644
--- a/tests/test_bankfull.py
+++ b/tests/test_bankfull.py
@@ -44,7 +44,7 @@ class TestBankfull(unittest.TestCase):
         )
 
     def test_bankfull(self):
-        mt.post.gen_bankfull_discharge(self.in_file, self.out_file, peri_bkfl=True)
+        mt.post.bankfull_discharge(self.in_file, self.out_file, peri_bkfl=True)
         self.assertTrue(self.out_file.is_file())
         ds = xr.load_dataset(self.out_file)
         self.assertIn("Q_bkfl", ds.variables)