diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,30 @@
+Copyright (c)2011, Chris Done
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+    * Redistributions of source code must retain the above copyright
+      notice, this list of conditions and the following disclaimer.
+
+    * Redistributions in binary form must reproduce the above
+      copyright notice, this list of conditions and the following
+      disclaimer in the documentation and/or other materials provided
+      with the distribution.
+
+    * Neither the name of Chris Done nor the names of other
+      contributors may be used to endorse or promote products derived
+      from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/Setup.hs b/Setup.hs
new file mode 100644
--- /dev/null
+++ b/Setup.hs
@@ -0,0 +1,2 @@
+import Distribution.Simple
+main = defaultMain
diff --git a/css.cabal b/css.cabal
new file mode 100644
--- /dev/null
+++ b/css.cabal
@@ -0,0 +1,22 @@
+Name:                css
+Version:             0.1
+Synopsis:            Minimal monadic CSS DSL.
+Description:         Minimal monadic CSS DSL. Type-safe property values to come later.
+License:             BSD3
+License-file:        LICENSE
+Author:              Chris Done <chrisdone@gmail.com>
+Maintainer:          Chris Done <chrisdone@gmail.com>
+Copyright:           2010 Chris Done
+Category:            Language
+Build-type:          Simple
+Cabal-version:       >=1.2
+Stability:           Experimental
+
+Library
+  Exposed-modules:   Language.CSS, Language.CSS.Types, Language.CSS.Properties
+  Other-modules:     Data.Monoid.Operator
+  Ghc-options:       -Wall
+  Build-depends:     base >= 4 && < 5
+                    ,text >= 0.11
+                    ,mtl  >= 2.0
+  Hs-source-dirs:    src
diff --git a/src/Data/Monoid/Operator.hs b/src/Data/Monoid/Operator.hs
new file mode 100644
--- /dev/null
+++ b/src/Data/Monoid/Operator.hs
@@ -0,0 +1,8 @@
+-- | Convenient operator.
+
+module Data.Monoid.Operator where
+
+import Data.Monoid
+
+(++) :: (Monoid a) => a -> a -> a
+(++) = mappend
diff --git a/src/Language/CSS.hs b/src/Language/CSS.hs
new file mode 100644
--- /dev/null
+++ b/src/Language/CSS.hs
@@ -0,0 +1,75 @@
+{-# OPTIONS -Wall #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE GeneralizedNewtypeDeriving #-}
+{-# LANGUAGE NamedFieldPuns #-}
+{-# LANGUAGE FlexibleInstances #-}
+-- | CSS generation.
+
+module Language.CSS
+  (module Language.CSS.Types
+  ,module Language.CSS.Properties
+  ,runCSS
+  ,renderCSS
+  ,renderPrettyCSS
+  ,rules
+  ,rule)
+    where
+
+import           Language.CSS.Properties
+import           Language.CSS.Types
+
+import           Control.Monad.Writer    (MonadWriter,runWriter,tell)
+import           Data.Either             (lefts,rights)
+import           Data.Monoid             (Monoid(..))
+import           Data.Monoid.Operator    ((++))
+import           Data.Text.Lazy          (Text)
+import qualified Data.Text.Lazy          as T
+import           Prelude                 hiding ((++))
+
+-- | Generate CSS rules.
+runCSS :: CSS Rule -> [Rule]
+runCSS = snd . runWriter . unCSS
+
+-- | Generate CSS properties.
+runBody :: CSS (Either Property Rule) -> [(Either Property Rule)]
+runBody = snd . runWriter . unCSS
+
+-- | Render a CSS AST to text, flat.
+renderCSS :: [Rule] -> Text
+renderCSS = mconcat . map renderRule where
+  renderRule (Rule _name [] []) = ""
+  renderRule (Rule name props sub) =
+    parent ++
+    renderCSS (map prefix sub)
+      where parent | null props = ""
+                   | otherwise = name ++ "{" ++ renderProps props ++ "}"
+            prefix subr@Rule{ruleExpr} =
+              subr { ruleExpr = name ++ " " ++ ruleExpr }
+  renderProps = T.intercalate ";" . map renderProp
+  renderProp (Property name value) = name ++ ":" ++ value
+
+-- | Render a CSS AST to text, pretty.
+renderPrettyCSS :: [Rule] -> Text
+renderPrettyCSS = mconcat . map renderRule where
+  renderRule (Rule name props sub) =
+    name ++ "{\n" ++ renderProps props ++ "\n}" ++ "\n" ++
+    renderPrettyCSS (map prefix sub)
+      where prefix subr@Rule{ruleExpr} =
+              subr { ruleExpr = name ++ " " ++ ruleExpr }
+  renderProps = T.intercalate ";\n" . map (("    "++) . renderProp)
+  renderProp (Property name value) = name ++ ": " ++ value
+
+class Ruleable a where
+  rule :: Text -> CSS (Either Property Rule) -> CSS a
+  rules :: [Text] -> CSS (Either Property Rule) -> CSS a
+  rules rs body = mapM_ (`rule` body) rs
+
+instance Ruleable Rule where
+  rule name getProps = do
+    let body = runBody getProps
+    tell $ [Rule name (lefts body) (rights body)]
+
+instance Ruleable (Either Property Rule) where
+  rule name getProps = do
+    let body = runBody getProps
+    tell $ [Right $ Rule name (lefts body) (rights body)]
diff --git a/src/Language/CSS/Properties.hs b/src/Language/CSS/Properties.hs
new file mode 100644
--- /dev/null
+++ b/src/Language/CSS/Properties.hs
@@ -0,0 +1,478 @@
+{-# OPTIONS -Wall #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE GeneralizedNewtypeDeriving #-}
+{-# LANGUAGE NamedFieldPuns #-}
+-- | CSS generation.
+
+module Language.CSS.Properties where
+
+import Language.CSS.Types   (CSS,Rule,Property(..))
+
+import Control.Monad.Writer (MonadWriter,tell)
+import Data.Text.Lazy       (Text)
+
+-- | Make a CSS property.
+prop :: Text -> Text -> CSS (Either Property Rule)
+prop name value = tell [Left $ Property name value]
+
+-- Microsoft
+
+-- | CSS property: -ms-filter
+msFilter :: Text -> CSS (Either Property Rule)
+msFilter = prop "-mz-filter"
+
+-- Additional ones from CSS3
+
+-- | CSS property: opacity
+opacity :: Text -> CSS (Either Property Rule)
+opacity = prop "opacity"
+
+-- | CSS property: border-radius
+borderRadius :: Text -> CSS (Either Property Rule)
+borderRadius = prop "border-radius"
+
+-- | CSS property: text-shadow
+textShadow :: Text -> CSS (Either Property Rule)
+textShadow = prop "text-shadow"
+
+-- | CSS property: box-shadow
+boxShadow :: Text -> CSS (Either Property Rule)
+boxShadow = prop "box-shadow"
+
+-- | CSS property: border-top-leftradius
+borderTopLeftRadius :: Text -> CSS (Either Property Rule)
+borderTopLeftRadius = prop "border-top-left-radius"
+
+-- | CSS property: border-bottom-leftradius
+borderBottomLeftRadius :: Text -> CSS (Either Property Rule)
+borderBottomLeftRadius = prop "border-bottom-left-radius"
+
+-- | CSS property: border-top-rightradius
+borderTopRightRadius :: Text -> CSS (Either Property Rule)
+borderTopRightRadius = prop "border-top-right-radius"
+
+-- | CSS property: border-bottom-rightradius
+borderBottomRightRadius :: Text -> CSS (Either Property Rule)
+borderBottomRightRadius = prop "border-bottom-right-radius"
+
+-- Generated from http://www.w3.org/TR/CSS21/propidx.html
+
+-- | CSS property: azimuth
+azimuth :: Text -> CSS (Either Property Rule)
+azimuth = prop "azimuth"
+
+-- | CSS property: background-color
+backgroundColor :: Text -> CSS (Either Property Rule)
+backgroundColor = prop "background-color"
+
+-- | CSS property: background-image
+backgroundImage :: Text -> CSS (Either Property Rule)
+backgroundImage = prop "background-image"
+
+-- | CSS property: background-position
+backgroundPosition :: Text -> CSS (Either Property Rule)
+backgroundPosition = prop "background-position"
+
+-- | CSS property: background-repeat
+backgroundRepeat :: Text -> CSS (Either Property Rule)
+backgroundRepeat = prop "background-repeat"
+
+-- | CSS property: background
+background :: Text -> CSS (Either Property Rule)
+background = prop "background"
+
+-- | CSS property: border-collapse
+borderCollapse :: Text -> CSS (Either Property Rule)
+borderCollapse = prop "border-collapse"
+
+-- | CSS property: border-color
+borderColor :: Text -> CSS (Either Property Rule)
+borderColor = prop "border-color"
+
+-- | CSS property: border-spacing
+borderSpacing :: Text -> CSS (Either Property Rule)
+borderSpacing = prop "border-spacing"
+
+-- | CSS property: border-style
+borderStyle :: Text -> CSS (Either Property Rule)
+borderStyle = prop "border-style"
+
+-- | CSS property: border-top
+borderTop :: Text -> CSS (Either Property Rule)
+borderTop = prop "border-top"
+
+-- | CSS property: border-bottom
+borderBottom :: Text -> CSS (Either Property Rule)
+borderBottom = prop "border-bottom"
+
+-- | CSS property: border-top-color
+borderTopColor :: Text -> CSS (Either Property Rule)
+borderTopColor = prop "border-top-color"
+
+-- | CSS property: border-top-style
+borderTopStyle :: Text -> CSS (Either Property Rule)
+borderTopStyle = prop "border-top-style"
+
+-- | CSS property: border-top-width
+borderTopWidth :: Text -> CSS (Either Property Rule)
+borderTopWidth = prop "border-top-width"
+
+-- | CSS property: border-width
+borderWidth :: Text -> CSS (Either Property Rule)
+borderWidth = prop "border-width"
+
+-- | CSS property: border
+border :: Text -> CSS (Either Property Rule)
+border = prop "border"
+
+-- | CSS property: border-right
+borderRight :: Text -> CSS (Either Property Rule)
+borderRight = prop "border-right"
+
+-- | CSS property: border-left
+borderLeft :: Text -> CSS (Either Property Rule)
+borderLeft = prop "border-left"
+
+-- | CSS property: bottom
+bottom :: Text -> CSS (Either Property Rule)
+bottom = prop "bottom"
+
+-- | CSS property: caption-side
+captionSide :: Text -> CSS (Either Property Rule)
+captionSide = prop "caption-side"
+
+-- | CSS property: clear
+clear :: Text -> CSS (Either Property Rule)
+clear = prop "clear"
+
+-- | CSS property: clip
+clip :: Text -> CSS (Either Property Rule)
+clip = prop "clip"
+
+-- | CSS property: color
+color :: Text -> CSS (Either Property Rule)
+color = prop "color"
+
+-- | CSS property: content
+content :: Text -> CSS (Either Property Rule)
+content = prop "content"
+
+-- | CSS property: counter-increment
+counterIncrement :: Text -> CSS (Either Property Rule)
+counterIncrement = prop "counter-increment"
+
+-- | CSS property: counter-reset
+counterReset :: Text -> CSS (Either Property Rule)
+counterReset = prop "counter-reset"
+
+-- | CSS property: cue-after
+cueAfter :: Text -> CSS (Either Property Rule)
+cueAfter = prop "cue-after"
+
+-- | CSS property: cue-before
+cueBefore :: Text -> CSS (Either Property Rule)
+cueBefore = prop "cue-before"
+
+-- | CSS property: cue
+cue :: Text -> CSS (Either Property Rule)
+cue = prop "cue"
+
+-- | CSS property: cursor
+cursor :: Text -> CSS (Either Property Rule)
+cursor = prop "cursor"
+
+-- | CSS property: direction
+direction :: Text -> CSS (Either Property Rule)
+direction = prop "direction"
+
+-- | CSS property: display
+display :: Text -> CSS (Either Property Rule)
+display = prop "display"
+
+-- | CSS property: elevation
+elevation :: Text -> CSS (Either Property Rule)
+elevation = prop "elevation"
+
+-- | CSS property: empty-cells
+emptyCells :: Text -> CSS (Either Property Rule)
+emptyCells = prop "empty-cells"
+
+-- | CSS property: float
+float :: Text -> CSS (Either Property Rule)
+float = prop "float"
+
+-- | CSS property: font-family
+fontFamily :: Text -> CSS (Either Property Rule)
+fontFamily = prop "font-family"
+
+-- | CSS property: font-size
+fontSize :: Text -> CSS (Either Property Rule)
+fontSize = prop "font-size"
+
+-- | CSS property: font-style
+fontStyle :: Text -> CSS (Either Property Rule)
+fontStyle = prop "font-style"
+
+-- | CSS property: font-variant
+fontVariant :: Text -> CSS (Either Property Rule)
+fontVariant = prop "font-variant"
+
+-- | CSS property: font-weight
+fontWeight :: Text -> CSS (Either Property Rule)
+fontWeight = prop "font-weight"
+
+-- | CSS property: font
+font :: Text -> CSS (Either Property Rule)
+font = prop "font"
+
+-- | CSS property: height
+height :: Text -> CSS (Either Property Rule)
+height = prop "height"
+
+-- | CSS property: left
+left :: Text -> CSS (Either Property Rule)
+left = prop "left"
+
+-- | CSS property: letter-spacing
+letterSpacing :: Text -> CSS (Either Property Rule)
+letterSpacing = prop "letter-spacing"
+
+-- | CSS property: line-height
+lineHeight :: Text -> CSS (Either Property Rule)
+lineHeight = prop "line-height"
+
+-- | CSS property: list-style-image
+listStyleImage :: Text -> CSS (Either Property Rule)
+listStyleImage = prop "list-style-image"
+
+-- | CSS property: list-style-position
+listStylePosition :: Text -> CSS (Either Property Rule)
+listStylePosition = prop "list-style-position"
+
+-- | CSS property: list-style-type
+listStyleType :: Text -> CSS (Either Property Rule)
+listStyleType = prop "list-style-type"
+
+-- | CSS property: list-style
+listStyle :: Text -> CSS (Either Property Rule)
+listStyle = prop "list-style"
+
+-- | CSS property: margin-right
+marginRight :: Text -> CSS (Either Property Rule)
+marginRight = prop "margin-right"
+
+-- | CSS property: margin-left
+marginLeft :: Text -> CSS (Either Property Rule)
+marginLeft = prop "margin-left"
+
+-- | CSS property: margin-top
+marginTop :: Text -> CSS (Either Property Rule)
+marginTop = prop "margin-top"
+
+-- | CSS property: margin-bottom
+marginBottom :: Text -> CSS (Either Property Rule)
+marginBottom = prop "margin-bottom"
+
+-- | CSS property: margin
+margin :: Text -> CSS (Either Property Rule)
+margin = prop "margin"
+
+-- | CSS property: max-height
+maxHeight :: Text -> CSS (Either Property Rule)
+maxHeight = prop "max-height"
+
+-- | CSS property: max-width
+maxWidth :: Text -> CSS (Either Property Rule)
+maxWidth = prop "max-width"
+
+-- | CSS property: min-height
+minHeight :: Text -> CSS (Either Property Rule)
+minHeight = prop "min-height"
+
+-- | CSS property: min-width
+minWidth :: Text -> CSS (Either Property Rule)
+minWidth = prop "min-width"
+
+-- | CSS property: orphans
+orphans :: Text -> CSS (Either Property Rule)
+orphans = prop "orphans"
+
+-- | CSS property: outline-color
+outlineColor :: Text -> CSS (Either Property Rule)
+outlineColor = prop "outline-color"
+
+-- | CSS property: outline-style
+outlineStyle :: Text -> CSS (Either Property Rule)
+outlineStyle = prop "outline-style"
+
+-- | CSS property: outline-width
+outlineWidth :: Text -> CSS (Either Property Rule)
+outlineWidth = prop "outline-width"
+
+-- | CSS property: outline
+outline :: Text -> CSS (Either Property Rule)
+outline = prop "outline"
+
+-- | CSS property: overflow
+overflow :: Text -> CSS (Either Property Rule)
+overflow = prop "overflow"
+
+-- | CSS property: padding-top
+paddingTop :: Text -> CSS (Either Property Rule)
+paddingTop = prop "padding-top"
+
+-- | CSS property: padding-bottom
+paddingBottom :: Text -> CSS (Either Property Rule)
+paddingBottom = prop "padding-bottom"
+
+-- | CSS property: padding-left
+paddingLeft :: Text -> CSS (Either Property Rule)
+paddingLeft = prop "padding-left"
+
+-- | CSS property: padding-right
+paddingRight :: Text -> CSS (Either Property Rule)
+paddingRight = prop "padding-right"
+
+-- | CSS property: padding
+padding :: Text -> CSS (Either Property Rule)
+padding = prop "padding"
+
+-- | CSS property: page-break-after
+pageBreakAfter :: Text -> CSS (Either Property Rule)
+pageBreakAfter = prop "page-break-after"
+
+-- | CSS property: page-break-before
+pageBreakBefore :: Text -> CSS (Either Property Rule)
+pageBreakBefore = prop "page-break-before"
+
+-- | CSS property: page-break-inside
+pageBreakInside :: Text -> CSS (Either Property Rule)
+pageBreakInside = prop "page-break-inside"
+
+-- | CSS property: pause-after
+pauseAfter :: Text -> CSS (Either Property Rule)
+pauseAfter = prop "pause-after"
+
+-- | CSS property: pause-before
+pauseBefore :: Text -> CSS (Either Property Rule)
+pauseBefore = prop "pause-before"
+
+-- | CSS property: pause
+pause :: Text -> CSS (Either Property Rule)
+pause = prop "pause"
+
+-- | CSS property: pitch-range
+pitchRange :: Text -> CSS (Either Property Rule)
+pitchRange = prop "pitch-range"
+
+-- | CSS property: pitch
+pitch :: Text -> CSS (Either Property Rule)
+pitch = prop "pitch"
+
+-- | CSS property: play-during
+playDuring :: Text -> CSS (Either Property Rule)
+playDuring = prop "play-during"
+
+-- | CSS property: position
+position :: Text -> CSS (Either Property Rule)
+position = prop "position"
+
+-- | CSS property: quotes
+quotes :: Text -> CSS (Either Property Rule)
+quotes = prop "quotes"
+
+-- | CSS property: richness
+richness :: Text -> CSS (Either Property Rule)
+richness = prop "richness"
+
+-- | CSS property: right
+right :: Text -> CSS (Either Property Rule)
+right = prop "right"
+
+-- | CSS property: speak-header
+speakHeader :: Text -> CSS (Either Property Rule)
+speakHeader = prop "speak-header"
+
+-- | CSS property: speak-numeral
+speakNumeral :: Text -> CSS (Either Property Rule)
+speakNumeral = prop "speak-numeral"
+
+-- | CSS property: speak-punctuation
+speakPunctuation :: Text -> CSS (Either Property Rule)
+speakPunctuation = prop "speak-punctuation"
+
+-- | CSS property: speak
+speak :: Text -> CSS (Either Property Rule)
+speak = prop "speak"
+
+-- | CSS property: speech-rate
+speechRate :: Text -> CSS (Either Property Rule)
+speechRate = prop "speech-rate"
+
+-- | CSS property: stress
+stress :: Text -> CSS (Either Property Rule)
+stress = prop "stress"
+
+-- | CSS property: table-layout
+tableLayout :: Text -> CSS (Either Property Rule)
+tableLayout = prop "table-layout"
+
+-- | CSS property: text-align
+textAlign :: Text -> CSS (Either Property Rule)
+textAlign = prop "text-align"
+
+-- | CSS property: text-decoration
+textDecoration :: Text -> CSS (Either Property Rule)
+textDecoration = prop "text-decoration"
+
+-- | CSS property: text-indent
+textIndent :: Text -> CSS (Either Property Rule)
+textIndent = prop "text-indent"
+
+-- | CSS property: text-transform
+textTransform :: Text -> CSS (Either Property Rule)
+textTransform = prop "text-transform"
+
+-- | CSS property: top
+top :: Text -> CSS (Either Property Rule)
+top = prop "top"
+
+-- | CSS property: unicode-bidi
+unicodeBidi :: Text -> CSS (Either Property Rule)
+unicodeBidi = prop "unicode-bidi"
+
+-- | CSS property: vertical-align
+verticalAlign :: Text -> CSS (Either Property Rule)
+verticalAlign = prop "vertical-align"
+
+-- | CSS property: visibility
+visibility :: Text -> CSS (Either Property Rule)
+visibility = prop "visibility"
+
+-- | CSS property: voice-family
+voiceFamily :: Text -> CSS (Either Property Rule)
+voiceFamily = prop "voice-family"
+
+-- | CSS property: volume
+volume :: Text -> CSS (Either Property Rule)
+volume = prop "volume"
+
+-- | CSS property: white-space
+whiteSpace :: Text -> CSS (Either Property Rule)
+whiteSpace = prop "white-space"
+
+-- | CSS property: widows
+widows :: Text -> CSS (Either Property Rule)
+widows = prop "widows"
+
+-- | CSS property: width
+width :: Text -> CSS (Either Property Rule)
+width = prop "width"
+
+-- | CSS property: word-spacing
+wordSpacing :: Text -> CSS (Either Property Rule)
+wordSpacing = prop "word-spacing"
+
+-- | CSS property: z-index
+zIndex :: Text -> CSS (Either Property Rule)
+zIndex = prop "z-index"
diff --git a/src/Language/CSS/Types.hs b/src/Language/CSS/Types.hs
new file mode 100644
--- /dev/null
+++ b/src/Language/CSS/Types.hs
@@ -0,0 +1,27 @@
+{-# OPTIONS -Wall #-}
+{-# LANGUAGE GeneralizedNewtypeDeriving #-}
+-- | CSS generation.
+
+module Language.CSS.Types where
+
+import Control.Monad.Writer (Writer,MonadWriter)
+import Data.Text.Lazy       (Text)
+
+-- | The CSS writer.
+newtype CSSM x a = CSSM { unCSS :: Writer [x] a }
+  deriving (Functor,Monad,MonadWriter [x])
+
+type CSS x = CSSM x ()
+
+-- | A CSS rule.
+data Rule = Rule { ruleExpr :: Text
+                 , ruleProperties :: [Property]
+                 , ruleRules :: [Rule]
+                 }
+  deriving Show
+
+-- | A CSS property.
+data Property = Property { propertyName :: Text
+                         , propertyValue :: Text
+                         }
+  deriving Show
