diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,8 @@
+## Fourmolu 0.3.0.0
+
+* New config option `newlines-between-decls`, to choose the number of blank lines between top-level declarations.
+* Minor CLI improvements. In particular, the set of valid values for each option is communicated more consistently.
+
 ## Fourmolu 0.2.0.0
 
 * More consistent indentation. Previously, with indentation set to n, some constructs such as nested lists and tuples would use an ugly mix of n-space and 2-space indentation.
diff --git a/README.md b/README.md
--- a/README.md
+++ b/README.md
@@ -37,6 +37,7 @@
 diff-friendly-import-export: true # 'false' uses Ormolu-style lists
 respectful: true # don't be too opinionated about newlines etc.
 haddock-style: multi-line # '--' vs. '{-'
+newlines-between-decls: 1 # number of newlines between top-level declarations
 ```
 
 See [here](fourmolu.yaml) for a config to simulate the behaviour of Ormolu.
diff --git a/app/Main.hs b/app/Main.hs
--- a/app/Main.hs
+++ b/app/Main.hs
@@ -1,7 +1,10 @@
+{-# LANGUAGE AllowAmbiguousTypes #-}
 {-# LANGUAGE ApplicativeDo #-}
 {-# LANGUAGE CPP #-}
 {-# LANGUAGE LambdaCase #-}
+{-# LANGUAGE RankNTypes #-}
 {-# LANGUAGE RecordWildCards #-}
+{-# LANGUAGE ScopedTypeVariables #-}
 {-# LANGUAGE TemplateHaskell #-}
 {-# LANGUAGE TypeApplications #-}
 
@@ -10,8 +13,8 @@
 import Control.Exception (SomeException, displayException, try)
 import Control.Monad
 import Data.Bool (bool)
-import Data.Char (toLower)
 import Data.Either (lefts)
+import Data.Functor.Identity (Identity (..))
 import Data.List (intercalate, sort)
 import Data.Maybe (fromMaybe)
 import qualified Data.Text.IO as TIO
@@ -114,7 +117,7 @@
   | -- | Exit with non-zero status code if
     -- source is not already formatted
     Check
-  deriving (Eq, Show)
+  deriving (Eq, Show, Bounded, Enum)
 
 optsParserInfo :: ParserInfo Opts
 optsParserInfo =
@@ -157,7 +160,7 @@
             [ short 'i',
               help "A shortcut for --mode inplace"
             ]
-            <|> (option parseMode . mconcat)
+            <|> (option parseBoundedEnum . mconcat)
               [ long "mode",
                 short 'm',
                 metavar "MODE",
@@ -216,81 +219,147 @@
     (optional . option auto . mconcat)
       [ long "indentation",
         metavar "WIDTH",
-        help "Number of spaces per indentation step (default 4)"
+        help $
+          "Number of spaces per indentation step"
+            <> showDefaultValue poIndentation
       ]
   poCommaStyle <-
-    (optional . option parseCommaStyle . mconcat)
+    (optional . option parseBoundedEnum . mconcat)
       [ long "comma-style",
         metavar "STYLE",
-        help "How to place commas in multi-line lists, records etc: 'leading' (default) or 'trailing'"
+        help $
+          "How to place commas in multi-line lists, records etc: "
+            <> showAllValues @CommaStyle
+            <> showDefaultValue poCommaStyle
       ]
   poIndentWheres <-
-    (optional . option parseBool . mconcat)
+    (optional . option parseBoundedEnum . mconcat)
       [ long "indent-wheres",
         metavar "BOOL",
         help $
           "Whether to indent 'where' bindings past the preceding body"
-            <> " (rather than half-indenting the 'where' keyword) (default 'false')"
+            <> " (rather than half-indenting the 'where' keyword)"
+            <> showDefaultValue poIndentWheres
       ]
   poRecordBraceSpace <-
-    (optional . option parseBool . mconcat)
+    (optional . option parseBoundedEnum . mconcat)
       [ long "record-brace-space",
         metavar "BOOL",
-        help "Whether to leave a space before an opening record brace (default 'false')"
+        help $
+          "Whether to leave a space before an opening record brace"
+            <> showDefaultValue poRecordBraceSpace
       ]
   poDiffFriendlyImportExport <-
-    (optional . option parseBool . mconcat)
+    (optional . option parseBoundedEnum . mconcat)
       [ long "diff-friendly-import-export",
         metavar "BOOL",
         help $
           "Whether to make use of extra commas in import/export lists"
-            <> " (as opposed to Ormolu's style) (default 'true')"
+            <> " (as opposed to Ormolu's style)"
+            <> showDefaultValue poDiffFriendlyImportExport
       ]
   poRespectful <-
-    (optional . option parseBool . mconcat)
+    (optional . option parseBoundedEnum . mconcat)
       [ long "respectful",
         metavar "BOOL",
-        help "Give the programmer more choice on where to insert blank lines (default 'true')"
+        help $
+          "Give the programmer more choice on where to insert blank lines"
+            <> showDefaultValue poRespectful
       ]
   poHaddockStyle <-
-    (optional . option parseHaddockStyle . mconcat)
+    (optional . option parseBoundedEnum . mconcat)
       [ long "haddock-style",
         metavar "STYLE",
-        help "How to print Haddock comments (default 'multi-line')"
+        help $
+          "How to print Haddock comments: "
+            <> showAllValues @HaddockPrintStyle
+            <> showDefaultValue poHaddockStyle
       ]
+  poNewlinesBetweenDecls <-
+    (optional . option auto . mconcat)
+      [ long "newlines-between-decls",
+        metavar "HEIGHT",
+        help "Number of spaces between top-level declarations (default 1)"
+      ]
   pure PrinterOpts {..}
 
 ----------------------------------------------------------------------------
 -- Helpers
 
--- | Parse 'Mode'.
-parseMode :: ReadM Mode
-parseMode = eitherReader $ \case
-  "stdout" -> Right Stdout
-  "inplace" -> Right InPlace
-  "check" -> Right Check
-  s -> Left $ "unknown mode: " ++ s
+-- | A standard parser of CLI option arguments, applicable to arguments that
+-- have a finite (preferably small) number of possible values. (Basically an
+-- inverse of 'toCLIArgument'.)
+parseBoundedEnum ::
+  forall a.
+  (Enum a, Bounded a, ToCLIArgument a) =>
+  ReadM a
+parseBoundedEnum =
+  eitherReader
+    ( \s ->
+        case lookup s argumentToValue of
+          Just v -> Right v
+          Nothing ->
+            Left $
+              "unknown value: '"
+                <> s
+                <> "'\nValid values are: "
+                <> showAllValues @a
+                <> "."
+    )
+  where
+    argumentToValue = map (\x -> (toCLIArgument x, x)) [minBound ..]
 
--- | Parse 'CommaStyle'.
-parseCommaStyle :: ReadM CommaStyle
-parseCommaStyle = eitherReader $ \case
-  "leading" -> Right Leading
-  "trailing" -> Right Trailing
-  s -> Left $ "unknown comma style: " ++ s
+-- | Values that appear as arguments of CLI options and thus have
+-- a corresponding textual representation.
+class ToCLIArgument a where
+  -- | Convert a value to its representation as a CLI option argument.
+  toCLIArgument :: a -> String
 
--- | Parse 'HaddockStyle'.
-parseHaddockStyle :: ReadM HaddockPrintStyle
-parseHaddockStyle = eitherReader $ \case
-  "single-line" -> Right HaddockSingleLine
-  "multi-line" -> Right HaddockMultiLine
-  s -> Left $ "unknown haddock style: " ++ s
+  -- | Convert a value to its representation as a CLI option argument wrapped
+  -- in apostrophes.
+  toCLIArgument' :: a -> String
+  toCLIArgument' x = "'" <> toCLIArgument x <> "'"
 
--- | Parse a 'Bool'. Unlike 'auto', this is not case sensitive.
-parseBool :: ReadM Bool
-parseBool = eitherReader $ \x -> case map toLower x of
-  "false" -> Right False
-  "true" -> Right True
-  s -> Left $ "not a boolean value: " ++ s
+instance ToCLIArgument Bool where
+  toCLIArgument True = "true"
+  toCLIArgument False = "false"
+
+instance ToCLIArgument CommaStyle where
+  toCLIArgument Leading = "leading"
+  toCLIArgument Trailing = "trailing"
+
+instance ToCLIArgument Int where
+  toCLIArgument = show
+
+instance ToCLIArgument HaddockPrintStyle where
+  toCLIArgument HaddockSingleLine = "single-line"
+  toCLIArgument HaddockMultiLine = "multi-line"
+
+instance ToCLIArgument Mode where
+  toCLIArgument Stdout = "stdout"
+  toCLIArgument InPlace = "inplace"
+  toCLIArgument Check = "check"
+
+showAllValues :: forall a. (Enum a, Bounded a, ToCLIArgument a) => String
+showAllValues = format (map toCLIArgument' [(minBound :: a) ..])
+  where
+    format [] = []
+    format [x] = x
+    format [x1, x2] = x1 <> " or " <> x2
+    format (x : xs) = x <> ", " <> format xs
+
+-- | CLI representation of the default value of an option, formatted for
+-- inclusion in the help text.
+showDefaultValue ::
+  ToCLIArgument a =>
+  (PrinterOptsTotal -> Identity a) ->
+  String
+showDefaultValue =
+  (" (default " <>)
+    . (<> ")")
+    . toCLIArgument'
+    . runIdentity
+    . ($ defaultPrinterOpts)
 
 -- | Build the full config, by adding 'PrinterOpts' from a file, if found.
 mkConfig :: FilePath -> Opts -> IO (Config RegionIndices)
diff --git a/fourmolu.cabal b/fourmolu.cabal
--- a/fourmolu.cabal
+++ b/fourmolu.cabal
@@ -1,6 +1,6 @@
 cabal-version:   1.18
 name:            fourmolu
-version:         0.2.0.0
+version:         0.3.0.0
 license:         BSD3
 license-file:    LICENSE.md
 maintainer:
diff --git a/src/Ormolu/Config.hs b/src/Ormolu/Config.hs
--- a/src/Ormolu/Config.hs
+++ b/src/Ormolu/Config.hs
@@ -118,7 +118,9 @@
     -- | Be less opinionated about spaces/newlines etc.
     poRespectful :: f Bool,
     -- | How to print doc comments
-    poHaddockStyle :: f HaddockPrintStyle
+    poHaddockStyle :: f HaddockPrintStyle,
+    -- | Number of newlines between top-level decls
+    poNewlinesBetweenDecls :: f Int
   }
   deriving (Generic)
 
@@ -134,7 +136,7 @@
   (<>) = fillMissingPrinterOpts
 
 instance Monoid PrinterOptsPartial where
-  mempty = PrinterOpts Nothing Nothing Nothing Nothing Nothing Nothing Nothing
+  mempty = PrinterOpts Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing
 
 -- | A version of 'PrinterOpts' without empty fields.
 type PrinterOptsTotal = PrinterOpts Identity
@@ -152,7 +154,8 @@
       poRecordBraceSpace = pure False,
       poDiffFriendlyImportExport = pure True,
       poRespectful = pure True,
-      poHaddockStyle = pure HaddockMultiLine
+      poHaddockStyle = pure HaddockMultiLine,
+      poNewlinesBetweenDecls = pure 1
     }
 
 -- | Fill the field values that are 'Nothing' in the first argument
@@ -171,7 +174,8 @@
       poRecordBraceSpace = fillField poRecordBraceSpace,
       poDiffFriendlyImportExport = fillField poDiffFriendlyImportExport,
       poRespectful = fillField poRespectful,
-      poHaddockStyle = fillField poHaddockStyle
+      poHaddockStyle = fillField poHaddockStyle,
+      poNewlinesBetweenDecls = fillField poNewlinesBetweenDecls
     }
   where
     fillField :: (forall g. PrinterOpts g -> g a) -> f a
@@ -180,7 +184,7 @@
 data CommaStyle
   = Leading
   | Trailing
-  deriving (Eq, Ord, Show, Generic)
+  deriving (Eq, Ord, Show, Generic, Bounded, Enum)
 
 instance FromJSON CommaStyle where
   parseJSON =
@@ -192,7 +196,7 @@
 data HaddockPrintStyle
   = HaddockSingleLine
   | HaddockMultiLine
-  deriving (Eq, Ord, Show, Generic)
+  deriving (Eq, Ord, Show, Generic, Bounded, Enum)
 
 instance FromJSON HaddockPrintStyle where
   parseJSON =
diff --git a/src/Ormolu/Printer/Combinators.hs b/src/Ormolu/Printer/Combinators.hs
--- a/src/Ormolu/Printer/Combinators.hs
+++ b/src/Ormolu/Printer/Combinators.hs
@@ -20,6 +20,7 @@
     atom,
     space,
     newline,
+    declNewline,
     inci,
     inciIf,
     inciBy,
diff --git a/src/Ormolu/Printer/Internal.hs b/src/Ormolu/Printer/Internal.hs
--- a/src/Ormolu/Printer/Internal.hs
+++ b/src/Ormolu/Printer/Internal.hs
@@ -18,6 +18,7 @@
     atom,
     space,
     newline,
+    declNewline,
     useRecordDot,
     inci,
     inciBy,
@@ -316,6 +317,9 @@
         other -> other
     }
 
+declNewline :: R ()
+declNewline = newlineRawN =<< getPrinterOpt poNewlinesBetweenDecls
+
 -- | Output a newline. First time 'newline' is used after some non-'newline'
 -- output it gets inserted immediately. Second use of 'newline' does not
 -- output anything but makes sure that the next non-white space output will
@@ -353,15 +357,20 @@
 -- | Low-level newline primitive. This one always just inserts a newline, no
 -- hooks can be attached.
 newlineRaw :: R ()
-newlineRaw = R . modify $ \sc ->
+newlineRaw = newlineRawN 1
+
+-- | Low-level newline primitive. This always inserts 'n' newlines.
+newlineRawN :: Int -> R ()
+newlineRawN n = R . modify $ \sc ->
   let requestedDel = scRequestedDelimiter sc
       builderSoFar = scBuilder sc
+      n' = case requestedDel of
+        AfterNewline -> n - 1
+        RequestedNewline -> n - 1
+        VeryBeginning -> n - 1
+        _ -> n
    in sc
-        { scBuilder = case requestedDel of
-            AfterNewline -> builderSoFar
-            RequestedNewline -> builderSoFar
-            VeryBeginning -> builderSoFar
-            _ -> builderSoFar <> "\n",
+        { scBuilder = builderSoFar <> mconcat (replicate n' "\n"),
           scColumn = 0,
           scIndent = 0,
           scThisLineSpans = [],
diff --git a/src/Ormolu/Printer/Meat/Declaration.hs b/src/Ormolu/Printer/Meat/Declaration.hs
--- a/src/Ormolu/Printer/Meat/Declaration.hs
+++ b/src/Ormolu/Printer/Meat/Declaration.hs
@@ -68,12 +68,12 @@
       -- ensure we always add blank lines around documented declarations
       case grouping of
         Disregard ->
-          breakpoint : renderGroup curr
+          declNewline : renderGroup curr
         Respect ->
           if separatedByBlankNE getLoc prev curr
             || isDocumented prev
             || isDocumented curr
-            then breakpoint : renderGroup curr
+            then declNewline : renderGroup curr
             else renderGroup curr
 
 -- | Is a declaration group documented?
diff --git a/src/Ormolu/Printer/Meat/Module.hs b/src/Ormolu/Printer/Meat/Module.hs
--- a/src/Ormolu/Printer/Meat/Module.hs
+++ b/src/Ormolu/Printer/Meat/Module.hs
@@ -79,7 +79,7 @@
     forM_ (normalizeImports preserveGroups hsmodImports) $ \importGroup -> do
       forM_ importGroup (located' (p_hsmodImport qualifiedPost))
       newline
-    newline
+    declNewline
     switchLayout (getLoc <$> hsmodDecls) $ do
       preserveSpacing <- getPrinterOpt poRespectful
       (if preserveSpacing then p_hsDeclsRespectGrouping else p_hsDecls) Free hsmodDecls
diff --git a/src/Ormolu/Utils.hs b/src/Ormolu/Utils.hs
--- a/src/Ormolu/Utils.hs
+++ b/src/Ormolu/Utils.hs
@@ -23,7 +23,7 @@
 
 import Data.Char (isSpace)
 import Data.List (dropWhileEnd)
-import Data.List.NonEmpty (NonEmpty (..))
+import Data.List.NonEmpty (NonEmpty (..), nonEmpty)
 import qualified Data.List.NonEmpty as NE
 import Data.Maybe (fromMaybe)
 import Data.Text (Text)
@@ -135,7 +135,9 @@
 removeIndentation :: String -> (String, Int)
 removeIndentation (lines -> xs) = (unlines (drop n <$> xs), n)
   where
-    n = minimum (getIndent <$> xs)
+    n = case nonEmpty xs of
+      Nothing -> 0
+      Just l -> minimum (getIndent <$> l)
     getIndent y =
       if all isSpace y
         then 0
diff --git a/tests/Ormolu/PrinterSpec.hs b/tests/Ormolu/PrinterSpec.hs
--- a/tests/Ormolu/PrinterSpec.hs
+++ b/tests/Ormolu/PrinterSpec.hs
@@ -27,7 +27,8 @@
             poRecordBraceSpace = pure True,
             poDiffFriendlyImportExport = pure False,
             poRespectful = pure False,
-            poHaddockStyle = pure HaddockSingleLine
+            poHaddockStyle = pure HaddockSingleLine,
+            poNewlinesBetweenDecls = pure 1
           }
   sequence_ $ uncurry checkExample <$> [(ormoluOpts, ""), (defaultPrinterOpts, "-four")] <*> es
 
