diff --git a/library/Optima.hs b/library/Optima.hs
--- a/library/Optima.hs
+++ b/library/Optima.hs
@@ -5,6 +5,11 @@
   -- * Params
   Params,
   param,
+  paramGroup,
+  -- * ParamGroup
+  ParamGroup,
+  member,
+  memberGroup,
   -- * Param
   Param,
   value,
@@ -22,6 +27,7 @@
   -- * ValueFormat
   ValueFormat,
   formattedByEnum,
+  formattedByEnumUsingShow,
   unformatted,
 )
 where
@@ -51,9 +57,12 @@
 newtype Param a = Param (Maybe Char -> Text -> Optparse.Parser a)
 
 {-|
-Parameter product, which gets resolved by prefixing the names
+Parameter group, which gets identified by prefixing the names.
+
+Should be used to define parameters, which only make sense in combination.
+E.g., a server config can be defined by providing port and host together.
 -}
-newtype ProductParam a = ProductParam (Text -> Optparse.Parser a)
+newtype ParamGroup a = ParamGroup (Text -> Optparse.Parser a)
 
 {-|
 Parameter value parser.
@@ -78,6 +87,14 @@
 deriving instance Applicative Params
 deriving instance Alternative Params
 
+deriving instance Functor ParamGroup
+instance Applicative ParamGroup where
+  pure x = ParamGroup (\ _ -> pure x)
+  (<*>) (ParamGroup left) (ParamGroup right) = ParamGroup (\ prefix -> left prefix <*> right prefix)
+instance Alternative ParamGroup where
+  empty = ParamGroup (\ _ -> empty)
+  (<|>) (ParamGroup left) (ParamGroup right) = ParamGroup (\ prefix -> left prefix <|> right prefix)
+
 deriving instance Functor Param
 
 deriving instance Functor Value
@@ -109,7 +126,7 @@
     mods = Optparse.fullDesc <> Optparse.progDesc (Text.unpack description)
 
 
--- ** Param
+-- ** Params
 -------------------------
 
 {-|
@@ -118,7 +135,31 @@
 param :: Maybe Char {-^ Single-char name -} -> Text {-^ Long name -} -> Param a -> Params a
 param shortName longName (Param parser) = Params (parser shortName longName)
 
+{-|
+Lift a parameter group parser.
 
+The param group cannot use short names, only long names.
+-}
+paramGroup :: Text {-^ Prefix for the long names of the parameters. If empty, then there'll be no prefixing -} -> ParamGroup a -> Params a
+paramGroup prefix (ParamGroup parser) = Params (parser prefix)
+
+
+-- ** ParamGroup
+-------------------------
+
+{-|
+Lift a param parser into parameter group.
+-}
+member :: Text {-^ Long name of the parameter -} -> Param a -> ParamGroup a
+member name (Param parser) = ParamGroup (\ prefix -> parser Nothing (prefixIfMakesSense prefix name)) where
+
+{-|
+Unite a group by a shared prefix.
+-}
+memberGroup :: Text {-^ Long name prefix -} -> ParamGroup a -> ParamGroup a
+memberGroup prefix (ParamGroup parser) = ParamGroup (\ higherPrefix -> parser (prefixIfMakesSense higherPrefix prefix))
+
+
 -- ** Param
 -------------------------
 
@@ -198,13 +239,27 @@
 -------------------------
 
 {-|
-Derive value format specification from the Enum instance.
+Derive value format specification from the Enum instance and
+explicit mapping of values to their representations.
 -}
-formattedByEnum :: (Bounded a, Enum a, Show a) => ValueFormat a
-formattedByEnum = let
+formattedByEnum :: (Bounded a, Enum a) => (a -> Text) -> ValueFormat a
+formattedByEnum valueRepresentation = formattedByEnumUsingBuilderMapping (TextBuilder.text . valueRepresentation)
+
+{-|
+Derive value format specification from the Enum and Show instances.
+-}
+formattedByEnumUsingShow :: (Bounded a, Enum a, Show a) => ValueFormat a
+formattedByEnumUsingShow = formattedByEnumUsingBuilderMapping (TextBuilder.string . show)
+
+{-|
+Derive value format specification from the Enum instance and
+explicit mapping of values to their representations.
+-}
+formattedByEnumUsingBuilderMapping :: (Bounded a, Enum a) => (a -> TextBuilder.Builder) -> ValueFormat a
+formattedByEnumUsingBuilderMapping valueRepresentation = let
   values = enumFromTo minBound (asTypeOf maxBound (descriptionToA description))
   descriptionToA = undefined :: ValueFormat a -> a
-  description = EnumValueFormat (fmap (TextBuilder.string . show) values)
+  description = EnumValueFormat (fmap valueRepresentation values)
   in description
 
 {-|
@@ -236,6 +291,11 @@
 
 renderIfNotEmpty :: TextBuilder.Builder -> Maybe Text
 renderIfNotEmpty = fmap TextBuilder.run . validate (not . TextBuilder.null)
+
+prefixIfMakesSense :: Text -> Text -> Text
+prefixIfMakesSense prefix text = if Text.null prefix
+  then text
+  else prefix <> "-" <> text
 
 
 -- ** Mods
diff --git a/optima.cabal b/optima.cabal
--- a/optima.cabal
+++ b/optima.cabal
@@ -1,5 +1,5 @@
 name: optima
-version: 0.1.2
+version: 0.2
 category: CLI, Parsing, Options
 synopsis: Simple command line interface arguments parser
 homepage: https://github.com/metrix-ai/optima
