diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,16 @@
 Brick changelog
 ---------------
 
+0.69
+----
+
+New features:
+ * `Brick.Widgets.Core`: added `relativeTo` to support relative
+   positioning across layers. This allows elements in higher layers
+   to be positioned relative to elements in lower layers as long as
+   those elements have had their extents reported with `reportExtent` or
+   `clickable`.
+
 0.68.1
 ------
 
diff --git a/README.md b/README.md
--- a/README.md
+++ b/README.md
@@ -83,6 +83,8 @@
 | [`hledger-iadd`](http://github.com/rootzlevel/hledger-iadd) | An interactive terminal UI for adding hledger journal entries |
 | [`wordle`](https://github.com/ivanjermakov/wordle) | An implementation of the Wordle game |
 | [`kpxhs`](https://github.com/akazukin5151/kpxhs) | An interactive [Keepass](https://github.com/keepassxreboot/keepassxc/) database viewer |
+| [`htyper`](https://github.com/Simon-Hostettler/htyper) | A typing speed test program |
+| [`ullekha`](https://github.com/ajithnn/ullekha) | An interactive terminal notes/todo app with file/redis persistence |
 
 These third-party packages also extend `brick`:
 
diff --git a/brick.cabal b/brick.cabal
--- a/brick.cabal
+++ b/brick.cabal
@@ -1,5 +1,5 @@
 name:                brick
-version:             0.68.1
+version:             0.69
 synopsis:            A declarative terminal user interface library
 description:
   Write terminal user interfaces (TUIs) painlessly with 'brick'! You
diff --git a/programs/LayerDemo.hs b/programs/LayerDemo.hs
--- a/programs/LayerDemo.hs
+++ b/programs/LayerDemo.hs
@@ -12,48 +12,71 @@
 import qualified Brick.Main as M
 import qualified Brick.Widgets.Border as B
 import qualified Brick.Widgets.Center as C
+import Brick.Types
+  ( Location(..)
+  )
 import Brick.Widgets.Core
   ( translateBy
   , str
+  , relativeTo
+  , reportExtent
+  , withDefAttr
   )
+import Brick.Util (fg)
 import Brick.AttrMap
   ( attrMap
+  , AttrName
   )
 
 data St =
-    St { _topLayerLocation :: T.Location
+    St { _middleLayerLocation :: T.Location
        , _bottomLayerLocation :: T.Location
        }
 
 makeLenses ''St
 
-drawUi :: St -> [Widget ()]
+data Name =
+    MiddleLayerElement
+    deriving (Ord, Eq, Show)
+
+drawUi :: St -> [Widget Name]
 drawUi st =
     [ C.centerLayer $
       B.border $ str "This layer is centered but other\nlayers are placed underneath it."
-    , topLayer st
+    , arrowLayer
+    , middleLayer st
     , bottomLayer st
     ]
 
-topLayer :: St -> Widget ()
-topLayer st =
-    translateBy (st^.topLayerLocation) $
-    B.border $ str "Top layer\n(Arrow keys move)"
+arrowLayer :: Widget Name
+arrowLayer =
+    let msg = "Relatively\n" <>
+              "positioned\n" <>
+              "arrow---->"
+    in relativeTo MiddleLayerElement (Location (-10, -2)) $
+       withDefAttr arrowAttr $
+       str msg
 
-bottomLayer :: St -> Widget ()
+middleLayer :: St -> Widget Name
+middleLayer st =
+    translateBy (st^.middleLayerLocation) $
+    reportExtent MiddleLayerElement $
+    B.border $ str "Middle layer\n(Arrow keys move)"
+
+bottomLayer :: St -> Widget Name
 bottomLayer st =
     translateBy (st^.bottomLayerLocation) $
     B.border $ str "Bottom layer\n(Ctrl-arrow keys move)"
 
-appEvent :: St -> T.BrickEvent () e -> T.EventM () (T.Next St)
+appEvent :: St -> T.BrickEvent Name e -> T.EventM Name (T.Next St)
 appEvent st (T.VtyEvent (V.EvKey V.KDown []))  =
-    M.continue $ st & topLayerLocation.locationRowL %~ (+ 1)
+    M.continue $ st & middleLayerLocation.locationRowL %~ (+ 1)
 appEvent st (T.VtyEvent (V.EvKey V.KUp []))    =
-    M.continue $ st & topLayerLocation.locationRowL %~ (subtract 1)
+    M.continue $ st & middleLayerLocation.locationRowL %~ (subtract 1)
 appEvent st (T.VtyEvent (V.EvKey V.KRight [])) =
-    M.continue $ st & topLayerLocation.locationColumnL %~ (+ 1)
+    M.continue $ st & middleLayerLocation.locationColumnL %~ (+ 1)
 appEvent st (T.VtyEvent (V.EvKey V.KLeft []))  =
-    M.continue $ st & topLayerLocation.locationColumnL %~ (subtract 1)
+    M.continue $ st & middleLayerLocation.locationColumnL %~ (subtract 1)
 
 appEvent st (T.VtyEvent (V.EvKey V.KDown  [V.MCtrl])) =
     M.continue $ st & bottomLayerLocation.locationRowL %~ (+ 1)
@@ -67,14 +90,17 @@
 appEvent st (T.VtyEvent (V.EvKey V.KEsc [])) = M.halt st
 appEvent st _ = M.continue st
 
-app :: M.App St e ()
+arrowAttr :: AttrName
+arrowAttr = "attr"
+
+app :: M.App St e Name
 app =
     M.App { M.appDraw = drawUi
           , M.appStartEvent = return
           , M.appHandleEvent = appEvent
-          , M.appAttrMap = const $ attrMap V.defAttr []
+          , M.appAttrMap = const $ attrMap V.defAttr [(arrowAttr, fg V.cyan)]
           , M.appChooseCursor = M.neverShowCursor
           }
 
 main :: IO ()
-main = void $ M.defaultMain app $ St (T.Location (0, 0)) (T.Location (0, 0))
+main = void $ M.defaultMain app $ St (T.Location (20, 5)) (T.Location (0, 0))
diff --git a/programs/MouseDemo.hs b/programs/MouseDemo.hs
--- a/programs/MouseDemo.hs
+++ b/programs/MouseDemo.hs
@@ -69,11 +69,7 @@
   B.border $
   C.hCenterLayer $
   vLimit 8 $
-  -- n.b. if clickable and viewport are inverted here, click event
-  -- coordinates will only identify the viewable range, not the actual
-  -- editor widget coordinates.
   viewport Prose Vertical $
-  clickable Prose $
   vBox $ map str $ lines (st^.prose)
 
 infoLayer :: St -> Widget Name
@@ -81,11 +77,13 @@
     c <- T.getContext
     let h = c^.T.availHeightL
         msg = case st^.lastReportedClick of
-                Nothing -> "nothing"
-                Just (name, T.Location l) -> show name <> " at " <> show l
+                Nothing ->
+                    "Click and hold/drag to report a mouse click"
+                Just (name, T.Location l) ->
+                    "Mouse down at " <> show name <> " @ " <> show l
     T.render $ translateBy (T.Location (0, h-1)) $ clickable Info $
                withDefAttr "info" $
-               C.hCenter (str ("Last reported click: " <> msg))
+               C.hCenter $ str msg
 
 appEvent :: St -> T.BrickEvent Name e -> T.EventM Name (T.Next St)
 appEvent st (T.MouseDown n _ _ loc) = do
diff --git a/src/Brick/Main.hs b/src/Brick/Main.hs
--- a/src/Brick/Main.hs
+++ b/src/Brick/Main.hs
@@ -277,11 +277,18 @@
                     run newVty (newRS { renderCache = mempty }) newAppState brickChan
 
     let emptyES = ES [] mempty mempty
-        emptyRS = RS M.empty mempty S.empty mempty mempty mempty
+        emptyRS = RS M.empty mempty S.empty mempty mempty mempty mempty
         eventRO = EventRO M.empty initialVty mempty emptyRS
 
     (st, eState) <- runStateT (runReaderT (runEventM (appStartEvent app initialAppState)) eventRO) emptyES
-    let initialRS = RS M.empty (esScrollRequests eState) S.empty mempty [] (requestedVisibleNames eState)
+    let initialRS = RS { viewportMap = M.empty
+                       , rsScrollRequests = esScrollRequests eState
+                       , observedNames = S.empty
+                       , renderCache = mempty
+                       , clickableNames = []
+                       , requestedVisibleNames_ = requestedVisibleNames eState
+                       , reportedExtents = mempty
+                       }
     brickChan <- newBChan 20
     run initialVty initialRS st brickChan
 
@@ -452,7 +459,7 @@
     s & observedNamesL .~ S.empty
       & clickableNamesL .~ mempty
 
-renderApp :: Vty -> App s e n -> s -> RenderState n -> IO (RenderState n, [Extent n])
+renderApp :: (Ord n) => Vty -> App s e n -> s -> RenderState n -> IO (RenderState n, [Extent n])
 renderApp vty app appState rs = do
     sz <- displayBounds $ outputIface vty
     let (newRS, pic, theCursor, exts) = renderFinal (appAttrMap app appState)
diff --git a/src/Brick/Types/Internal.hs b/src/Brick/Types/Internal.hs
--- a/src/Brick/Types/Internal.hs
+++ b/src/Brick/Types/Internal.hs
@@ -59,11 +59,13 @@
   , BrickEvent(..)
   , RenderM
   , getContext
+  , lookupReportedExtent
   , Widget(..)
 
   , rsScrollRequestsL
   , viewportMapL
   , clickableNamesL
+  , reportedExtentsL
   , renderCacheL
   , observedNamesL
   , requestedVisibleNames_L
@@ -80,9 +82,11 @@
   )
 where
 
+import Control.Monad.Trans.Class (lift)
 import Control.Monad.Trans.Reader
 import Control.Monad.Trans.State.Lazy
 import Lens.Micro (_1, _2, Lens')
+import Lens.Micro.Mtl (use)
 import Lens.Micro.TH (makeLenses)
 import qualified Data.Set as S
 import qualified Data.Map as M
@@ -140,6 +144,7 @@
        , renderCache :: !(M.Map n ([n], Result n))
        , clickableNames :: ![n]
        , requestedVisibleNames_ :: !(S.Set n)
+       , reportedExtents :: !(M.Map n (Extent n))
        } deriving (Read, Show, Generic, NFData)
 
 -- | The type of the rendering monad. This monad is used by the
@@ -401,3 +406,8 @@
 suffixLenses ''Result
 suffixLenses ''BorderSegment
 makeLenses ''Viewport
+
+lookupReportedExtent :: (Ord n) => n -> RenderM n (Maybe (Extent n))
+lookupReportedExtent n = do
+    m <- lift $ use reportedExtentsL
+    return $ M.lookup n m
diff --git a/src/Brick/Widgets/Core.hs b/src/Brick/Widgets/Core.hs
--- a/src/Brick/Widgets/Core.hs
+++ b/src/Brick/Widgets/Core.hs
@@ -64,8 +64,9 @@
   -- * Naming
   , Named(..)
 
-  -- * Translation
+  -- * Translation and positioning
   , translateBy
+  , relativeTo
 
   -- * Cropping
   , cropLeftBy
@@ -1029,6 +1030,20 @@
       result <- render p
       return $ addResultOffset off
              $ result & imageL %~ (V.translate (off^.locationColumnL) (off^.locationRowL))
+
+-- | Given a widget, translate it to position it relative to the
+-- upper-left coordinates of a reported extent with the specified
+-- positioning offset. This is useful for positioning something in a
+-- higher layer relative to a reported extent in a lower layer. If the
+-- specified name has no reported extent, this just draws the specified
+-- widget with no special positioning.
+relativeTo :: (Show n, Ord n) => n -> Location -> Widget n -> Widget n
+relativeTo n off w =
+    Widget (hSize w) (vSize w) $ do
+        mExt <- lookupReportedExtent n
+        case mExt of
+            Nothing -> render w
+            Just ext -> render $ translateBy (extentUpperLeft ext <> off) w
 
 -- | Crop the specified widget on the left by the specified number of
 -- columns. Defers to the cropped widget for growth policy.
diff --git a/src/Brick/Widgets/Internal.hs b/src/Brick/Widgets/Internal.hs
--- a/src/Brick/Widgets/Internal.hs
+++ b/src/Brick/Widgets/Internal.hs
@@ -8,8 +8,11 @@
 where
 
 import Lens.Micro ((^.), (&), (%~))
+import Lens.Micro.Mtl ((%=))
+import Control.Monad (forM_)
 import Control.Monad.Trans.State.Lazy
 import Control.Monad.Trans.Reader
+import qualified Data.Map as M
 import Data.Maybe (catMaybes)
 import qualified Graphics.Vty as V
 
@@ -20,7 +23,8 @@
 import Brick.BorderMap (BorderMap)
 import qualified Brick.BorderMap as BM
 
-renderFinal :: AttrMap
+renderFinal :: (Ord n)
+            => AttrMap
             -> [Widget n]
             -> V.DisplayRegion
             -> ([CursorLocation n] -> Maybe (CursorLocation n))
@@ -31,7 +35,12 @@
     where
         (layerResults, !newRS) = flip runState rs $ sequence $
             (\p -> runReaderT p ctx) <$>
-            (render <$> cropToContext <$> layerRenders)
+            (\layerWidget -> do
+                result <- render $ cropToContext layerWidget
+                forM_ (result^.extentsL) $ \e ->
+                    reportedExtentsL %= M.insert (extentName e) e
+                return result
+                ) <$> reverse layerRenders
 
         ctx = Context { ctxAttrName = mempty
                       , availWidth = w
@@ -50,14 +59,16 @@
                       , ctxHScrollBarClickableConstr = Nothing
                       , ctxVScrollBarClickableConstr = Nothing
                       }
-        pic = V.picForLayers $ uncurry V.resize (w, h) <$> (^.imageL) <$> layerResults
 
+        layersTopmostFirst = reverse layerResults
+        pic = V.picForLayers $ uncurry V.resize (w, h) <$> (^.imageL) <$> layersTopmostFirst
+
         -- picWithBg is a workaround for runaway attributes.
         -- See https://github.com/coreyoconnor/vty/issues/95
         picWithBg = pic { V.picBackground = V.Background ' ' V.defAttr }
 
-        layerCursors = (^.cursorsL) <$> layerResults
-        layerExtents = reverse $ (^.extentsL) <$> layerResults
+        layerCursors = (^.cursorsL) <$> layersTopmostFirst
+        layerExtents = reverse $ (^.extentsL) <$> layersTopmostFirst
         theCursor = chooseCursor $ concat layerCursors
 
 -- | After rendering the specified widget, crop its result image to the
