diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,7 @@
+# 0.2.0.0 - 2025-08-29
+
+- Allow customizing how code blocks are wrapped
+
 # 0.1.2.0
 
 - Adapt to Alectryon 1.4.0
diff --git a/LICENSE b/LICENSE
--- a/LICENSE
+++ b/LICENSE
@@ -1,4 +1,4 @@
-Copyright (c) 2020 Li-yao Xia
+Copyright (c) 2020-2025 Li-yao Xia
 
 Permission is hereby granted, free of charge, to any person obtaining
 a copy of this software and associated documentation files (the
diff --git a/README.md b/README.md
--- a/README.md
+++ b/README.md
@@ -117,7 +117,7 @@
     (...)
     compile $ do
       (...)
-      Alectryon.tryTransform opts doc >>= (...)
+      Alectryon.tryTransform opts rawHTML doc >>= (...)
 ```
 
 When writing a post, build your site with the option `--run-alectryon` to interpret
diff --git a/Setup.hs b/Setup.hs
deleted file mode 100644
--- a/Setup.hs
+++ /dev/null
@@ -1,2 +0,0 @@
-import Distribution.Simple
-main = defaultMain
diff --git a/hakyll-alectryon.cabal b/hakyll-alectryon.cabal
--- a/hakyll-alectryon.cabal
+++ b/hakyll-alectryon.cabal
@@ -1,6 +1,6 @@
 cabal-version:       2.4
 name:                hakyll-alectryon
-version:             0.1.2.0
+version:             0.2.0.0
 synopsis:            Hakyll extension for rendering Coq code using Alectryon
 description:
   An extension to write Literate Coq blog posts using Hakyll.
@@ -15,24 +15,24 @@
 license-file:        LICENSE
 author:              Li-yao Xia
 maintainer:          lysxia@gmail.com
-copyright:           Li-yao Xia 2020
+copyright:           Li-yao Xia 2020-2025
 category:            Text, Web
-extra-source-files:  CHANGELOG.md, README.md
+extra-doc-files:     CHANGELOG.md, README.md
 
 library
   exposed-modules:
     Hakyll.Alectryon
   build-depends:
-    aeson,
-    hakyll >= 4,
-    mtl,
-    optparse-applicative,
-    pandoc,
-    pandoc-types,
-    filepath,
-    process,
-    bytestring,
-    text,
+    aeson < 2.3,
+    hakyll >= 4 && < 4.17,
+    mtl < 2.4,
+    optparse-applicative < 0.20,
+    pandoc < 3.8,
+    pandoc-types < 1.24,
+    filepath < 1.6,
+    process < 1.7,
+    bytestring < 0.13,
+    text < 2.2,
     base >=4.9 && < 5
   hs-source-dirs:      src
   ghc-options: -Wall
diff --git a/src/Hakyll/Alectryon.hs b/src/Hakyll/Alectryon.hs
--- a/src/Hakyll/Alectryon.hs
+++ b/src/Hakyll/Alectryon.hs
@@ -28,6 +28,10 @@
   , transformAlectryon
   , transformPygments
 
+    -- ** Block wrappers
+  , rawHTML
+  , divBlock
+
     -- * Options
   , Options(..)
   , defaultOptions
@@ -74,7 +78,7 @@
 import System.FilePath ((</>))
 import System.Process (readProcess)
 
-import Text.Pandoc (Block(CodeBlock, RawBlock), Pandoc(..))
+import Text.Pandoc (Block(CodeBlock, RawBlock, Div), Pandoc(..))
 import Text.Pandoc.Walk (query, walkM)
 import Hakyll.Core.File (TmpFile(..), newTmpFile)
 import Hakyll
@@ -97,11 +101,11 @@
 -- | If the @alectryon@ flag is set, 'transform'.
 --
 -- Do nothing if @alectryon@ flag is not set.
-tryTransform :: Options -> Item Pandoc -> Compiler (Item Pandoc)
-tryTransform opt idoc = do
+tryTransform :: Options -> TextToBlock -> Item Pandoc -> Compiler (Item Pandoc)
+tryTransform opt toBlock idoc = do
   m <- getMetadata (itemIdentifier idoc)
   case lookupStringList "alectryon" m <|> enable of
-    Just args -> withItemBody (transform opt (lookupString "alectryon-cache" m) args) idoc
+    Just args -> withItemBody (transform opt (lookupString "alectryon-cache" m) args toBlock) idoc
     Nothing -> pure idoc
   where
     enable = if enableAll opt then Just [] else Nothing
@@ -110,9 +114,10 @@
 --
 -- Do nothing if @alectryon@ flag is not set.
 --
--- This is 'tryTransform' with all options enabled.
+-- This is 'tryTransform' with all options enabled
+-- and using 'rawHTML' to construct code blocks.
 tryTransform_ :: Item Pandoc -> Compiler (Item Pandoc)
-tryTransform_ = tryTransform enabledOptions
+tryTransform_ = tryTransform enabledOptions rawHTML
 
 -- | Convert @alectryon@ and @coq@ code blocks to HTML using Alectryon and
 -- pygments respectively.
@@ -120,10 +125,11 @@
   Options ->
   Maybe FilePath {- ^ Cache directory location -} ->
   [String] {- ^ Alectryon options (e.g., Coq @-Q@, @-R@) -} ->
+  TextToBlock ->
   Pandoc -> Compiler Pandoc
-transform opt cache args =
-  transformAlectryon opt ((</> "alectryon.html") <$> cache) args
-    >=> transformPygments opt ((</> "pygments.html") <$> cache)
+transform opt cache args toBlock =
+  transformAlectryon opt ((</> "alectryon.html") <$> cache) args toBlock
+    >=> transformPygments opt ((</> "pygments.html") <$> cache) toBlock
 
 -- | Convert @alectryon@ code blocks to HTML using Alectryon.
 --
@@ -132,10 +138,11 @@
   Options ->
   Maybe FilePath {- ^ Cache file location -} ->
   [String] {- ^ Alectryon options (e.g., Coq @-Q@, @-R@) -} ->
+  TextToBlock ->
   Pandoc -> Compiler Pandoc
-transformAlectryon opt cache args doc = do
+transformAlectryon opt cache args toBlock doc = do
     snips <- doRunAlectryon opt cache args blocks
-    pure (updateAlectryonBlocks snips doc)
+    pure (updateAlectryonBlocks toBlock snips doc)
   where
     blocks = query getBlock doc
     getBlock = onAlectryonBlocks [] (: [])
@@ -144,10 +151,11 @@
 transformPygments ::
   Options ->
   Maybe FilePath {- ^ Cache file location -} ->
+  TextToBlock ->
   Pandoc -> Compiler Pandoc
-transformPygments opt cache doc = do
+transformPygments opt cache toBlock doc = do
     snips <- doRunPygments opt cache blocks
-    pure (updateBlocks coqName snips doc)
+    pure (updateBlocks toBlock coqName snips doc)
   where
     blocks = query getBlock doc
     getBlock = onCoqBlocks [] (: [])
@@ -160,22 +168,37 @@
 coqName :: Text
 coqName = "coq"
 
-nextBlock :: State [Text] Block
-nextBlock = do
+-- | Convert Alectryon and Pygments output (raw HTML) to Pandoc 'Block'.
+--
+-- Basic wrappers included in this module:
+-- - 'rawHTML'
+-- - 'divBlock'
+type TextToBlock = Text -> Block
+
+-- | Include Alectryon blocks as-is.
+rawHTML :: TextToBlock
+rawHTML = RawBlock "html"
+
+-- | Wrap Alectryon blocks in @<div class=\"...\">@.
+divBlock :: [Text] -> TextToBlock
+divBlock attrs = Div ("", attrs, []) . (:[]) . rawHTML
+
+nextBlock :: (Text -> Block) -> State [Text] Block
+nextBlock f = do
   xs <- get
   case xs of
     [] -> error "No blocks left"
     x : xs -> do
       put xs
-      pure (RawBlock "html" x)
+      pure (f x)
 
-updateAlectryonBlocks :: [Text] -> Pandoc -> Pandoc
-updateAlectryonBlocks = updateBlocks name
+updateAlectryonBlocks :: (Text -> Block) -> [Text] -> Pandoc -> Pandoc
+updateAlectryonBlocks f = updateBlocks f name
 
-updateBlocks :: Text -> [Text] -> Pandoc -> Pandoc
-updateBlocks name bs = fst . flip runState bs . walkM setBlock
+updateBlocks :: (Text -> Block) -> Text -> [Text] -> Pandoc -> Pandoc
+updateBlocks f name bs = fst . flip runState bs . walkM setBlock
   where
-    setBlock b = onBlocks name (pure b) (\_ -> nextBlock) b
+    setBlock b = onBlocks name (pure b) (\_ -> nextBlock f) b
 
 onCoqBlocks :: b -> (Text -> b) -> Block -> b
 onCoqBlocks = onBlocks coqName
