diff --git a/prop-unit.cabal b/prop-unit.cabal
--- a/prop-unit.cabal
+++ b/prop-unit.cabal
@@ -5,7 +5,7 @@
 -- see: https://github.com/sol/hpack
 
 name:           prop-unit
-version:        0.1.4
+version:        0.2.0
 synopsis:       Conveniences for using Hedgehog as a unit test runner
 description:    Please see the README on GitHub at <https://github.com/ejconlon/prop-unit#readme>
 category:       Testing
@@ -101,6 +101,7 @@
   ghc-options: -Wall -Wcompat -Widentities -Wincomplete-record-updates -Wincomplete-uni-patterns -Wpartial-fields -Wredundant-constraints -fno-warn-unused-top-binds -fwrite-ide-info -hiedir=.hie -threaded -rtsopts -with-rtsopts=-N
   build-depends:
       base >=4.12 && <5
+    , containers
     , hedgehog >=1.4 && <1.6
     , prop-unit
     , tasty
diff --git a/src/PropUnit.hs b/src/PropUnit.hs
--- a/src/PropUnit.hs
+++ b/src/PropUnit.hs
@@ -1,3 +1,5 @@
+{-# LANGUAGE UndecidableInstances #-}
+
 module PropUnit
   ( DependencyType (..)
   , Gen
@@ -20,10 +22,25 @@
   , testGroup
   , testMain
   , withResource
+  , GenDefault (..)
+  , genDefaultTag
+  , genDefaultIntegral
+  , genDefaultEnum
+  , genDefaultList
+  , genDefaultString
+  , genDefaultGeneric
+  , Std
   )
 where
 
+import Control.Applicative ((<|>))
 import Control.Monad (when)
+import Data.Int (Int16, Int32, Int64, Int8)
+import Data.Proxy (Proxy (..))
+import Data.Word (Word16, Word32, Word64, Word8)
+import GHC.Exts (IsList (..), IsString (..))
+import GHC.Generics (Generic (..), K1 (..), M1 (..), U1 (..), (:*:) (..), (:+:) (..))
+import GHC.TypeLits (KnownNat, Nat, natVal)
 import Hedgehog
   ( DiscardLimit
   , Gen
@@ -44,6 +61,8 @@
   , (/==)
   , (===)
   )
+import qualified Hedgehog.Gen as Gen
+import qualified Hedgehog.Range as Range
 import System.Environment (lookupEnv, setEnv)
 import System.IO (BufferMode (..), hSetBuffering, stderr, stdout)
 import Test.Tasty (DependencyType (..), TestName, TestTree, after, defaultMain, testGroup, withResource)
@@ -82,3 +101,147 @@
 testMain f = do
   lim <- setupTests
   defaultMain (f lim)
+
+class GenDefault tag a where
+  -- | Default generator for @a@
+  --
+  -- The type-level @tag@ allows types @a@ to have multiple defaults.
+  genDefault :: Proxy tag -> Gen a
+
+-- | DerivingVia wrapper for types with default instances under other tags
+newtype ViaTag tag' a = ViaTag {unViaTag :: a}
+
+instance (GenDefault tag' a) => GenDefault tag (ViaTag tag' a) where
+  genDefault _ = fmap ViaTag (genDefault @tag' Proxy)
+
+genDefaultTag :: forall tag a tag'. (GenDefault tag' a) => Proxy tag' -> Proxy tag -> Gen a
+genDefaultTag _ _ = fmap (unViaTag @tag' @a) (genDefault (Proxy @tag))
+
+-- | DerivingVia wrapper for Integral types
+newtype ViaIntegral a = ViaIntegral {unViaIntegral :: a}
+
+instance (Integral a, Bounded a) => GenDefault tag (ViaIntegral a) where
+  genDefault _ = fmap ViaIntegral (Gen.integral (Range.constantFrom 0 minBound maxBound))
+
+genDefaultIntegral :: forall tag a. (Integral a, Bounded a) => Proxy tag -> Gen a
+genDefaultIntegral _ = fmap (unViaIntegral @a) (genDefault (Proxy @tag))
+
+-- | DerivingVia wrapper for Enum types
+newtype ViaEnum a = ViaEnum {unViaEnum :: a}
+
+instance (Enum a, Bounded a) => GenDefault tag (ViaEnum a) where
+  genDefault _ = fmap ViaEnum Gen.enumBounded
+
+genDefaultEnum :: forall tag a. (Enum a, Bounded a) => Proxy tag -> Gen a
+genDefaultEnum _ = fmap (unViaEnum @a) (genDefault (Proxy @tag))
+
+-- | DerivingVia wrapper for FromList types
+newtype ViaList a (mn :: Nat) (mx :: Nat) = ViaList {unViaList :: a}
+
+instance (IsList a, GenDefault tag (Item a), KnownNat mn, KnownNat mx) => GenDefault tag (ViaList a mn mx) where
+  genDefault p =
+    let bn = fromInteger (natVal (Proxy @mn))
+        bx = fromInteger (natVal (Proxy @mx))
+    in  fmap (ViaList . fromList) (Gen.list (Range.constant bn bx) (genDefault p))
+
+genDefaultList
+  :: forall tag a mn mx
+   . (IsList a, KnownNat mn, KnownNat mx, GenDefault tag (Item a))
+  => Proxy mn
+  -> Proxy mx
+  -> Proxy tag
+  -> Gen a
+genDefaultList _ _ _ = fmap (unViaList @a @mn @mx) (genDefault (Proxy @tag))
+
+-- | DerivingVia wrapper for FromString types
+newtype ViaString s (mn :: Nat) (mx :: Nat) = ViaString {unViaString :: s}
+
+instance (IsString s, GenDefault tag Char, KnownNat mn, KnownNat mx) => GenDefault tag (ViaString s mn mx) where
+  genDefault p =
+    let bn = fromInteger (natVal (Proxy @mn))
+        bx = fromInteger (natVal (Proxy @mx))
+    in  fmap (ViaString . fromString) (Gen.list (Range.constant bn bx) (genDefault p))
+
+genDefaultString
+  :: forall tag a mn mx
+   . (IsString a, KnownNat mn, KnownNat mx, GenDefault tag Char)
+  => Proxy mn
+  -> Proxy mx
+  -> Proxy tag
+  -> Gen a
+genDefaultString _ _ _ = fmap (unViaString @a @mn @mx) (genDefault (Proxy @tag))
+
+class GGenDefault tag f where
+  ggenDefault :: Proxy tag -> Gen (f a)
+
+instance GGenDefault tag U1 where
+  ggenDefault _ = pure U1
+
+instance (GGenDefault tag a) => GGenDefault tag (M1 i c a) where
+  ggenDefault = fmap M1 . ggenDefault
+
+instance (GGenDefault tag a, GGenDefault tag b) => GGenDefault tag (a :*: b) where
+  ggenDefault p = liftA2 (:*:) (ggenDefault p) (ggenDefault p)
+
+instance (GGenDefault tag a, GGenDefault tag b) => GGenDefault tag (a :+: b) where
+  ggenDefault p = fmap L1 (ggenDefault p) <|> fmap R1 (ggenDefault p)
+
+instance (GenDefault tag a) => GGenDefault tag (K1 i a) where
+  ggenDefault = fmap K1 . genDefault
+
+-- | DerivingVia wrapper for Generic types
+newtype ViaGeneric tag a = ViaGeneric {unViaGeneric :: a}
+
+instance (Generic a, GGenDefault tag (Rep a)) => GenDefault tag (ViaGeneric tag a) where
+  genDefault = fmap (ViaGeneric . to) . ggenDefault
+
+genDefaultGeneric :: forall tag a. (Generic a, GGenDefault tag (Rep a)) => Proxy tag -> Gen a
+genDefaultGeneric _ = fmap (unViaGeneric @tag @a) (genDefault (Proxy @tag))
+
+-- | Type tag for these "standard" default generators.
+-- You can use this tag directly or choose type-by-type with 'ViaTag'.
+data Std
+
+instance GenDefault Std () where genDefault = genDefaultEnum
+
+instance GenDefault Std Bool where genDefault = genDefaultEnum
+
+instance GenDefault Std Char where genDefault = genDefaultEnum
+
+instance GenDefault Std Int where genDefault = genDefaultIntegral
+
+instance GenDefault Std Int8 where genDefault = genDefaultIntegral
+
+instance GenDefault Std Int16 where genDefault = genDefaultIntegral
+
+instance GenDefault Std Int32 where genDefault = genDefaultIntegral
+
+instance GenDefault Std Int64 where genDefault = genDefaultIntegral
+
+instance GenDefault Std Word where genDefault = genDefaultIntegral
+
+instance GenDefault Std Word8 where genDefault = genDefaultIntegral
+
+instance GenDefault Std Word16 where genDefault = genDefaultIntegral
+
+instance GenDefault Std Word32 where genDefault = genDefaultIntegral
+
+instance GenDefault Std Word64 where genDefault = genDefaultIntegral
+
+instance (GenDefault Std a) => GenDefault Std (Maybe a) where genDefault = genDefaultGeneric
+
+instance (GenDefault Std a, GenDefault Std b) => GenDefault Std (Either a b) where genDefault = genDefaultGeneric
+
+instance (GenDefault Std a, GenDefault Std b) => GenDefault Std (a, b) where genDefault = genDefaultGeneric
+
+instance (GenDefault Std a, GenDefault Std b, GenDefault Std c) => GenDefault Std (a, b, c) where
+  genDefault = genDefaultGeneric
+
+instance (GenDefault Std a, GenDefault Std b, GenDefault Std c, GenDefault Std d) => GenDefault Std (a, b, c, d) where
+  genDefault = genDefaultGeneric
+
+instance
+  (GenDefault Std a, GenDefault Std b, GenDefault Std c, GenDefault Std d, GenDefault Std e)
+  => GenDefault Std (a, b, c, d, e)
+  where
+  genDefault = genDefaultGeneric
diff --git a/test/Main.hs b/test/Main.hs
--- a/test/Main.hs
+++ b/test/Main.hs
@@ -1,8 +1,34 @@
+{-# LANGUAGE DataKinds #-}
+{-# LANGUAGE UndecidableInstances #-}
+
 module Main (main) where
 
+import Control.Monad (replicateM)
+import Data.Proxy (Proxy (..))
+import qualified Data.Set as Set
+import GHC.Exts (IsList, IsString)
+import GHC.Generics (Generic)
+import Hedgehog (Gen)
 import qualified Hedgehog.Gen as Gen
 import qualified Hedgehog.Range as Range
-import PropUnit (TestLimit, TestTree, forAll, testGroup, testMain, testProp, testUnit, (===))
+import PropUnit
+  ( GenDefault (..)
+  , Std
+  , TestLimit
+  , TestTree
+  , assert
+  , forAll
+  , genDefaultEnum
+  , genDefaultGeneric
+  , genDefaultList
+  , genDefaultString
+  , genDefaultTag
+  , testGroup
+  , testMain
+  , testProp
+  , testUnit
+  , (===)
+  )
 
 testAsUnit :: TestTree
 testAsUnit = testUnit "as unit" $ do
@@ -15,10 +41,73 @@
   x <- forAll (Gen.int (Range.constant 1 10))
   abs x * abs x === x * x
 
+testBasic :: TestLimit -> TestTree
+testBasic lim =
+  testGroup
+    "basic"
+    [ testAsUnit
+    , testAsProp lim
+    ]
+
+data Tag
+
+instance GenDefault Tag Int where genDefault = genDefaultTag (Proxy @Std)
+
+instance GenDefault Tag Char where genDefault = genDefaultTag (Proxy @Std)
+
+newtype AList a = AList [a]
+  deriving newtype (Eq, Ord, Show, IsList)
+
+instance (GenDefault Tag a) => GenDefault Tag (AList a) where genDefault = genDefaultList (Proxy @0) (Proxy @2)
+
+newtype AString = AString String
+  deriving newtype (Eq, Ord, Show, IsString)
+
+instance GenDefault Tag AString where genDefault = genDefaultString (Proxy @0) (Proxy @2)
+
+data Choice = ChoiceA | ChoiceB
+  deriving stock (Eq, Ord, Show, Enum, Bounded)
+
+instance GenDefault Tag Choice where genDefault = genDefaultEnum
+
+instance (GenDefault Tag a) => GenDefault Tag (Maybe a) where genDefault = genDefaultGeneric
+
+data Record = Record !Int !(Maybe Record)
+  deriving stock (Eq, Ord, Show, Generic)
+
+instance GenDefault Tag Record where genDefault = genDefaultGeneric
+
+data GenCase where
+  GenCase :: (Ord a) => String -> Gen a -> GenCase
+
+genDefaultByProxy :: (GenDefault Tag a) => Proxy a -> Gen a
+genDefaultByProxy _ = genDefault (Proxy @Tag)
+
+mkGenCase :: (Ord a, GenDefault Tag a) => String -> Proxy a -> GenCase
+mkGenCase name = GenCase name . genDefaultByProxy
+
+genCases :: [GenCase]
+genCases =
+  [ mkGenCase "Int" (Proxy @Int)
+  , mkGenCase "Char" (Proxy @Char)
+  , mkGenCase "Choice" (Proxy @Choice)
+  , mkGenCase "AList" (Proxy @(AList Char))
+  , mkGenCase "AString" (Proxy @AString)
+  , mkGenCase "Record" (Proxy @Record)
+  ]
+
+testGenCase :: GenCase -> TestTree
+testGenCase (GenCase name gen) = testUnit name $ do
+  xs <- fmap Set.fromList (replicateM 10 (Gen.sample gen))
+  assert (Set.size xs > 1)
+
+testDerive :: TestTree
+testDerive = testGroup "derive" (fmap testGenCase genCases)
+
 main :: IO ()
 main = testMain $ \lim ->
   testGroup
     "PropUnit"
-    [ testAsUnit
-    , testAsProp lim
+    [ testBasic lim
+    , testDerive
     ]
