diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,5 @@
+## [_Unreleased_](https://github.com/pbrisbin/ronn/compare/ronn-optparse-applicative-v1.0.0.0...main)
+
+## [v1.0.0.0](https://github.com/pbrisbin/ronn/tree/ronn-optparse-applicative-v1.0.0.0)
+
+First tagged pre-release.
diff --git a/README.md b/README.md
new file mode 100644
--- /dev/null
+++ b/README.md
@@ -0,0 +1,18 @@
+# `ronn-optparse-applicative`
+
+[![Hackage](https://img.shields.io/hackage/v/ronn-optparse-applicative.svg?style=flat)](https://hackage.haskell.org/package/ronn-optparse-applicative)
+[![Stackage Nightly](http://stackage.org/package/ronn-optparse-applicative/badge/nightly)](http://stackage.org/nightly/package/ronn-optparse-applicative)
+[![Stackage LTS](http://stackage.org/package/ronn-optparse-applicative/badge/lts)](http://stackage.org/lts/package/ronn-optparse-applicative)
+
+Automatically create a `Ronn` value from `Options.Applicative.Parser`.
+
+## Usage
+
+See [the test][test] and [the result][golden].
+
+[test]: ./ronn-optparse-applicative/tests/Ronn/Options.Applicative.hs
+[golden]: ./doc/ronn-optparse-applicative.1.ronn
+
+---
+
+[CHANGELOG](./ronn-optparse-applicative/CHANGELOG.md)
diff --git a/ronn-optparse-applicative.cabal b/ronn-optparse-applicative.cabal
new file mode 100644
--- /dev/null
+++ b/ronn-optparse-applicative.cabal
@@ -0,0 +1,81 @@
+cabal-version:   1.18
+name:            ronn-optparse-applicative
+version:         1.0.0.0
+license:         AGPL-3
+maintainer:      Pat Brisbin
+homepage:        https://github.com/pbrisbin/ronn#readme
+bug-reports:     https://github.com/pbrisbin/ronn/issues
+synopsis:        Produce Ronn from OptEnvConf
+description:     Please see README.md
+category:        Documentation
+build-type:      Simple
+extra-doc-files:
+    README.md
+    CHANGELOG.md
+
+source-repository head
+    type:     git
+    location: https://github.com/pbrisbin/ronn
+
+library
+    exposed-modules:    Ronn.Options.Applicative
+    hs-source-dirs:     src
+    other-modules:      Paths_ronn_optparse_applicative
+    default-language:   GHC2021
+    default-extensions:
+        DataKinds DeriveAnyClass DerivingStrategies DerivingVia
+        DuplicateRecordFields GADTs LambdaCase NoFieldSelectors
+        NoImplicitPrelude NoMonomorphismRestriction OverloadedRecordDot
+        OverloadedStrings RecordWildCards TypeFamilies
+
+    ghc-options:
+        -fignore-optim-changes -fwrite-ide-info -Weverything
+        -Wno-all-missed-specialisations -Wno-missing-exported-signatures
+        -Wno-missing-import-lists -Wno-missing-kind-signatures
+        -Wno-missing-local-signatures -Wno-missing-safe-haskell-mode
+        -Wno-monomorphism-restriction -Wno-prepositive-qualified-module
+        -Wno-safe -Wno-unsafe
+
+    build-depends:
+        base >=4.16.4.0 && <5,
+        optparse-applicative >=0.17.1.0,
+        ronn >=1.1.0.0,
+        text >=1.2.5.0
+
+    if impl(ghc >=9.8)
+        ghc-options:
+            -Wno-missing-role-annotations -Wno-missing-poly-kind-signatures
+
+test-suite spec
+    type:               exitcode-stdio-1.0
+    main-is:            Main.hs
+    hs-source-dirs:     tests
+    other-modules:
+        Ronn.Options.ApplicativeSpec
+        Paths_ronn_optparse_applicative
+
+    default-language:   GHC2021
+    default-extensions:
+        DataKinds DeriveAnyClass DerivingStrategies DerivingVia
+        DuplicateRecordFields GADTs LambdaCase NoFieldSelectors
+        NoImplicitPrelude NoMonomorphismRestriction OverloadedRecordDot
+        OverloadedStrings RecordWildCards TypeFamilies
+
+    ghc-options:
+        -fignore-optim-changes -fwrite-ide-info -Weverything
+        -Wno-all-missed-specialisations -Wno-missing-exported-signatures
+        -Wno-missing-import-lists -Wno-missing-kind-signatures
+        -Wno-missing-local-signatures -Wno-missing-safe-haskell-mode
+        -Wno-monomorphism-restriction -Wno-prepositive-qualified-module
+        -Wno-safe -Wno-unsafe -threaded -rtsopts -with-rtsopts=-N
+
+    build-depends:
+        base >=4.16.4.0 && <5,
+        hspec >=2.9.7,
+        optparse-applicative >=0.17.1.0,
+        ronn-optparse-applicative,
+        ronn-test
+
+    if impl(ghc >=9.8)
+        ghc-options:
+            -Wno-missing-role-annotations -Wno-missing-poly-kind-signatures
diff --git a/src/Ronn/Options/Applicative.hs b/src/Ronn/Options/Applicative.hs
new file mode 100644
--- /dev/null
+++ b/src/Ronn/Options/Applicative.hs
@@ -0,0 +1,123 @@
+-- |
+--
+-- Module      : Ronn.Options.Applicative
+-- Copyright   : (c) 2024 Patrick Brisbin
+-- License     : AGPL-3
+-- Maintainer  : pbrisbin@gmail.com
+-- Stability   : experimental
+-- Portability : POSIX
+module Ronn.Options.Applicative
+  ( optSynopsis
+  , optDefinitions
+  ) where
+
+import Prelude
+
+import Control.Monad (void)
+import Data.List (intersperse, sortBy)
+import Data.String (IsString (..))
+import Data.Text (Text)
+import Options.Applicative (Parser)
+import Options.Applicative.Common (mapParser, treeMapParser)
+import Options.Applicative.Help.Chunk (Chunk (..))
+import Options.Applicative.Help.Pretty (Doc)
+import Options.Applicative.Help.Pretty qualified as Pretty
+import Options.Applicative.Types
+  ( OptName (..)
+  , OptProperties (..)
+  , OptReader (..)
+  , OptTree (..)
+  , Option (..)
+  )
+import Ronn.AST
+
+optSynopsis :: Parser a -> [Part]
+optSynopsis = go False . treeMapParser (const void)
+ where
+  go :: Bool -> OptTree (Option ()) -> [Part]
+  go nested = \case
+    Leaf o -> [optSynopsisPart o]
+    MultNode ts -> concatMap (go True) ts
+    AltNode _ [Leaf o]
+      | FlagReader {} <- optMain o -> [Brackets $ optSynopsisPart o] -- optional o
+      | otherwise -> [optSynopsisPart o]
+    AltNode _ ts ->
+      [ (if nested then Parens else id) $
+          Concat $
+            intersperse " \\| " $
+              concatMap (go True) ts
+      ]
+    BindNode t -> go True t
+
+optSynopsisPart :: Option x -> Part
+optSynopsisPart o = bracketize go
+ where
+  go = case optMain o of
+    OptReader names _ _ ->
+      let mv = propMetaVar $ optProps o
+      in  Concat $ intersperse "\\|" $ renderNames (Just mv) names
+    FlagReader names _ ->
+      Concat $ intersperse "\\|" $ renderNames Nothing names
+    ArgReader {} ->
+      case propShowDefault $ optProps o of
+        Nothing -> Variable $ fromString $ propMetaVar $ optProps o
+        Just {} -> Brackets $ fromString $ propMetaVar $ optProps o
+    CmdReader {} -> "" -- TODO
+  bracketize = case propShowDefault $ optProps o of
+    Nothing -> id
+    Just {} -> Brackets
+
+optDefinitions :: Parser a -> [Definition]
+optDefinitions = mapParser optDefinition
+
+optDefinition :: a -> Option x -> Definition
+optDefinition _ o =
+  Definition
+    { name = case optMain o of
+        OptReader names _ _ ->
+          let mv = propMetaVar $ optProps o
+          in  Concat $ intersperse ", " $ renderNames (Just mv) names
+        FlagReader names _ ->
+          Concat $ intersperse ", " $ renderNames Nothing names
+        ArgReader {} -> Code $ fromString $ propMetaVar $ optProps o
+        CmdReader {} -> undefined -- TODO
+    , description =
+        let
+          help = Raw (docToText $ propHelp $ optProps o)
+          suffix =
+            maybe [] (pure . Parens . ("default " <>) . fromString) $
+              propShowDefault $
+                optProps o
+        in
+          Line $ help : suffix
+    , content = Nothing
+    }
+
+docToText :: Chunk Doc -> Text
+docToText =
+  maybe
+    ""
+    ( Pretty.renderStrict
+        . Pretty.layoutPretty Pretty.defaultLayoutOptions
+    )
+    . unChunk
+
+renderNames :: Maybe String -> [OptName] -> [Part]
+renderNames mArg = map (renderName mArg) . sortNames
+
+renderName :: Maybe String -> OptName -> Part
+renderName mArg = \case
+  OptShort c -> Code $ fromString ['-', c]
+  OptLong st -> addArg $ Code $ fromString $ "--" <> st
+ where
+  addArg :: Part -> Part
+  addArg = case mArg of
+    Nothing -> id
+    Just arg -> Concat . (<> ["=", Variable $ fromString arg]) . pure
+
+sortNames :: [OptName] -> [OptName]
+sortNames = sortBy $ curry $ \case
+  (OptShort a, OptShort b) -> compare a b
+  (OptShort _, OptLong _) -> LT
+  (OptLong a, OptLong b) -> compare a b
+  (OptLong _, OptShort _) -> GT
diff --git a/tests/Main.hs b/tests/Main.hs
new file mode 100644
--- /dev/null
+++ b/tests/Main.hs
@@ -0,0 +1,10 @@
+{-# OPTIONS_GHC -F -pgmF hspec-discover -Wno-missing-export-lists #-}
+
+-- |
+--
+-- Module      : Main
+-- Copyright   : (c) 2024 Patrick Brisbin
+-- License     : AGPL-3
+-- Maintainer  : pbrisbin@gmail.com
+-- Stability   : experimental
+-- Portability : POSIX
diff --git a/tests/Ronn/Options/ApplicativeSpec.hs b/tests/Ronn/Options/ApplicativeSpec.hs
new file mode 100644
--- /dev/null
+++ b/tests/Ronn/Options/ApplicativeSpec.hs
@@ -0,0 +1,47 @@
+{-# OPTIONS_GHC -Wno-orphans #-}
+
+-- |
+--
+-- Module      : Ronn.Options.ApplicativeSpec
+-- Copyright   : (c) 2024 Patrick Brisbin
+-- License     : AGPL-3
+-- Maintainer  : pbrisbin@gmail.com
+-- Stability   : experimental
+-- Portability : POSIX
+module Ronn.Options.ApplicativeSpec
+  ( spec
+  ) where
+
+import Prelude
+
+import Options.Applicative
+import Ronn.Options.Applicative
+import Ronn.Test
+import Test.Hspec
+
+instance HasSynopsis Parser where
+  getSynopsis = Just . optSynopsis
+
+instance HasOptDefinitions Parser where
+  getOptDefinitions = Just . optDefinitions
+
+instance HasEnvDefinitions Parser
+
+spec :: Spec
+spec = do
+  specify "complete example" $
+    ronnGolden "optparse-applicative" $
+      (,,)
+        <$> optional (switch (long "debug" <> help "Enable debug"))
+        <*> option
+          (str @String)
+          ( mconcat
+              [ short 'o'
+              , long "output"
+              , help "Output file"
+              , metavar "FILE"
+              , value "-"
+              , showDefault
+              ]
+          )
+        <*> argument (str @String) (help "Input file" <> metavar "INPUT")
