diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,6 +5,17 @@
 The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
 and this project adheres to the [Haskell Package Versioning Policy](https://pvp.haskell.org/).
 
+## [1.1.0] - 2026-07-21
+
+### Added
+
+- `prop`, `fprop`, `xprop` combinators
+- `runHUnit` re-export
+
+### Removed
+
+- `HasCallStack` and `Spec` re-exports
+
 ## [1.0.1] - 2026-07-13
 
 ### Added
diff --git a/hspec-effectful.cabal b/hspec-effectful.cabal
--- a/hspec-effectful.cabal
+++ b/hspec-effectful.cabal
@@ -1,6 +1,6 @@
 cabal-version: 3.0
 name: hspec-effectful
-version: 1.0.1
+version: 1.1.0
 synopsis: Effectful driver for Hspec
 category: Test
 description:
@@ -61,6 +61,7 @@
   import: common
   hs-source-dirs: src
   build-depends:
+    QuickCheck >=2.12 && <3,
     hspec >=2.11 && <2.12,
     hspec-core >=2.11 && <2.12,
     hspec-expectations >=0.8 && <0.9,
diff --git a/src/Effectful/Hspec.hs b/src/Effectful/Hspec.hs
--- a/src/Effectful/Hspec.hs
+++ b/src/Effectful/Hspec.hs
@@ -2,6 +2,7 @@
 {-# LANGUAGE Trustworthy #-}
 {-# OPTIONS_GHC -Wno-missing-role-annotations #-}
 {-# OPTIONS_GHC -Wno-orphans #-}
+{-# OPTIONS_GHC -Wno-term-variable-capture #-}
 
 -- |
 -- Module      : Effectful.Hspec
@@ -36,7 +37,7 @@
 -- >             put $ funds - amount
 -- >             pure True
 --
--- Use 'describe' to group related examples, 'it' to state individual expectations,
+-- Use 'describe' to group related examples, 'it' to state individual expectations
 -- and setup/teardown combinators such as 'before_', 'after_', or 'around_' to
 -- run the examples from a known initial state:
 --
@@ -65,6 +66,7 @@
     , runHspecWith
     , runHspec'
     , runHspec
+    , runHUnit
 
       -- * Spec construction
     , describe
@@ -112,10 +114,15 @@
     , shouldNotReturn
     , shouldThrow
     , anyException
+
+      -- * Properties
+    , prop
+    , xprop
+    , fprop
+
+      -- * Config
     , Config (..)
     , defaultConfig
-    , Hspec.Spec
-    , HasCallStack
     )
 where
 
@@ -131,8 +138,10 @@
 import Test.Hspec.Core.Spec (ResultStatus (..))
 import Test.Hspec.Expectations (Selector, anyException)
 import Test.Hspec.Expectations.Pretty.Matcher (matchList)
+import Test.Hspec.QuickCheck qualified as Hspec
 import Test.Hspec.Runner (Config (..), defaultConfig)
 import Test.Hspec.Runner qualified as Hspec
+import Test.QuickCheck qualified as QuickCheck
 import Prelude
 
 data Hspec :: Effect
@@ -174,160 +183,139 @@
     Hspec{unlift} <- getStaticRep
     unsafeEff_ . unlift . inject $ action
 
+appendSpec :: (Hspec :> es) => Hspec.Spec -> Eff es ()
+appendSpec spec = stateStaticRep \hspec -> ((), hspec{spec = hspec.spec >> spec})
+
 mapSpec :: (HasCallStack, Hspec :> es) => (Hspec.Spec -> Hspec.Spec) -> Eff es a -> Eff es a
 mapSpec f action =
     stateStaticRepM \before -> do
         putStaticRep before{spec = pure ()}
         result <- action
-        after <- getStaticRep
-        pure (result, before{spec = spec before >> f (spec after)})
+        after <- getStaticRep @Hspec
+        pure (result, before{spec = before.spec >> f after.spec})
 
--- | Lifted 'Hspec.describe'
+-- | Lifted 'Hspec.describe'.
 describe :: (HasCallStack, Hspec :> es) => String -> Eff es a -> Eff es a
 describe = mapSpec . Hspec.describe
 
--- | Lifted 'Hspec.it'
+-- | Lifted 'Hspec.it'.
 it :: (HasCallStack, Hspec :> es) => String -> Eff es () -> Eff es ()
 it label action = do
     ioAction <-
         unsafeConcUnliftIO Ephemeral Unlimited \runInIO ->
             pure (runInIO action)
-    stateStaticRep \hspec -> ((), hspec{spec = spec hspec >> Hspec.it label ioAction})
+    appendSpec $ Hspec.it label ioAction
 
--- | Lifted 'Hspec.specify'
+-- | Lifted 'Hspec.specify'.
 specify :: (HasCallStack, Hspec :> es) => String -> Eff es () -> Eff es ()
 specify = it
 
--- | Lifted 'Hspec.context'
+-- | Lifted 'Hspec.context'.
 context :: (HasCallStack, Hspec :> es) => String -> Eff es a -> Eff es a
 context = describe
 
--- | Lifted 'Hspec.pending'
+-- | Lifted 'Hspec.pending'.
 pending :: (HasCallStack, Hspec :> es) => Eff es ()
 pending = getStaticRep @Hspec >> unsafeEff_ Hspec.pending
 
 pending_ :: Eff es ()
 pending_ = throwIO $ Pending Nothing Nothing
 
--- | Lifted 'Hspec.pendingWith'
+-- | Lifted 'Hspec.pendingWith'.
 pendingWith :: (HasCallStack, Hspec :> es) => String -> Eff es ()
 pendingWith = (getStaticRep @Hspec >>) . unsafeEff_ . Hspec.pendingWith
 
--- | Lifted 'Hspec.xit'
+-- | Lifted 'Hspec.xit'.
 xit :: (HasCallStack, Hspec :> es) => String -> Eff es () -> Eff es ()
 xit = (before_ pending_ .) . it
 
--- | Lifted 'Hspec.xspecify'
+-- | Lifted 'Hspec.xspecify'.
 xspecify :: (HasCallStack, Hspec :> es) => String -> Eff es () -> Eff es ()
 xspecify = xit
 
--- | Lifted 'Hspec.xdescribe'
+-- | Lifted 'Hspec.xdescribe'.
 xdescribe :: (HasCallStack, Hspec :> es) => String -> Eff es a -> Eff es a
 xdescribe = (before_ pending_ .) . describe
 
--- | Lifted 'Hspec.xcontext'
+-- | Lifted 'Hspec.xcontext'.
 xcontext :: (HasCallStack, Hspec :> es) => String -> Eff es a -> Eff es a
 xcontext = xdescribe
 
--- | Lifted 'Hspec.focus'
+-- | Lifted 'Hspec.focus'.
 focus :: (HasCallStack, Hspec :> es) => Eff es a -> Eff es a
 focus = mapSpec Hspec.focus
 
--- | Lifted 'Hspec.fit'
+-- | Lifted 'Hspec.fit'.
 fit :: (HasCallStack, Hspec :> es) => String -> Eff es () -> Eff es ()
 fit = fmap focus . it
 
--- | Lifted 'Hspec.fspecify'
+-- | Lifted 'Hspec.fspecify'.
 fspecify :: (HasCallStack, Hspec :> es) => String -> Eff es () -> Eff es ()
 fspecify = fit
 
--- | Lifted 'Hspec.fdescribe'
+-- | Lifted 'Hspec.fdescribe'.
 fdescribe :: (HasCallStack, Hspec :> es) => String -> Eff es a -> Eff es a
 fdescribe = fmap focus . describe
 
--- | Lifted 'Hspec.fcontext'
+-- | Lifted 'Hspec.fcontext'.
 fcontext :: (HasCallStack, Hspec :> es) => String -> Eff es a -> Eff es a
 fcontext = fdescribe
 
--- | Lifted 'Hspec.parallel'
+-- | Lifted 'Hspec.parallel'.
 parallel :: (HasCallStack, Hspec :> es) => Eff es a -> Eff es a
 parallel = mapSpec Hspec.parallel
 
--- | Lifted 'Hspec.sequential'
+-- | Lifted 'Hspec.sequential'.
 sequential :: (HasCallStack, Hspec :> es) => Eff es a -> Eff es a
 sequential = mapSpec Hspec.sequential
 
--- | Lifted 'Hspec.before_'
+-- | Lifted 'Hspec.before_'.
 before_ :: (HasCallStack, Hspec :> es) => Eff es () -> Eff es a -> Eff es a
 before_ setup action = do
     ioSetup <-
         unsafeConcUnliftIO Ephemeral Unlimited \unlift ->
             pure (unlift setup)
-    stateStaticRepM \prev -> do
-        putStaticRep prev{spec = pure ()}
-        result <- action
-        after <- getStaticRep
-        pure (result, prev{spec = spec prev >> Hspec.before_ ioSetup (spec after)})
+    mapSpec (Hspec.before_ ioSetup) action
 
--- | Lifted 'Hspec.beforeAll_'
+-- | Lifted 'Hspec.beforeAll_'.
 beforeAll_ :: (HasCallStack, Hspec :> es) => Eff es () -> Eff es a -> Eff es a
 beforeAll_ setup action = do
     ioSetup <-
         unsafeConcUnliftIO Ephemeral Unlimited \unlift ->
             pure (unlift setup)
-    stateStaticRepM \prev -> do
-        putStaticRep prev{spec = pure ()}
-        result <- action
-        after <- getStaticRep
-        pure (result, prev{spec = spec prev >> Hspec.beforeAll_ ioSetup (spec after)})
+    mapSpec (Hspec.beforeAll_ ioSetup) action
 
--- | Lifted 'Hspec.after_'
+-- | Lifted 'Hspec.after_'.
 after_ :: (HasCallStack, Hspec :> es) => Eff es () -> Eff es a -> Eff es a
 after_ teardown action = do
     ioTeardown <-
         unsafeConcUnliftIO Ephemeral Unlimited \unlift ->
             pure (unlift teardown)
-    stateStaticRepM \prev -> do
-        putStaticRep prev{spec = pure ()}
-        result <- action
-        after <- getStaticRep
-        pure (result, prev{spec = spec prev >> Hspec.after_ ioTeardown (spec after)})
+    mapSpec (Hspec.after_ ioTeardown) action
 
--- | Lifted 'Hspec.afterAll_'
+-- | Lifted 'Hspec.afterAll_'.
 afterAll_ :: (HasCallStack, Hspec :> es) => Eff es () -> Eff es a -> Eff es a
 afterAll_ teardown action = do
     ioTeardown <-
         unsafeConcUnliftIO Ephemeral Unlimited \unlift ->
             pure (unlift teardown)
-    stateStaticRepM \prev -> do
-        putStaticRep prev{spec = pure ()}
-        result <- action
-        after <- getStaticRep
-        pure (result, prev{spec = spec prev >> Hspec.afterAll_ ioTeardown (spec after)})
+    mapSpec (Hspec.afterAll_ ioTeardown) action
 
--- | Lifted 'Hspec.around_'
+-- | Lifted 'Hspec.around_'.
 around_ :: (HasCallStack, Hspec :> es) => (Eff es () -> Eff es ()) -> Eff es a -> Eff es a
 around_ wrapper action = do
     ioWrapper <-
         unsafeConcUnliftIO Ephemeral Unlimited \unlift ->
             pure (unlift . wrapper . unsafeEff_)
-    stateStaticRepM \prev -> do
-        putStaticRep prev{spec = pure ()}
-        result <- action
-        after <- getStaticRep
-        pure (result, prev{spec = spec prev >> Hspec.around_ ioWrapper (spec after)})
+    mapSpec (Hspec.around_ ioWrapper) action
 
--- | Lifted 'Hspec.aroundAll_'
+-- | Lifted 'Hspec.aroundAll_'.
 aroundAll_ :: (HasCallStack, Hspec :> es) => (Eff es () -> Eff es ()) -> Eff es a -> Eff es a
 aroundAll_ wrapper action = do
     ioWrapper <-
         unsafeConcUnliftIO Ephemeral Unlimited \unlift ->
             pure (unlift . wrapper . unsafeEff_)
-    stateStaticRepM \prev -> do
-        putStaticRep prev{spec = pure ()}
-        result <- action
-        after <- getStaticRep
-        pure (result, prev{spec = spec prev >> Hspec.aroundAll_ ioWrapper (spec after)})
+    mapSpec (Hspec.aroundAll_ ioWrapper) action
 
 expectationFailure :: (HasCallStack, Hspec :> es) => String -> Expectation es
 expectationFailure msg = hunit $ HUnit.assertFailure msg
@@ -350,14 +338,12 @@
 
 infix 1 `shouldNotBe`, `shouldNotSatisfy`, `shouldNotContain`, `shouldNotReturn`
 
--- |
--- @actual \`shouldBe\` expected@ sets the expectation that @actual@ is equal
+-- | @actual \`shouldBe\` expected@ sets the expectation that @actual@ is equal
 -- to @expected@.
 shouldBe :: (HasCallStack, Show a, Eq a, Hspec :> es) => a -> a -> Expectation es
 actual `shouldBe` expected = hunit $ actual @?= expected
 
--- |
--- @v \`shouldSatisfy\` p@ sets the expectation that @p v@ is @True@.
+-- | @v \`shouldSatisfy\` p@ sets the expectation that @p v@ is @True@.
 shouldSatisfy :: (HasCallStack, Show a, Hspec :> es) => a -> (a -> Bool) -> Expectation es
 v `shouldSatisfy` p = expectTrue ("predicate failed on: " ++ show v) (p v)
 
@@ -372,55 +358,46 @@
   where
     errorMsg = show result ++ " " ++ errorDesc ++ " " ++ show expected
 
--- |
--- @list \`shouldStartWith\` prefix@ sets the expectation that @list@ starts with @prefix@,
+-- | @list \`shouldStartWith\` prefix@ sets the expectation that @list@ starts with @prefix@.
 shouldStartWith :: (HasCallStack, Show a, Eq a, Hspec :> es) => [a] -> [a] -> Expectation es
 shouldStartWith = compareWith List.isPrefixOf "does not start with"
 
--- |
--- @list \`shouldEndWith\` suffix@ sets the expectation that @list@ ends with @suffix@,
+-- | @list \`shouldEndWith\` suffix@ sets the expectation that @list@ ends with @suffix@.
 shouldEndWith :: (HasCallStack, Show a, Eq a, Hspec :> es) => [a] -> [a] -> Expectation es
 shouldEndWith = compareWith List.isSuffixOf "does not end with"
 
--- |
--- @list \`shouldContain\` sublist@ sets the expectation that @sublist@ is contained,
+-- | @list \`shouldContain\` sublist@ sets the expectation that @sublist@ is contained,
 -- wholly and intact, anywhere in @list@.
 shouldContain :: (HasCallStack, Show a, Eq a, Hspec :> es) => [a] -> [a] -> Expectation es
 shouldContain = compareWith List.isInfixOf "does not contain"
 
--- |
--- @xs \`shouldMatchList\` ys@ sets the expectation that @xs@ has the same
--- elements that @ys@ has, possibly in another order
+-- | @xs \`shouldMatchList\` ys@ sets the expectation that @xs@ has the same
+-- elements that @ys@ has, possibly in another order.
 shouldMatchList :: (HasCallStack, Show a, Eq a, Hspec :> es) => [a] -> [a] -> Expectation es
 xs `shouldMatchList` ys = maybe (return ()) expectationFailure (matchList xs ys)
 
--- |
--- @action \`shouldReturn\` expected@ sets the expectation that @action@
+-- | @action \`shouldReturn\` expected@ sets the expectation that @action@
 -- returns @expected@.
 shouldReturn :: (HasCallStack, Show a, Eq a, Hspec :> es) => Eff es a -> a -> Expectation es
 action `shouldReturn` expected = action >>= (`shouldBe` expected)
 
--- |
--- @actual \`shouldNotBe\` notExpected@ sets the expectation that @actual@ is not
--- equal to @notExpected@
+-- | @actual \`shouldNotBe\` notExpected@ sets the expectation that @actual@ is not
+-- equal to @notExpected@.
 shouldNotBe :: (HasCallStack, Show a, Eq a, Hspec :> es) => a -> a -> Expectation es
 actual `shouldNotBe` notExpected = expectTrue ("not expected: " ++ show actual) (actual /= notExpected)
 
--- |
--- @v \`shouldNotSatisfy\` p@ sets the expectation that @p v@ is @False@.
+-- | @v \`shouldNotSatisfy\` p@ sets the expectation that @p v@ is @False@.
 shouldNotSatisfy :: (HasCallStack, Show a, Hspec :> es) => a -> (a -> Bool) -> Expectation es
 v `shouldNotSatisfy` p = expectTrue ("predicate succeeded on: " ++ show v) ((not . p) v)
 
--- |
--- @list \`shouldNotContain\` sublist@ sets the expectation that @sublist@ is not
+-- | @list \`shouldNotContain\` sublist@ sets the expectation that @sublist@ is not
 -- contained anywhere in @list@.
 shouldNotContain :: (HasCallStack, Show a, Eq a, Hspec :> es) => [a] -> [a] -> Expectation es
 list `shouldNotContain` sublist = expectTrue errorMsg (not $ sublist `List.isInfixOf` list)
   where
     errorMsg = show list ++ " does contain " ++ show sublist
 
--- |
--- @action \`shouldNotReturn\` notExpected@ sets the expectation that @action@
+-- | @action \`shouldNotReturn\` notExpected@ sets the expectation that @action@
 -- does not return @notExpected@.
 shouldNotReturn
     :: (HasCallStack, Show a, Eq a, Hspec :> es)
@@ -429,8 +406,7 @@
     -> Expectation es
 action `shouldNotReturn` notExpected = action >>= (`shouldNotBe` notExpected)
 
--- |
--- @action \`shouldThrow\` selector@ sets the expectation that @action@ throws
+-- | @action \`shouldThrow\` selector@ sets the expectation that @action@ throws
 -- an exception.  The precise nature of the expected exception is described
 -- with a 'Selector'.
 shouldThrow
@@ -453,3 +429,15 @@
       where
         instanceOf :: Selector a -> a
         instanceOf _ = error "Effectful.Hspec.shouldThrow: broken Typeable instance"
+
+-- | Lifted 'Hspec.prop'.
+prop :: (HasCallStack, QuickCheck.Testable prop, Hspec :> es) => String -> prop -> Eff es ()
+prop = (appendSpec .) . Hspec.prop
+
+-- | Lifted 'Hspec.xprop'.
+xprop :: (HasCallStack, QuickCheck.Testable prop, Hspec :> es) => String -> prop -> Eff es ()
+xprop = (appendSpec .) . Hspec.xprop
+
+-- | Lifted 'Hspec.fprop'.
+fprop :: (HasCallStack, QuickCheck.Testable prop, Hspec :> es) => String -> prop -> Eff es ()
+fprop = (appendSpec .) . Hspec.fprop
