diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,20 @@
+Copyright (c) 2025 José Lorenzo Rodríguez
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be included
+in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/README.md b/README.md
new file mode 100644
--- /dev/null
+++ b/README.md
@@ -0,0 +1,142 @@
+# Web View Colonnade
+
+Build HTML tables using [colonnade](https://hackage.haskell.org/package/colonnade) and rendering with the [web-view](https://hackage.haskell.org/package/web-view) library in Haskell.
+
+This library provides functionality similar to `lucid-colonnade` and `blaze-colonnade`, but for the web-view library. It lets you build complex HTML tables with minimal boilerplate.
+
+## Installation
+
+Add the following to your `package.yaml` or `.cabal` file:
+
+```yaml
+dependencies:
+  - colonnade
+  - web-view
+  - web-view-colonnade
+```
+
+## Examples
+
+### Basic Example
+
+```haskell
+{-# LANGUAGE OverloadedStrings #-}
+
+import qualified Data.Text as T
+import qualified Colonnade as C
+import qualified Web.View.View as V
+import qualified Web.View.Element as E
+import Web.View (renderText)
+import WebView.Colonnade
+
+-- Define a data type to represent our data
+data Person = Person
+  { name :: T.Text
+  , age :: Int
+  }
+
+-- Define some data
+people :: [Person]
+people =
+  [ Person "Alice" 30
+  , Person "Bob" 25
+  , Person "Carol" 35
+  ]
+
+-- Define the table structure
+personTable :: C.Colonnade C.Headed Person (V.View c ())
+personTable = mconcat
+  [ C.headed "Name" (E.text . name)
+  , C.headed "Age" (E.text . T.pack . show . age)
+  ]
+
+main :: IO ()
+main = do
+  -- Render a table with custom attributes
+  let html = encodeHtmlTable (V.extClass "person-table") personTable people
+  putStrLn $ renderText html
+```
+
+This produces:
+
+```html
+<table class='person-table'>
+  <thead>
+    <tr>
+      <th>Name</th>
+      <th>Age</th>
+    </tr>
+  </thead>
+  <tbody>
+    <tr>
+      <td>Alice</td>
+      <td>30</td>
+    </tr>
+    <tr>
+      <td>Bob</td>
+      <td>25</td>
+    </tr>
+    <tr>
+      <td>Carol</td>
+      <td>35</td>
+    </tr>
+  </tbody>
+</table>
+```
+
+### Cell Attributes
+
+The `Cell` type allows you to add attributes to individual table cells:
+
+```haskell
+personCellTable :: C.Colonnade C.Headed Person (Cell c)
+personCellTable = mconcat
+  [ C.headed "Name" (\p -> Cell (V.extClass "name-cell") (E.text $ name p))
+  , C.headed "Age" (\p -> Cell (V.extClass "age-cell") (E.text . T.pack . show $ age p))
+  ]
+
+main :: IO ()
+main = do
+  let html = encodeCellTable (V.extClass "person-table") personCellTable people
+  putStrLn $ renderText html
+```
+
+This will render each cell with the appropriate class attributes.
+
+## Contributing
+
+We welcome contributions to web-view-colonnade! This project uses a nix-based devenv shell for development.
+
+### Setting Up the Development Environment
+
+1. Make sure you have [Nix](https://nixos.org/download.html) installed
+2. Install [devenv](https://devenv.sh/getting-started/)
+3. Clone this repository:
+   ```
+   git clone https://github.com/lorenzo/web-view-colonnade.git
+   cd web-view-colonnade
+   ```
+4. Start the development shell:
+   ```
+   devenv shell
+   ```
+5. Build the project:
+   ```
+   cabal build
+   ```
+6. Run the tests:
+   ```
+   cabal test
+   ```
+
+### Pull Requests
+
+1. Fork the repository
+2. Create a new branch for your feature
+3. Add tests for your new feature
+4. Ensure all tests pass
+5. Submit a pull request
+
+## License
+
+This project is licensed under the MIT license - see the LICENSE file for details.
diff --git a/src/WebView/Colonnade.hs b/src/WebView/Colonnade.hs
new file mode 100644
--- /dev/null
+++ b/src/WebView/Colonnade.hs
@@ -0,0 +1,372 @@
+{-# LANGUAGE BangPatterns #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE ScopedTypeVariables #-}
+{-# LANGUAGE TypeFamilies #-}
+{-# LANGUAGE TypeApplications #-}
+
+
+module WebView.Colonnade
+  ( -- * What is this?
+    -- $whatis
+    -- * How to use this library
+    -- $use
+
+    -- * Encoding functions
+    encodeHtmlTable
+  , encodeCellTable
+  , encodeTable
+    -- * Cell
+  , Cell(..)
+  , charCell
+  , stringCell
+  , textCell
+  , htmlCell
+  , htmlFromCell
+  ) where
+
+import Colonnade (Colonnade)
+import qualified Web.View.View as V
+import qualified Web.View.Element as E
+import qualified Colonnade.Encode as E
+import qualified Data.Text as T
+import Data.String (IsString(..))
+import Data.Foldable (for_)
+import Web.View.Types (Mod)
+
+{- $whatis
+  Build HTML tables using @web-view@ and @colonnade@. This module provides
+  functionality similar to @lucid-colonnade@ and @blaze-colonnade@ but for the
+  web-view library.
+-}
+
+{- $use
+
+  = Usage
+
+  We start with a few necessary imports and some example data types:
+
+  >>> :set -XOverloadedStrings
+  >>> import Data.Monoid (mconcat,(<>))
+  >>> import Data.Char (toLower)
+  >>  import qualified Data.Text as T
+  >>> import Data.Profunctor (Profunctor(lmap))
+  >>> import Colonnade (Colonnade,Headed,Headless,headed)
+  >>> import Web.View
+  >>> import qualified Web.View.Style as V 
+  >>> import qualified Web.View.Types as V
+  >>> data Department = Management | Sales | Engineering deriving (Show,Eq)
+  >>> data Employee = Employee { name :: T.Text, department :: Department, age :: Int }
+
+  We define some employees that we will display in a table:
+
+  >>> :{
+  let employees =
+        [ Employee "Thaddeus" Sales 34
+        , Employee "Lucia" Engineering 33
+        , Employee "Pranav" Management 57
+        ]
+  :}
+
+  Let's build a table that displays the name and the age
+  of an employee. Additionally, we will emphasize the names of
+  engineers using a @\<strong\>@ tag.
+
+  >>> :{
+  let tableEmpA :: Colonnade Headed Employee (View c ())
+      tableEmpA = mconcat
+        [ headed "Name" $ \emp -> case department emp of
+            Engineering -> el bold (text (name emp))
+            _ -> text (name emp)
+        , headed "Age" (text . T.pack . show . age)
+        ]
+  :}
+
+  The type signature of @tableEmpA@ is inferrable but is written
+  out for clarity in this example. Note that the first
+  argument to 'headed' can be passed as a string literal due to the @OverloadedStrings@ extension.
+  Let's continue:
+
+  >>> let customAttrs = V.extClass "stylish-table" <> V.att "id" "main-table"
+  >>> renderText (encodeHtmlTable customAttrs tableEmpA employees)
+  <table class='stylish-table' id='main-table'>
+    <thead>
+      <tr>
+        <th>Name</th>
+        <th>Age</th>
+      </tr>
+    </thead>
+    <tbody>
+      <tr>
+        <td>Thaddeus</td>
+        <td>34</td>
+      </tr>
+      <tr>
+        <td><div class="bold">Lucia</div></td>
+        <td>33</td>
+      </tr>
+      <tr>
+        <td>Pranav</td>
+        <td>57</td>
+      </tr>
+    </tbody>
+  </table>
+
+  Excellent. As expected, Lucia's name is wrapped in a @\<strong\>@ tag
+  since she is an engineer.
+
+  One limitation of using @View@ as the content
+  type of a 'Colonnade' is that we are unable to add attributes to
+  the @\<td\>@ and @\<th\>@ elements. This library provides the 'Cell' type
+  to work around this problem. A 'Cell' is just a @V.View@ content and a set
+  of attributes to be applied to its parent @\<th\>@ or @\<td\>@. To illustrate
+  its use, another employee table will be built. This table will
+  contain a single column indicating the department of each employee. Each
+  cell will be assigned a class name based on the department. Let's build a table 
+  that encodes departments:
+
+  >>> :{
+  let tableDept :: Colonnade Headed Department (Cell c)
+      tableDept = mconcat
+        [ headed "Dept." $ \d -> Cell
+            (V.extClass (V.ClassName $ T.pack (map Data.Char.toLower (show d))))
+            (E.text (T.pack (show d)))
+        ]
+  :}
+
+  Again, @OverloadedStrings@ plays a role, this time allowing the
+  literal @"Dept."@ to be accepted as a value of type 'Cell'. To avoid
+  this extension, 'stringCell' could be used to upcast the 'String'.
+  To try out our 'Colonnade' on a list of departments, we need to use
+  'encodeCellTable' instead of 'encodeHtmlTable':
+
+  >>> let twoDepts = [Sales,Management]
+  >>> renderText (encodeCellTable customAttrs tableDept twoDepts)
+  <table class='stylish-table' id='main-table'>
+    <thead>
+      <tr>
+        <th>Dept.</th>
+      </tr>
+    </thead>
+    <tbody>
+      <tr>
+        <td class='sales'>Sales</td>
+      </tr>
+      <tr>
+        <td class='management'>Management</td>
+      </tr>
+    </tbody>
+  </table>
+
+  The attributes on the @\<td\>@ elements show up as they are expected to.
+  Now, we take advantage of the @Profunctor@ instance of 'Colonnade' to allow
+  this to work on @Employee@\'s instead:
+
+  >>> :t lmap
+  lmap :: Profunctor p => (a -> b) -> p b c -> p a c
+  >>> let tableEmpB = lmap department tableDept
+  >>> :t tableEmpB
+  tableEmpB :: Colonnade Headed Employee (Cell c)
+  >>> renderText (encodeCellTable customAttrs tableEmpB employees)
+  <table class='stylish-table' id='main-table'>
+    <thead>
+      <tr>
+        <th>Dept.</th>
+      </tr>
+    </thead>
+    <tbody>
+      <tr>
+        <td class='sales'>Sales</td>
+      </tr>
+      <tr>
+        <td class='engineering'>Engineering</td>
+      </tr>
+      <tr>
+        <td class='management'>Management</td>
+      </tr>
+    </tbody>
+  </table>
+
+  This table shows the department of each of our three employees, additionally
+  making a lowercased version of the department into a class name for the @\<td\>@.
+  This table is nice for illustrative purposes, but it does not provide all the
+  information that we have about the employees. If we combine it with the
+  earlier table we wrote, we can present everything in the table. One small
+  roadblock is that the types of @tableEmpA@ and @tableEmpB@ do not match, which
+  prevents a straightforward monoidal append:
+
+  >>> :t tableEmpA
+  tableEmpA :: Colonnade Headed Employee (V.View c ())
+  >>> :t tableEmpB
+  tableEmpB :: Colonnade Headed Employee (Cell c)
+
+  We can upcast the content type with 'fmap':
+
+  >>> let tableEmpC = fmap htmlCell tableEmpA <> tableEmpB
+  >>> :t tableEmpC
+  tableEmpC :: Colonnade Headed Employee (Cell c)
+  >>> renderText (encodeCellTable customAttrs tableEmpC employees)
+  <table class='stylish-table' id='main-table'>
+    <thead>
+      <tr>
+        <th>Name</th>
+        <th>Age</th>
+        <th>Dept.</th>
+      </tr>
+    </thead>
+    <tbody>
+      <tr>
+        <td>Thaddeus</td>
+        <td>34</td>
+        <td class='sales'>Sales</td>
+      </tr>
+      <tr>
+        <td><strong>Lucia</strong></td>
+        <td>33</td>
+        <td class='engineering'>Engineering</td>
+      </tr>
+      <tr>
+        <td>Pranav</td>
+        <td>57</td>
+        <td class='management'>Management</td>
+      </tr>
+    </tbody>
+  </table>
+-}
+
+-- | A table cell with attributes and content
+data Cell c = Cell
+  { cellAttributes :: Mod c  -- ^ Attributes for the td/th element
+  , cellHtml :: V.View c ()      -- ^ Content inside the cell
+  }
+
+instance IsString (Cell c) where
+  fromString = stringCell
+
+instance Semigroup (Cell c) where
+  Cell attrs1 content1 <> Cell attrs2 content2 = 
+    Cell (attrs1 <> attrs2) (content1 >> content2)
+
+instance Monoid (Cell c) where
+  mempty = Cell mempty (pure ())
+  mappend = (<>)
+
+-- | Create a cell from HTML content
+htmlCell :: V.View c () -> Cell c
+htmlCell content = Cell mempty content
+
+-- | Create a cell from a string
+stringCell :: String -> Cell c
+stringCell = htmlCell . E.text . T.pack
+
+-- | Create a cell from a character
+charCell :: Char -> Cell c
+charCell = stringCell . pure
+
+-- | Create a cell from text
+textCell :: T.Text -> Cell c
+textCell = htmlCell . E.text
+
+-- | Convert a cell to an HTML element
+htmlFromCell :: (Mod c -> V.View c () -> V.View c ()) -> (Cell c) -> V.View c ()
+htmlFromCell f (Cell attrs content) = f attrs content
+
+-- | Encode a table with HTML content
+encodeHtmlTable ::
+  forall h f x c.
+  (E.Headedness h, Foldable f) =>
+  -- | Attributes of @\<table\>@ element
+  Mod c ->
+  -- | How to encode data as columns
+  Colonnade h x (V.View c ()) ->
+  -- | Collection of data
+  f x ->
+  V.View c ()
+encodeHtmlTable =
+  encodeTable
+    (E.headednessPure (mempty, mempty))
+    mempty
+    (const mempty)
+    (\tagFn content -> tagFn mempty content)
+
+-- | Encode a table with cells that may have attributes
+encodeCellTable ::
+  forall h f x c.
+  (E.Headedness h, Foldable f) =>
+  -- | Attributes of @\<table\>@ element
+  Mod c ->
+  -- | How to encode data as columns
+  Colonnade h x (Cell c) ->
+  -- | Collection of data
+  f x ->
+  V.View c ()
+encodeCellTable =
+  encodeTable
+    (E.headednessPure (mempty, mempty))
+    mempty
+    (const mempty)
+    htmlFromCell
+
+{- | Encode a table. This handles a very general case and
+  is seldom needed by users. One of the arguments provided is
+  used to add attributes to the generated @\<tr\>@ elements.
+-}
+encodeTable ::
+  forall h f x v c.
+  (E.Headedness h, Foldable f) =>
+  -- | Attributes and structure for header section
+  h (Mod c, Mod c) ->
+  -- | Attributes for tbody element
+  Mod c ->
+  -- | Attributes for each tr element
+  (x -> Mod c) ->
+  -- | Cell wrapper function
+  ((Mod c -> V.View c () -> V.View c ()) -> v -> V.View c ()) ->
+  -- | Table attributes
+  Mod c ->
+  -- | How to encode data as columns
+  Colonnade h x v ->
+  -- | Collection of data
+  f x ->
+  V.View c ()
+encodeTable mtheadAttrs tbodyAttrs trAttrs wrapContent tableAttrs colonnade xs =
+  V.tag "table" tableAttrs $ do
+    d1 <- case E.headednessExtractForall of
+      Nothing -> pure mempty
+      Just extractForall -> do
+        let (theadAttrs, theadTrAttrs) = extract mtheadAttrs
+        V.tag "thead" theadAttrs $
+          V.tag "tr" theadTrAttrs $ do
+            foldlMapM' (wrapContent (V.tag "th") . extract . E.oneColonnadeHead) (E.getColonnade colonnade)
+        where
+          extract :: forall y. h y -> y
+          extract = E.runExtractForall extractForall
+    d2 <- encodeBody trAttrs wrapContent tbodyAttrs colonnade xs
+    pure (d1 <> d2)
+
+foldlMapM' :: forall g b a m. (Foldable g, Monoid b, Monad m) => (a -> m b) -> g a -> m b
+foldlMapM' f xs = foldr f' pure xs mempty
+ where
+  f' :: a -> (b -> m b) -> b -> m b
+  f' x k bl = do
+    br <- f x
+    let !b = mappend bl br
+    k b
+
+encodeBody ::
+  (Foldable f) =>
+  -- | Attributes of each @\<tr\>@ element
+  (a -> Mod c) ->
+  -- | Wrap content and convert to 'Html'
+  ((Mod c -> V.View c () -> V.View c ()) -> v -> V.View c ()) ->
+  -- | Attributes of @\<tbody\>@ element
+  Mod c ->
+  -- | How to encode data as a row
+  Colonnade h a v ->
+  -- | Collection of data
+  f a ->
+  V.View c ()
+encodeBody trAttrs wrapContent tbodyAttrs colonnade xs = do
+  V.tag "tbody" tbodyAttrs $ do
+    for_ xs $ \x -> do
+      V.tag "tr" (trAttrs x) $ do
+        E.rowMonadic colonnade (wrapContent (V.tag "td")) x
diff --git a/test/Main.hs b/test/Main.hs
new file mode 100644
--- /dev/null
+++ b/test/Main.hs
@@ -0,0 +1,8 @@
+module Main (main) where
+
+import Test.Hspec
+import qualified WebView.ColonnadeSpec
+
+main :: IO ()
+main = hspec $ do
+  describe "WebView.Colonnade" WebView.ColonnadeSpec.spec
diff --git a/test/WebView/ColonnadeSpec.hs b/test/WebView/ColonnadeSpec.hs
new file mode 100644
--- /dev/null
+++ b/test/WebView/ColonnadeSpec.hs
@@ -0,0 +1,174 @@
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE QuasiQuotes #-}
+module WebView.ColonnadeSpec (spec) where
+
+import Test.Hspec
+import WebView.Colonnade
+import qualified Web.View.View as V
+import qualified Web.View.Style as V
+import qualified Web.View.Element as E
+import Web.View (renderText)
+import qualified Colonnade as C
+import qualified Colonnade.Encode as E
+import qualified Data.Text as T
+import Data.String (IsString(fromString))
+import Data.String.Interpolate (i)
+
+data Person = Person
+  { name :: T.Text
+  , age :: Int
+  } deriving (Show, Eq)
+
+spec :: Spec
+spec = do
+  describe "Cell" $ do
+    it "implements IsString" $ do
+      let s = "test"
+          cell = fromString s :: Cell c
+      let result = renderText (htmlFromCell (\_ -> V.tag "td" mempty) cell)
+      result `shouldBe` [i|<td>#{s}</td>|]
+
+    it "implements Semigroup" $ do
+      let s1 = "test1"
+          s2 = "test2"
+          cell1 = fromString s1 :: Cell c
+          cell2 = fromString s2 :: Cell c
+          combined = cell1 <> cell2
+      let result = renderText (htmlFromCell (\_ -> V.tag "td" mempty) combined)
+      result `shouldBe` [i|<td>
+  #{s1}#{s2}</td>|]
+
+    it "implements Monoid" $ do
+      let s = "test"
+          cell = fromString s :: Cell c
+      let result1 = renderText (htmlFromCell (\_ -> V.tag "td" mempty) (cell <> mempty))
+          result2 = renderText (htmlFromCell (\_ -> V.tag "td" mempty) cell)
+      result1 `shouldBe` result2
+
+  describe "encodeHtmlTable" $ do
+    let personColonnade = mconcat
+          [ C.headed "Name" (E.text . name)
+          , C.headed "Age" (E.text . T.pack . show . age)
+          ]
+        people = [Person "Alice" 30, Person "Bob" 25]
+
+    it "generates correct HTML structure" $ do
+      let html = encodeHtmlTable (V.extClass "table") personColonnade people
+      let result = renderText html
+      result `shouldBe` [i|<table class='table'>
+  <thead>
+    <tr>
+      <th>Name</th>
+      <th>Age</th>
+    </tr>
+  </thead>
+  <tbody>
+    <tr>
+      <td>Alice</td>
+      <td>30</td>
+    </tr>
+    <tr>
+      <td>Bob</td>
+      <td>25</td>
+    </tr>
+  </tbody>
+</table>|]
+
+    it "preserves table attributes" $ do
+      let attr = "class"
+          val = "test-table"
+          html = encodeHtmlTable (V.att attr val) personColonnade []
+          rendered = renderText html
+      rendered `shouldBe` [i|<table class='#{val}'>
+  <thead>
+    <tr>
+      <th>Name</th>
+      <th>Age</th>
+    </tr>
+  </thead>
+  <tbody></tbody>
+</table>|]
+
+  describe "encodeCellTable" $ do
+    let personColonnade = mconcat
+          [ C.headed "Name" (\p -> Cell (V.extClass "name") (E.text $ name p))
+          , C.headed "Age" (\p -> Cell (V.extClass "age") (E.text . T.pack . show $ age p))
+          ]
+        people = [Person "Alice" 30, Person "Bob" 25]
+
+    it "preserves cell attributes" $ do
+      let html = encodeCellTable mempty personColonnade people
+          rendered = renderText html
+      rendered `shouldBe` [i|<table>
+  <thead>
+    <tr>
+      <th>Name</th>
+      <th>Age</th>
+    </tr>
+  </thead>
+  <tbody>
+    <tr>
+      <td class='name'>Alice</td>
+      <td class='age'>30</td>
+    </tr>
+    <tr>
+      <td class='name'>Bob</td>
+      <td class='age'>25</td>
+    </tr>
+  </tbody>
+</table>|]
+
+  describe "Cell constructors" $ do
+    it "charCell creates valid HTML" $ do
+      let c = 'a'
+          cell = charCell c
+      let result = renderText (htmlFromCell (\_ -> V.tag "td" mempty) cell)
+      result `shouldBe` [i|<td>#{c}</td>|]
+
+    it "stringCell creates valid HTML" $ do
+      let s = "test"
+          cell = stringCell s
+      let result = renderText (htmlFromCell (\_ -> V.tag "td" mempty) cell)
+      result `shouldBe` [i|<td>#{s}</td>|]
+
+    it "textCell creates valid HTML" $ do
+      let t = "test" :: T.Text
+          cell = textCell t
+      let result = renderText (htmlFromCell (\_ -> V.tag "td" mempty) cell)
+      result `shouldBe` [i|<td>#{t}</td>|]
+
+  describe "encodeTable" $ do
+    let personColonnade = mconcat
+          [ C.headed "Name" (E.text . name)
+          , C.headed "Age" (E.text . T.pack . show . age)
+          ]
+        people = [Person "Alice" 30, Person "Bob" 25]
+
+    it "applies all attribute functions" $ do
+      let html = encodeTable
+            (E.headednessPure (V.extClass "head", V.extClass "head-row"))
+            (V.extClass "body")
+            (\_ -> V.extClass "row")
+            (\tagFn content -> tagFn (V.extClass "i") content)
+            (V.extClass "table")
+            personColonnade
+            people
+          rendered = renderText html
+      rendered `shouldBe` [i|<table class='table'>
+  <thead class='head'>
+    <tr class='head-row'>
+      <th class='i'>Name</th>
+      <th class='i'>Age</th>
+    </tr>
+  </thead>
+  <tbody class='body'>
+    <tr class='row'>
+      <td class='i'>Alice</td>
+      <td class='i'>30</td>
+    </tr>
+    <tr class='row'>
+      <td class='i'>Bob</td>
+      <td class='i'>25</td>
+    </tr>
+  </tbody>
+</table>|]
diff --git a/web-view-colonnade.cabal b/web-view-colonnade.cabal
new file mode 100644
--- /dev/null
+++ b/web-view-colonnade.cabal
@@ -0,0 +1,116 @@
+cabal-version:   3.4
+
+-- The cabal-version field refers to the version of the .cabal specification,
+-- and can be different from the cabal-install (the tool) version and the
+-- Cabal (the library) version you are using. As such, the Cabal (the library)
+-- version used must be equal or greater than the version stated in this field.
+-- Starting from the specification version 2.2, the cabal-version field must be
+-- the first thing in the cabal file.
+
+-- Initial package description 'web-view-colonnade' generated by
+-- 'cabal init'. For further documentation, see:
+--   http://haskell.org/cabal/users-guide/
+--
+-- The name of the package.
+name:            web-view-colonnade
+
+-- The package version.
+-- See the Haskell package versioning policy (PVP) for standards
+-- guiding when and how versions should be incremented.
+-- https://pvp.haskell.org
+-- PVP summary:     +-+------- breaking API changes
+--                  | | +----- non-breaking API additions
+--                  | | | +--- code changes with no API change
+version:         0.1.0.0
+
+-- A short (one-line) description of the package.
+synopsis:        Build HTML tables using web-view and colonnade.
+homepage:        https://github.com/lorenzo/web-view-colonnade
+
+-- A longer description of the package.
+description:
+  Build HTML tables using web-view and colonnade. This module provides
+  functionality similar to @lucid-colonnade@ and @blaze-colonnade@ but for the
+  web-view library.
+
+-- The license under which the package is released.
+license:         MIT
+
+-- The file containing the license text.
+license-file:    LICENSE
+
+-- The package author(s).
+author:          José Lorenzo Rodríguez
+
+-- An email address to which users can send suggestions, bug reports, and patches.
+maintainer:      lorenzo@users.noreply.github.com
+
+-- A copyright notice.
+category:        Web
+build-type:      Simple
+
+-- Extra doc files to be distributed with the package, such as a CHANGELOG or a README.
+extra-doc-files: README.md
+
+-- Extra source files to be distributed with the package, such as examples, or a tutorial module.
+-- extra-source-files:
+
+common warnings
+  ghc-options: -Wall
+
+common deps
+  build-depends:
+    , base        >=4.18.2.1 && <5
+    , colonnade   >=1.2.0    && <1.3
+    , containers  >=0.6.7    && <0.9
+    , text        >=2.0.2    && <2.2
+    , vector      >=0.13.2   && <0.14
+    , web-view    >=0.6.0    && <0.8
+
+library
+  -- Import common warning flags.
+  import:           warnings, deps
+
+  -- Modules exported by the library.
+  exposed-modules:  WebView.Colonnade
+
+  -- Modules included in this library but not exported.
+  -- other-modules:
+
+  -- LANGUAGE extensions used by modules in this package.
+  -- other-extensions:  
+
+  -- Directories containing source files.
+  hs-source-dirs:   src
+
+  -- Base language which the package is written in.
+  default-language: GHC2021
+
+test-suite web-view-colonnade-test
+  -- Import common warning flags.
+  import:           warnings, deps
+
+  -- Base language which the package is written in.
+  default-language: GHC2021
+
+  -- Modules included in this executable, other than Main.
+  other-modules:    WebView.ColonnadeSpec
+
+  -- LANGUAGE extensions used by modules in this package.
+  other-extensions: OverloadedStrings
+
+  -- The interface type and version of the test suite.
+  type:             exitcode-stdio-1.0
+
+  -- Directories containing source files.
+  hs-source-dirs:   test
+
+  -- The entrypoint to the test suite.
+  main-is:          Main.hs
+
+  -- Test dependencies.
+  build-depends:    web-view-colonnade
+
+source-repository head
+  type:     git
+  location: https://github.com/lorenzo/web-view-colonnade
