diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2011 Bardur Arantsson
+
+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/Setup.lhs b/Setup.lhs
new file mode 100644
--- /dev/null
+++ b/Setup.lhs
@@ -0,0 +1,4 @@
+#! /usr/bin/env runhaskell
+
+> import Distribution.Simple
+> main = defaultMain
diff --git a/dingo-widgets.cabal b/dingo-widgets.cabal
new file mode 100644
--- /dev/null
+++ b/dingo-widgets.cabal
@@ -0,0 +1,39 @@
+Name:                dingo-widgets
+Version:             0.0.2
+Synopsis:            Dingo Widgets
+Description:         This package contains a set of widgets for the Dingo Rich Internet Application platform.
+License:             MIT
+License-file:        LICENSE
+Category:            WWW
+Cabal-version:       >=1.6.0.1
+Build-type:          Simple
+Author:              Bardur Arantsson
+Maintainer:          Bardur Arantsson <bardur@scientician.net>
+
+Library
+  Build-Depends: base == 4.*
+               , aeson == 0.3.*
+               , bytestring >= 0.9.0.1
+               , blaze-html >= 0.4.1.6 && < 0.5
+               , containers >= 0.4
+               , dingo-core >= 0 && < 0.1
+               , file-embed >= 0.0.4 && < 0.1
+               , fclabels == 1.0.*
+               , shakespeare-js >= 0.10 && < 0.11
+               , template-haskell
+               , text == 0.11.*
+               , transformers >= 0.2.2 && < 0.3
+  Extensions:          DeriveDataTypeable
+                       MultiParamTypeClasses
+                       OverloadedStrings
+                       QuasiQuotes
+                       TemplateHaskell
+  ghc-options:         -Wall
+  hs-source-dirs:      src
+  Exposed-modules:     Dingo.ResourceBundle.JqueryUI
+                       Dingo.ResourceBundle.JqueryUI.Lightness
+                       Dingo.Widget.Button
+                       Dingo.Widget.Input
+                       Dingo.Widget.Panel
+                       Dingo.Widget.Select
+                       Dingo.Widget.DataTables
diff --git a/src/Dingo/ResourceBundle/JqueryUI.hs b/src/Dingo/ResourceBundle/JqueryUI.hs
new file mode 100644
--- /dev/null
+++ b/src/Dingo/ResourceBundle/JqueryUI.hs
@@ -0,0 +1,11 @@
+module Dingo.ResourceBundle.JqueryUI
+       ( jqueryUIResourceBundle
+       ) where
+
+import Dingo.ResourceBundle
+
+jqueryUIDirectory :: ResourceDirectory
+jqueryUIDirectory = $(embedDir "bundles/jquery-ui")
+
+jqueryUIResourceBundle :: ResourceBundle
+jqueryUIResourceBundle = makeResourceBundle jqueryUIDirectory
diff --git a/src/Dingo/ResourceBundle/JqueryUI/Lightness.hs b/src/Dingo/ResourceBundle/JqueryUI/Lightness.hs
new file mode 100644
--- /dev/null
+++ b/src/Dingo/ResourceBundle/JqueryUI/Lightness.hs
@@ -0,0 +1,11 @@
+module Dingo.ResourceBundle.JqueryUI.Lightness
+       ( jqueryUIStyleResourceBundle
+       ) where
+
+import Dingo.ResourceBundle
+
+resourceDirectory :: ResourceDirectory
+resourceDirectory = $(embedDir "bundles/jquery-ui-css/lightness")
+
+jqueryUIStyleResourceBundle :: ResourceBundle
+jqueryUIStyleResourceBundle = makeResourceBundle resourceDirectory
diff --git a/src/Dingo/Widget/Button.hs b/src/Dingo/Widget/Button.hs
new file mode 100644
--- /dev/null
+++ b/src/Dingo/Widget/Button.hs
@@ -0,0 +1,69 @@
+module Dingo.Widget.Button ( Button
+                           , mkButton
+                           , getLabel
+                           , onClick
+                           , setLabel
+                           ) where
+
+import Control.Monad (mzero)
+import Data.Aeson (FromJSON(..), ToJSON(..), Value(..))
+import Data.Monoid (mempty)
+import Data.Text (Text)
+import Data.Typeable (Typeable)
+import Dingo.Callback
+import Dingo.Event
+import Dingo.Widget
+import Text.Blaze ((!), toValue)
+import Text.Julius (julius)
+import qualified Text.Blaze.Html4.Strict as H
+import qualified Text.Blaze.Html4.Strict.Attributes as A
+
+-- Button type.
+data Button = Button { buttonId :: WidgetId
+                     }
+              deriving (Show, Typeable)
+
+-- Button state.
+data ButtonState = ButtonState Text
+                 deriving (Show, Typeable)
+
+instance FromJSON ButtonState where
+  parseJSON (String s) = return $ ButtonState s
+  parseJSON _          = mzero -- Failure
+
+instance ToJSON ButtonState where
+  toJSON (ButtonState s) = String s
+
+-- Button is a widget.
+instance Widget Button ButtonState where
+  -- Get the widget ID.
+  getWidgetId = buttonId
+  -- Render button to HTML.
+  renderWidget w =
+    H.button ! A.id (toValue $ buttonId w) $ mempty
+  -- Show button widget
+  showWidget button state = show button ++ "->" ++ show state
+  -- Client state handling.
+  encodeClientStateJs _ =
+    [julius| function () { return $(this).html(); } |]
+  decodeClientStateJs _ =
+    [julius| function (s) { $(this).html(s); } |]
+
+-- Make a new button.
+mkButton :: Widget w s => w -> Text -> CallbackM Button
+mkButton pw l = addWidget pw (\i -> return (Button i, ButtonState l))
+
+-- Register an OnClick handler.
+onClick :: Button -> CallbackM () -> CallbackM ()
+onClick btn = onEvent (widgetSelector btn) OnClick
+
+
+-- Get the label of the button.
+getLabel :: Button -> CallbackM Text
+getLabel w = fmap f (getWidgetState w)
+  where f Nothing = ""
+        f (Just (ButtonState l)) = l
+
+-- Set the value contained in the input field.
+setLabel :: Button -> Text -> CallbackM ()
+setLabel w l = setWidgetState w (ButtonState l)
diff --git a/src/Dingo/Widget/DataTables.hs b/src/Dingo/Widget/DataTables.hs
new file mode 100644
--- /dev/null
+++ b/src/Dingo/Widget/DataTables.hs
@@ -0,0 +1,96 @@
+module Dingo.Widget.DataTables
+       ( Table
+       , mkTable
+       , onTableBodyEvent
+       , setTableData
+       ) where
+
+import Control.Monad (forM_)
+import Data.Aeson (FromJSON(..), ToJSON(..), Value(..))
+import Data.Text (Text)
+import Data.Typeable (Typeable)
+import Dingo.Callback
+import Dingo.Event
+import Dingo.ResourceBundle.JqueryUI (jqueryUIResourceBundle)
+import Dingo.ResourceBundle.DataTables (dataTablesResourceBundle)
+import Dingo.Selector
+import Dingo.Widget
+import Text.Blaze ((!), toValue, toHtml)
+import Text.Julius (julius)
+import qualified Text.Blaze.Html4.Strict as H
+import qualified Text.Blaze.Html4.Strict.Attributes as A
+
+-- Table type.
+data Table = Table { tableId :: WidgetId
+                   , tableHeadings :: [Text]
+                   , tableData :: [[Text]]
+                   }
+            deriving (Show, Typeable)
+
+-- State associated with table widget.
+data TableState = TableState ()
+                deriving (Show, Typeable)
+
+instance FromJSON TableState where
+  parseJSON _ = return $ TableState ()
+
+instance ToJSON TableState where
+  toJSON (TableState ()) = Null
+
+-- Table is a widget.
+instance Widget Table TableState where
+  -- Get the widget ID.
+  getWidgetId = tableId
+  -- Render table to HTML.
+  renderWidget w =
+    H.table ! A.id (toValue $ tableId w) $ do
+      H.thead $ do
+        H.tr $ do
+          mapM_ (H.th ! A.width (toValue aWidth)) $ map toHtml $ tableHeadings w
+      H.tbody $ do
+        forM_ (tableData w) $ \rowData ->
+          H.tr $ do
+            mapM_ H.td $ map toHtml $ rowData
+    where
+      aWidth :: Int
+      aWidth = 150
+  -- Show widget.
+  showWidget w s =
+    show w ++ "->" ++ show s
+  -- Client state handling.
+  encodeClientStateJs _ =
+    [julius| null |]
+  decodeClientStateJs _ =
+    [julius| null |]
+
+  widgetResources _ =
+    [ jqueryUIResourceBundle, dataTablesResourceBundle ]
+
+-- Make a new table.
+mkTable :: Widget w s => w -> [Text] -> [[Text]] -> CallbackM Table
+mkTable pw headings data_ = do
+  table <- addWidget pw (\i -> return (Table i headings data_, TableState ()))
+  sendJavascript
+    [julius| $(document).ready(function () {
+        $('#i#{getWidgetId table}').dataTable({
+              "bJQueryUI" : true,
+              "sPaginationType" : "full_numbers"
+        });
+      }); |]
+  return table
+
+-- Change value in a table cell.
+setTableData :: Table -> (Integer,Integer) -> Text -> CallbackM ()
+setTableData table (row,col) data_ =
+  sendJavascript
+    [julius| $('#i#{getWidgetId table}').dataTable().fnUpdate('#{data_}', #{show row}, #{show col}); |]
+
+-- Set up event handler.
+onTableBodyEvent :: Table -> Event -> CallbackM () -> CallbackM ()
+onTableBodyEvent table event callback =
+  onEvent (tableRowsSelector table) event callback
+  where
+    tableRowsSelector :: Table -> Selector
+    tableRowsSelector w =
+      widgetSelector w .>*. element "tbody" .>*. element "tr"
+
diff --git a/src/Dingo/Widget/Input.hs b/src/Dingo/Widget/Input.hs
new file mode 100644
--- /dev/null
+++ b/src/Dingo/Widget/Input.hs
@@ -0,0 +1,71 @@
+module Dingo.Widget.Input ( Input
+                          , mkInput
+                          , getValue
+                          , onChange
+                          , setValue
+                          ) where
+
+import Control.Monad (mzero)
+import Data.Aeson.Types
+import Data.Text (Text)
+import Data.Typeable (Typeable)
+import Dingo.Callback
+import Dingo.Event
+import Dingo.Widget
+import Text.Blaze ((!), toValue)
+import Text.Julius (julius)
+import qualified Text.Blaze.Html4.Strict as H
+import qualified Text.Blaze.Html4.Strict.Attributes as A
+
+-- Input type.
+data Input = Input { inputId :: WidgetId
+                   }
+             deriving (Show, Typeable)
+
+-- State associated with an Input widget.
+data InputState = InputState { inputValue :: Text }
+                deriving (Show, Typeable)
+
+instance ToJSON InputState where
+  toJSON (InputState v) = String v
+
+instance FromJSON InputState where
+  parseJSON (String s) = return $ InputState s
+  parseJSON _          = mzero -- Not supported.
+
+-- Input is a widget.
+instance Widget Input InputState where
+  -- Get the widget ID.
+  getWidgetId = inputId
+  -- Render input to HTML.
+  renderWidget w =
+    H.input ! A.id (toValue $ inputId w)
+  -- Show widget.
+  showWidget w s =
+    show w ++ "->" ++ show s
+  -- Client state handling.
+  encodeClientStateJs _ =
+    [julius| function() { return $(this).val(); } |]
+  decodeClientStateJs _ =
+    [julius| function(s) { $(this).val(s); } |]
+
+
+-- Make a new input.
+mkInput :: Widget w s => w -> CallbackM Input
+mkInput = flip addWidget (\i -> return (Input i, InputState ""))
+
+-- Get the value contained in the input field.
+getValue :: Input -> CallbackM Text
+getValue w = do
+  ms <- getWidgetState w
+  case ms of
+    Nothing -> return ""
+    Just s -> return $ inputValue s
+
+-- Set the value contained in the input field.
+setValue :: Input -> Text -> CallbackM ()
+setValue w v = setWidgetState w (InputState v)
+
+-- Register an onChange callback for the input field.
+onChange :: Input -> CallbackM () -> CallbackM ()
+onChange i = onEvent (widgetSelector i) OnChange
diff --git a/src/Dingo/Widget/Panel.hs b/src/Dingo/Widget/Panel.hs
new file mode 100644
--- /dev/null
+++ b/src/Dingo/Widget/Panel.hs
@@ -0,0 +1,47 @@
+module Dingo.Widget.Panel ( Panel
+                          , mkPanel
+                          ) where
+
+import Data.Aeson (FromJSON(..), ToJSON(..), Value(..))
+import Data.Monoid (mempty)
+import Data.Typeable (Typeable)
+import Dingo.Callback
+import Dingo.Widget
+import Text.Blaze ((!), toValue)
+import Text.Julius (julius)
+import qualified Data.Map as M
+import qualified Text.Blaze.Html4.Strict as H
+import qualified Text.Blaze.Html4.Strict.Attributes as A
+
+-- Panel type.
+data Panel =
+  Panel { panelId :: WidgetId
+        }
+  deriving (Show, Typeable)
+
+data PanelState = PanelState ()
+                deriving (Show, Typeable)
+
+instance FromJSON PanelState where
+  parseJSON _ = return $ PanelState ()
+
+instance ToJSON PanelState where
+  toJSON (PanelState ()) = Object M.empty
+
+-- Panel is a widget.
+instance Widget Panel PanelState where
+  -- Get the widget ID.
+  getWidgetId = panelId
+  -- Render panel to HTML.
+  renderWidget w =
+    H.div ! A.id (toValue $ panelId w) $ do
+      mempty
+  -- Show widget.
+  showWidget w s = show w ++ "->" ++ show s
+  -- Client state handling.
+  encodeClientStateJs _ = [julius| null |]
+  decodeClientStateJs _ = [julius| null |]
+
+-- Make a new panel.
+mkPanel :: Widget w s => w -> CallbackM Panel
+mkPanel pw = addWidget pw (\i -> return (Panel i, PanelState ()))
diff --git a/src/Dingo/Widget/Select.hs b/src/Dingo/Widget/Select.hs
new file mode 100644
--- /dev/null
+++ b/src/Dingo/Widget/Select.hs
@@ -0,0 +1,82 @@
+module Dingo.Widget.Select ( Select
+                           , mkSelect
+                           , getValue
+                           , onChange
+                           , setValue
+                           , setChoices
+                           ) where
+
+import Control.Monad (mzero)
+import Data.Aeson (FromJSON(..), ToJSON(..), Value(..))
+import Data.Foldable (forM_)
+import Data.Text (Text)
+import Data.Typeable (Typeable)
+import Dingo.Callback
+import Dingo.Event
+import Dingo.Widget
+import Text.Blaze ((!), toHtml, toValue)
+import Text.Julius (julius)
+import qualified Text.Blaze.Html4.Strict as H
+import qualified Text.Blaze.Html4.Strict.Attributes as A
+
+-- Select type.
+data Select = Select { selectId :: WidgetId
+                     , selectChoices :: [(Text,Text)]
+                     }
+            deriving (Show, Typeable)
+
+-- State associated with an Select widget.
+data SelectState = SelectState { selectValue :: Text }
+                 deriving (Show, Typeable)
+
+instance FromJSON SelectState where
+  parseJSON (String s) = return $ SelectState s
+  parseJSON _ = mzero
+
+instance ToJSON SelectState where
+  toJSON (SelectState s) = String s
+
+-- Select is a widget.
+instance Widget Select SelectState where
+  -- Get the widget ID.
+  getWidgetId = selectId
+  -- Render select to HTML.
+  renderWidget w =
+    H.select ! A.id (toValue $ selectId w) $ do
+      forM_ (selectChoices w) $ \(v,dv) -> do
+        H.option ! A.value (toValue v) $ toHtml dv
+  -- Show widget.
+  showWidget w s =
+    show w ++ "->" ++ show s
+  -- Client state handling.
+  encodeClientStateJs _ =
+    [julius| function() { return $(this).val(); } |]
+  decodeClientStateJs _ =
+    [julius| function(s) { $(this).val(s); } |]
+
+-- Make a new select.
+mkSelect :: Widget w s => w -> [(Text,Text)] -> CallbackM Select
+mkSelect pw choices = addWidget pw (\i -> return (Select i choices, SelectState ""))
+
+-- Get the value contained in the select field.
+getValue :: Select -> CallbackM Text
+getValue w = do
+  ms <- getWidgetState w
+  case ms of
+    Nothing -> return ""
+    Just s -> return $ selectValue s
+
+-- Set the value contained in the select field.
+setValue :: Select -> Text -> CallbackM ()
+setValue w v = setWidgetState w (SelectState v)
+
+-- Register a callback for the OnChange event.
+onChange :: Select -> CallbackM () -> CallbackM ()
+onChange s = onEvent (widgetSelector s) OnChange
+
+-- Change the set of choices to display.
+setChoices :: Select -> [(Text,Text)] -> CallbackM ()
+setChoices w choices =
+  setWidgetContents w $ do
+    forM_ choices $ \(v,dv) ->
+      H.option ! A.value (toValue v) $ toHtml dv
