diff --git a/src/Data/Geometry/YX.hs b/src/Data/Geometry/YX.hs
--- a/src/Data/Geometry/YX.hs
+++ b/src/Data/Geometry/YX.hs
@@ -14,7 +14,7 @@
   steps4, steps8,
   -- * Box
   Box, box, arrayBox, boundingBox,
-  topLeft, bottomRight,
+  boxBounds, topLeft, bottomRight,
   boxHeight, boxWidth,
   inBox, boxRange, boxRows, boxIntersection,
   boxNeighbors4, boxNeighbors8,
@@ -94,11 +94,15 @@
 steps8 :: [YX]
 steps8 = [up + left, up, up + right, left, right, down + left, down, down + right]
 
--- | A 2D box.
+-- | A non-empty 2D box.
 --
--- A box might have zero width or height.
+-- A box might have zero width or height but will always contain at least one point.
 data Box = Box { _topLeft :: !YX , _bottomRight :: !YX } deriving (Eq, Show)
 
+-- | @since 0.0.4.1
+instance Semigroup Box where
+  (Box tl1 br1) <> (Box tl2 br2) = Box (tl1 /\ tl2) (br1 \/ br2)
+
 -- | Constructs a box from its extremities, returning 'Nothing' if the points are not ordered
 -- appropriately.
 box
@@ -128,6 +132,12 @@
 bottomRight :: Box -> YX
 bottomRight = _bottomRight
 
+-- | Returns the box' bounds, @(topLeft, bottomRight)@.
+--
+-- @since 0.0.4.1
+boxBounds :: Box -> (YX, YX)
+boxBounds (Box tl br) = (tl, br)
+
 -- | Returns the height of the box, always non-negative.
 boxHeight :: Box -> Int
 boxHeight (Box (YX y0 _) (YX y1 _)) = y1 - y0
@@ -206,7 +216,9 @@
 -- | Parses a newline delimited bytestring into an array in an effectful way.
 --
 -- @since 0.0.4.0
-byteStringToArrayM :: (IArray a e, MonadError String m) => (YX -> Char -> m e) -> ByteString -> m (a YX e)
+byteStringToArrayM
+  :: (IArray a e, MonadError String m)
+  => (YX -> Char -> m e) -> ByteString -> m (a YX e)
 byteStringToArrayM f bs = shape (BS.split '\n' bs) (-1) >>= materialize bs where
   shape [] (YX y0 x0) = pure (YX y0 (max x0 0))
   shape rows@(row : rows') yx@(YX y0 x0)
diff --git a/test/Spec.hs b/test/Spec.hs
--- a/test/Spec.hs
+++ b/test/Spec.hs
@@ -19,6 +19,18 @@
       boundingBox [] `shouldBe` Nothing
     it "can be built simple inputs" $ do
       boundingBox [YX 2 1, YX 2 2, YX 1 0] `shouldBe` box (YX 1 0) (YX 2 2)
+    it "returns bounds" $ do
+      let
+        tl = YX 1 0
+        br = YX 2 2
+        Just b = box tl br
+      boxBounds b `shouldBe` (tl, br)
+    it "can be combined" $ do
+      let
+        Just b1 = box 0 1
+        Just b2 = box 1 2
+        Just b3 = box 0 2
+      b1 <> b2 `shouldBe` b3
     it "checks membership" $ do
       let Just b = box (YX 1 0) (YX 2 2)
       YX 1 0 `inBox` b `shouldBe` True
diff --git a/yx.cabal b/yx.cabal
--- a/yx.cabal
+++ b/yx.cabal
@@ -1,5 +1,5 @@
 name:                yx
-version:             0.0.4.0
+version:             0.0.4.1
 synopsis:            Row-major coordinates
 description:         A minimal library for handling 2D coordinates.
 homepage:            https://github.com/mtth/yx
