diff --git a/Text/Css.hs b/Text/Css.hs
--- a/Text/Css.hs
+++ b/Text/Css.hs
@@ -1,8 +1,11 @@
 {-# LANGUAGE TemplateHaskell #-}
+{-# LANGUAGE GADTs #-}
 {-# LANGUAGE GeneralizedNewtypeDeriving #-}
 {-# LANGUAGE FlexibleInstances #-}
 {-# LANGUAGE PatternGuards #-}
 {-# LANGUAGE CPP #-}
+{-# LANGUAGE TypeFamilies #-}
+{-# LANGUAGE EmptyDataDecls #-}
 module Text.Css where
 
 import Data.List (intersperse, intercalate)
@@ -21,35 +24,69 @@
 import Control.Arrow ((***))
 import Text.IndentToBrace (i2b)
 import Data.Functor.Identity (runIdentity)
+import Control.Monad (unless)
 
+#if MIN_VERSION_base(4,5,0)
+import Data.Monoid ((<>))
+#else
+(<>) :: Monoid m => m -> m -> m
+(<>) = mappend
+{-# INLINE (<>) #-}
+#endif
+
 type CssUrl url = (url -> [(T.Text, T.Text)] -> T.Text) -> Css
 
-pack :: String -> Text
-pack = T.pack
-#if !MIN_VERSION_text(0, 11, 2)
-{-# NOINLINE pack #-}
-#endif
+type DList a = [a] -> [a]
 
-fromText :: Text -> Builder
-fromText = TLB.fromText
-{-# NOINLINE fromText #-}
+-- FIXME great use case for data kinds
+data Resolved
+data Unresolved
 
-class ToCss a where
-    toCss :: a -> Builder
+type family Selector a
+type instance Selector Resolved = Builder
+type instance Selector Unresolved = [Contents]
 
-instance ToCss [Char] where toCss = fromLazyText . TL.pack
-instance ToCss Text where toCss = fromText
-instance ToCss TL.Text where toCss = fromLazyText
+type family ChildBlocks a
+type instance ChildBlocks Resolved = ()
+type instance ChildBlocks Unresolved = [Block Unresolved]
 
-data Css' = Css'
-    { _cssSelectors :: Builder
-    , _cssAttributes :: [(Builder, Builder)]
+type family Str a
+type instance Str Resolved = Builder
+type instance Str Unresolved = Contents
+
+type family Mixins a
+type instance Mixins Resolved = ()
+type instance Mixins Unresolved = [Deref]
+
+data Block a = Block
+    { blockSelector :: !(Selector a)
+    , blockAttrs :: ![Attr a]
+    , blockBlocks :: !(ChildBlocks a)
+    , blockMixins :: !(Mixins a)
     }
-data CssTop = AtBlock String Builder [Css'] | Css Css' | AtDecl String Builder
 
-data Css = CssWhitespace [CssTop]
-         | CssNoWhitespace [CssTop]
+data Mixin = Mixin
+    { mixinAttrs :: ![Attr Resolved]
+    , mixinBlocks :: ![Block Resolved]
+    }
 
+data TopLevel a where
+    TopBlock   :: !(Block a) -> TopLevel a
+    TopAtBlock :: !String -- ^ name e.g., media
+               -> !(Str a) -- ^ selector
+               -> ![Block a]
+               -> TopLevel a
+    TopAtDecl  :: !String -> !(Str a) -> TopLevel a
+    TopVar     :: !String -> !String -> TopLevel Unresolved
+
+data Attr a = Attr
+    { attrKey :: !(Str a)
+    , attrVal :: !(Str a)
+    }
+
+data Css = CssWhitespace ![TopLevel Resolved]
+         | CssNoWhitespace ![TopLevel Resolved]
+
 data Content = ContentRaw String
              | ContentVar Deref
              | ContentUrl Deref
@@ -57,7 +94,6 @@
     deriving (Show, Eq)
 
 type Contents = [Content]
-type ContentPair = (Contents, Contents)
 
 data VarType = VTPlain | VTUrl | VTUrlParam
     deriving Show
@@ -66,10 +102,27 @@
                 | CDUrl url
                 | CDUrlParam (url, [(Text, Text)])
 
+pack :: String -> Text
+pack = T.pack
+#if !MIN_VERSION_text(0, 11, 2)
+{-# NOINLINE pack #-}
+#endif
+
+fromText :: Text -> Builder
+fromText = TLB.fromText
+{-# NOINLINE fromText #-}
+
+class ToCss a where
+    toCss :: a -> Builder
+
+instance ToCss [Char] where toCss = fromLazyText . TL.pack
+instance ToCss Text where toCss = fromText
+instance ToCss TL.Text where toCss = fromLazyText
+
 -- | Determine which identifiers are used by the given template, useful for
 -- creating systems like yesod devel.
 cssUsedIdentifiers :: Bool -- ^ perform the indent-to-brace conversion
-                   -> Parser [TopLevel]
+                   -> Parser [TopLevel Unresolved]
                    -> String
                    -> [(Deref, VarType)]
 cssUsedIdentifiers toi2b parseBlocks s' =
@@ -79,7 +132,8 @@
     a = either (error . show) id $ parse parseBlocks s s
     (scope0, contents) = go a
 
-    go :: [TopLevel] -> ([(String, String)], [Content])
+    go :: [TopLevel Unresolved]
+       -> (Scope, [Content])
     go [] = ([], [])
     go (TopAtDecl dec _FIXMEcs:rest) =
         (scope, rest'')
@@ -96,7 +150,7 @@
       where
         (scope1, rest1) = go (map TopBlock blocks)
         (scope2, rest2) = go rest
-    go (TopBlock (Block x y z):rest) =
+    go (TopBlock (Block x y z _mixins):rest) =
         (scope1 ++ scope2, rest0 ++ rest1 ++ rest2)
       where
         rest0 = intercalate [ContentRaw ","] x ++ concatMap go' y
@@ -106,10 +160,13 @@
         ((k, v):scope, rest')
       where
         (scope, rest') = go rest
-    go' (k, v) = k ++ v
+    go' (Attr k v) = k ++ v
 
 cssFileDebug :: Bool -- ^ perform the indent-to-brace conversion
-             -> Q Exp -> Parser [TopLevel] -> FilePath -> Q Exp
+             -> Q Exp
+             -> Parser [TopLevel Unresolved]
+             -> FilePath
+             -> Q Exp
 cssFileDebug toi2b parseBlocks' parseBlocks fp = do
     s <- fmap TL.unpack $ qRunIO $ readUtf8File fp
 #ifdef GHC_7_4
@@ -121,7 +178,7 @@
     parseBlocks'' <- parseBlocks'
     return $ cr `AppE` parseBlocks'' `AppE` (LitE $ StringL fp) `AppE` ListE c
 
-combineSelectors :: Selector -> Selector -> Selector
+combineSelectors :: [Contents] -> [Contents] -> [Contents]
 combineSelectors a b = do
     a' <- a
     b' <- b
@@ -129,14 +186,20 @@
 
 blockRuntime :: [(Deref, CDData url)]
              -> (url -> [(Text, Text)] -> Text)
-             -> Block
-             -> Either String ([Css'] -> [Css'])
+             -> Block Unresolved
+             -> Either String (DList (Block Resolved))
 -- FIXME share code with blockToCss
-blockRuntime cd render' (Block x y z) = do
+blockRuntime cd render' (Block x attrs z mixins) = do
+    unless (null mixins) $ Left "Runtime Lucius does not support mixins"
     x' <- mapM go' $ intercalate [ContentRaw ","] x
-    y' <- mapM go'' y
+    attrs' <- mapM resolveAttr attrs
     z' <- mapM (subGo x) z -- FIXME use difflists again
-    Right $ \rest -> Css' (mconcat x') y' : foldr ($) rest z'
+    Right $ \rest -> Block
+        { blockSelector = mconcat x'
+        , blockAttrs    = attrs'
+        , blockBlocks   = ()
+        , blockMixins   = ()
+        } : foldr ($) rest z'
     {-
     (:) (Css' (mconcat $ map go' $ intercalate [ContentRaw "," ] x) (map go'' y))
     . foldr (.) id (map (subGo x) z)
@@ -144,12 +207,14 @@
   where
     go' = contentToBuilderRT cd render'
 
-    go'' :: ([Content], [Content]) -> Either String (Builder, Builder)
-    go'' (k, v) = (,) <$> (mconcat <$> mapM go' k) <*> (mconcat <$> mapM go' v)
+    resolveAttr :: Attr Unresolved -> Either String (Attr Resolved)
+    resolveAttr (Attr k v) = Attr <$> (mconcat <$> mapM go' k) <*> (mconcat <$> mapM go' v)
 
-    subGo :: Selector -> Block -> Either String ([Css'] -> [Css'])
-    subGo x' (Block a b c) =
-        blockRuntime cd render' (Block a' b c)
+    subGo :: [Contents] -- ^ parent selectors
+          -> Block Unresolved
+          -> Either String (DList (Block Resolved))
+    subGo x' (Block a b c d) =
+        blockRuntime cd render' (Block a' b c d)
       where
         a' = combineSelectors x' a
 
@@ -173,7 +238,7 @@
         _ -> Left $ show d ++ ": expected CDUrlParam"
 
 cssRuntime :: Bool -- ^ i2b?
-           -> Parser [TopLevel]
+           -> Parser [TopLevel Unresolved]
            -> FilePath
            -> [(Deref, CDData url)]
            -> (url -> [(Text, Text)] -> Text)
@@ -184,17 +249,19 @@
     let a = either (error . show) id $ parse parseBlocks s s
     return $ CssWhitespace $ goTop [] a
   where
-    goTop :: [(String, String)] -> [TopLevel] -> [CssTop]
+    goTop :: [(String, String)] -- ^ scope
+          -> [TopLevel Unresolved]
+          -> [TopLevel Resolved]
     goTop _ [] = []
     goTop scope (TopAtDecl dec cs':rest) =
-        AtDecl dec cs : goTop scope rest
+        TopAtDecl dec cs : goTop scope rest
       where
         cs = either error mconcat $ mapM (contentToBuilderRT cd render') cs'
     goTop scope (TopBlock b:rest) =
-        map Css (either error ($[]) $ blockRuntime (addScope scope) render' b) ++
+        map TopBlock (either error ($[]) $ blockRuntime (addScope scope) render' b) ++
         goTop scope rest
     goTop scope (TopAtBlock name s' b:rest) =
-        AtBlock name s (foldr (either error id . blockRuntime (addScope scope) render') [] b) :
+        TopAtBlock name s (foldr (either error id . blockRuntime (addScope scope) render') [] b) :
         goTop scope rest
       where
         s = either error mconcat $ mapM (contentToBuilderRT cd render') s'
@@ -235,52 +302,74 @@
         Just _ -> Just s
 lookupD _ _ = Nothing
 
-data Block = Block Selector Pairs [Block]
-    deriving Show
-
-data TopLevel = TopBlock Block
-              | TopAtBlock
-                    { _atBlockName :: String
-                    , _atBlockSelector :: Contents
-                    , _atBlockInner :: [Block]
-                    }
-              | TopAtDecl String Contents
-              | TopVar String String
-
-type Pairs = [Pair]
-
-type Pair = (Contents, Contents)
-
-type Selector = [Contents]
-
-compressTopLevel :: TopLevel -> TopLevel
+compressTopLevel :: TopLevel Unresolved
+                 -> TopLevel Unresolved
 compressTopLevel (TopBlock b) = TopBlock $ compressBlock b
 compressTopLevel (TopAtBlock name s b) = TopAtBlock name s $ map compressBlock b
 compressTopLevel x@TopAtDecl{} = x
 compressTopLevel x@TopVar{} = x
 
-compressBlock :: Block -> Block
-compressBlock (Block x y blocks) =
-    Block (map cc x) (map go y) (map compressBlock blocks)
+compressBlock :: Block Unresolved
+              -> Block Unresolved
+compressBlock (Block x y blocks mixins) =
+    Block (map cc x) (map go y) (map compressBlock blocks) mixins
   where
-    go (k, v) = (cc k, cc v)
+    go (Attr k v) = Attr (cc k) (cc v)
     cc [] = []
     cc (ContentRaw a:ContentRaw b:c) = cc $ ContentRaw (a ++ b) : c
     cc (a:b) = a : cc b
 
-blockToCss :: Name -> Scope -> Block -> Q Exp
-blockToCss r scope (Block sel props subblocks) =
-    [|(:) (Css' $(selectorToBuilder r scope sel) $(listE $ map go props))
+blockToMixin :: Name
+             -> Scope
+             -> Block Unresolved
+             -> Q Exp
+blockToMixin r scope (Block _sel props subblocks mixins) =
+    [|Mixin
+        { mixinAttrs    = concat
+                        $ $(listE $ map go props)
+                        : map mixinAttrs $mixinsE
+        -- FIXME too many complications to implement sublocks for now...
+        , mixinBlocks   = [] -- foldr (.) id $(listE $ map subGo subblocks) []
+        }|]
+      {-
       . foldr (.) id $(listE $ map subGo subblocks)
+      . (concatMap mixinBlocks $mixinsE ++)
     |]
+    -}
   where
-    go (x, y) = tupE [contentsToBuilder r scope x, contentsToBuilder r scope y]
-    subGo (Block sel' b c) =
-        blockToCss r scope $ Block sel'' b c
+    mixinsE = return $ ListE $ map (derefToExp []) mixins
+    go (Attr x y) = conE 'Attr
+        `appE` (contentsToBuilder r scope x)
+        `appE` (contentsToBuilder r scope y)
+    subGo (Block sel' b c d) = blockToCss r scope $ Block sel' b c d
+
+blockToCss :: Name
+           -> Scope
+           -> Block Unresolved
+           -> Q Exp
+blockToCss r scope (Block sel props subblocks mixins) =
+    [|((Block
+        { blockSelector = $(selectorToBuilder r scope sel)
+        , blockAttrs    = concat
+                        $ $(listE $ map go props)
+                        : map mixinAttrs $mixinsE
+        , blockBlocks   = ()
+        , blockMixins   = ()
+        } :: Block Resolved):)
+      . foldr (.) id $(listE $ map subGo subblocks)
+      . (concatMap mixinBlocks $mixinsE ++)
+    |]
+  where
+    mixinsE = return $ ListE $ map (derefToExp []) mixins
+    go (Attr x y) = conE 'Attr
+        `appE` (contentsToBuilder r scope x)
+        `appE` (contentsToBuilder r scope y)
+    subGo (Block sel' b c d) =
+        blockToCss r scope $ Block sel'' b c d
       where
         sel'' = combineSelectors sel sel'
 
-selectorToBuilder :: Name -> Scope -> Selector -> Q Exp
+selectorToBuilder :: Name -> Scope -> [Contents] -> Q Exp
 selectorToBuilder r scope sels =
     contentsToBuilder r scope $ intercalate [ContentRaw ","] sels
 
@@ -305,46 +394,50 @@
 
 type Scope = [(String, String)]
 
-topLevelsToCassius :: [TopLevel] -> Q Exp
+topLevelsToCassius :: [TopLevel Unresolved]
+                   -> Q Exp
 topLevelsToCassius a = do
     r <- newName "_render"
     lamE [varP r] $ appE [|CssNoWhitespace . foldr ($) []|] $ fmap ListE $ go r [] a
   where
     go _ _ [] = return []
     go r scope (TopBlock b:rest) = do
-        e <- [|(++) $ map Css ($(blockToCss r scope b) [])|]
+        e <- [|(++) $ map TopBlock ($(blockToCss r scope b) [])|]
         es <- go r scope rest
         return $ e : es
     go r scope (TopAtBlock name s b:rest) = do
         let s' = contentsToBuilder r scope s
-        e <- [|(:) $ AtBlock $(lift name) $(s') $(blocksToCassius r scope b)|]
+        e <- [|(:) $ TopAtBlock $(lift name) $(s') $(blocksToCassius r scope b)|]
         es <- go r scope rest
         return $ e : es
     go r scope (TopAtDecl dec cs:rest) = do
-        e <- [|(:) $ AtDecl $(lift dec) $(contentsToBuilder r scope cs)|]
+        e <- [|(:) $ TopAtDecl $(lift dec) $(contentsToBuilder r scope cs)|]
         es <- go r scope rest
         return $ e : es
     go r scope (TopVar k v:rest) = go r ((k, v) : scope) rest
 
-blocksToCassius :: Name -> Scope -> [Block] -> Q Exp
+blocksToCassius :: Name
+                -> Scope
+                -> [Block Unresolved]
+                -> Q Exp
 blocksToCassius r scope a = do
     appE [|foldr ($) []|] $ listE $ map (blockToCss r scope) a
 
 renderCss :: Css -> TL.Text
 renderCss css =
-    toLazyText $ mconcat $ map go tops-- FIXME use a foldr
+    toLazyText $ mconcat $ map go tops
   where
     (haveWhiteSpace, tops) =
         case css of
             CssWhitespace x -> (True, x)
             CssNoWhitespace x -> (False, x)
-    go (Css x) = renderCss' haveWhiteSpace mempty x
-    go (AtBlock name s x) =
+    go (TopBlock x) = renderBlock haveWhiteSpace mempty x
+    go (TopAtBlock name s x) =
         fromText (pack $ concat ["@", name, " "]) `mappend`
         s `mappend`
         startBlock `mappend`
-        foldr mappend endBlock (map (renderCss' haveWhiteSpace (fromString "    ")) x)
-    go (AtDecl dec cs) = fromText (pack $ concat ["@", dec, " "]) `mappend`
+        foldr mappend endBlock (map (renderBlock haveWhiteSpace (fromString "    ")) x)
+    go (TopAtDecl dec cs) = fromText (pack $ concat ["@", dec, " "]) `mappend`
                       cs `mappend`
                       endDecl
 
@@ -360,16 +453,19 @@
         | haveWhiteSpace = fromString ";\n"
         | otherwise = singleton ';'
 
-renderCss' :: Bool -> Builder -> Css' -> Builder
-renderCss' _ _ (Css' _x []) = mempty
-renderCss' haveWhiteSpace indent (Css' x y) =
-    startSelect
-    `mappend` x
-    `mappend` startBlock
-    `mappend` mconcat (intersperse endDecl $ map go' y)
-    `mappend` endBlock
+renderBlock :: Bool -- ^ have whitespace?
+            -> Builder -- ^ indentation
+            -> Block Resolved
+            -> Builder
+renderBlock haveWhiteSpace indent (Block sel attrs () ())
+    | null attrs = mempty
+    | otherwise = startSelect
+               <> sel
+               <> startBlock
+               <> mconcat (intersperse endDecl $ map renderAttr attrs)
+               <> endBlock
   where
-    go' (k, v) = startDecl `mappend` k `mappend` colon `mappend` v
+    renderAttr (Attr k v) = startDecl <> k <> colon <> v
 
     colon
         | haveWhiteSpace = fromString ": "
@@ -394,3 +490,23 @@
     endDecl
         | haveWhiteSpace = fromString ";\n"
         | otherwise = singleton ';'
+
+instance Lift Mixin where
+    lift (Mixin a b) = [|Mixin a b|]
+instance Lift (Attr Unresolved) where
+    lift (Attr k v) = [|Attr k v :: Attr Unresolved |]
+instance Lift (Attr Resolved) where
+    lift (Attr k v) = [|Attr $(liftBuilder k) $(liftBuilder v) :: Attr Resolved |]
+
+liftBuilder :: Builder -> Q Exp
+liftBuilder b = [|fromText $ pack $(lift $ TL.unpack $ toLazyText b)|]
+
+instance Lift Content where
+    lift (ContentRaw s) = [|ContentRaw s|]
+    lift (ContentVar d) = [|ContentVar d|]
+    lift (ContentUrl d) = [|ContentUrl d|]
+    lift (ContentUrlParam d) = [|ContentUrlParam d|]
+instance Lift (Block Unresolved) where
+    lift (Block a b c d) = [|Block a b c d|]
+instance Lift (Block Resolved) where
+    lift (Block a b () ()) = [|Block $(liftBuilder a) b () ()|]
diff --git a/Text/Lucius.hs b/Text/Lucius.hs
--- a/Text/Lucius.hs
+++ b/Text/Lucius.hs
@@ -1,4 +1,5 @@
 {-# LANGUAGE TemplateHaskell #-}
+{-# LANGUAGE GADTs #-}
 {-# LANGUAGE GeneralizedNewtypeDeriving #-}
 {-# LANGUAGE FlexibleInstances #-}
 {-# LANGUAGE CPP #-}
@@ -10,6 +11,9 @@
     , luciusFile
     , luciusFileDebug
     , luciusFileReload
+      -- ** Mixins
+    , luciusMixin
+    , Mixin
       -- ** Runtime
     , luciusRT
     , luciusRT'
@@ -54,7 +58,6 @@
 import Numeric (readHex)
 import Control.Applicative ((<$>))
 import Control.Monad (when, unless)
-import Data.Either (partitionEithers)
 import Data.Monoid (mconcat)
 import Data.List (isSuffixOf)
 
@@ -77,17 +80,26 @@
 whiteSpace1 =
     ((oneOf " \t\n\r" >> return ()) <|> (parseComment >> return ()))
 
-parseBlock :: Parser Block
+parseBlock :: Parser (Block Unresolved)
 parseBlock = do
     sel <- parseSelector
     _ <- char '{'
     whiteSpace
     pairsBlocks <- parsePairsBlocks id
-    let (pairs, blocks) = partitionEithers pairsBlocks
+    let (pairs, blocks, mixins) = partitionPBs pairsBlocks
     whiteSpace
-    return $ Block sel pairs blocks
+    return $ Block sel pairs blocks mixins
 
-parseSelector :: Parser Selector
+partitionPBs :: [PairBlock] -> ([Attr Unresolved], [Block Unresolved], [Deref])
+partitionPBs =
+    go id id id
+  where
+    go a b c [] = (a [], b [], c [])
+    go a b c (PBAttr x:xs) = go (a . (x:)) b c xs
+    go a b c (PBBlock x:xs) = go a (b . (x:)) c xs
+    go a b c (PBMixin x:xs) = go a b (c . (x:)) xs
+
+parseSelector :: Parser (Selector Unresolved)
 parseSelector =
     go id
   where
@@ -108,18 +120,25 @@
     trimS True = dropWhile isSpace
     trimS False = reverse . dropWhile isSpace . reverse
 
-type PairBlock = Either Pair Block
+data PairBlock = PBAttr (Attr Unresolved)
+               | PBBlock (Block Unresolved)
+               | PBMixin Deref
 parsePairsBlocks :: ([PairBlock] -> [PairBlock]) -> Parser [PairBlock]
 parsePairsBlocks front = (char '}' >> return (front [])) <|> (do
     isBlock <- lookAhead checkIfBlock
-    x <- if isBlock
-            then (do
-                b <- parseBlock
-                whiteSpace
-                return $ Right b)
-            else Left <$> parsePair
+    x <- grabMixin <|> (if isBlock then grabBlock else grabPair)
     parsePairsBlocks $ front . (:) x)
   where
+    grabBlock = do
+        b <- parseBlock
+        whiteSpace
+        return $ PBBlock b
+    grabPair = PBAttr <$> parsePair
+    grabMixin = try $ do
+        whiteSpace
+        Right x <- parseCaret
+        whiteSpace
+        return $ PBMixin x
     checkIfBlock = do
         skipMany $ noneOf "#@{};"
         (parseHash >> checkIfBlock)
@@ -129,7 +148,7 @@
             <|> (anyChar >> checkIfBlock)
             <|> fail "checkIfBlock"
 
-parsePair :: Parser Pair
+parsePair :: Parser (Attr Unresolved)
 parsePair = do
     key <- parseContents ":"
     _ <- char ':'
@@ -137,7 +156,7 @@
     val <- parseContents ";}"
     (char ';' >> return ()) <|> return ()
     whiteSpace
-    return (key, val)
+    return $ Attr key val
 
 parseContents :: String -> Parser Contents
 parseContents = many1 . parseContent
@@ -189,7 +208,7 @@
 luciusFileDebug = cssFileDebug False [|parseTopLevels|] parseTopLevels
 luciusFileReload = luciusFileDebug
 
-parseTopLevels :: Parser [TopLevel]
+parseTopLevels :: Parser [TopLevel Unresolved]
 parseTopLevels =
     go id
   where
@@ -250,34 +269,39 @@
 stringCI [] = return ()
 stringCI (c:cs) = (char (toLower c) <|> char (toUpper c)) >> stringCI cs
 
-luciusRT' :: TL.Text -> Either String ([(Text, Text)] -> Either String [CssTop])
+luciusRT' :: TL.Text
+          -> Either String ([(Text, Text)] -> Either String [TopLevel Resolved])
 luciusRT' tl =
     case parse parseTopLevels (TL.unpack tl) (TL.unpack tl) of
         Left s -> Left $ show s
         Right tops -> Right $ \scope -> go scope tops
   where
-    go :: [(Text, Text)] -> [TopLevel] -> Either String [CssTop]
+    go :: [(Text, Text)]
+       -> [TopLevel Unresolved]
+       -> Either String [TopLevel Resolved]
     go _ [] = Right []
     go scope (TopAtDecl dec cs':rest) = do
         let scope' = map goScope scope
             render = error "luciusRT has no URLs"
         cs <- mapM (contentToBuilderRT scope' render) cs'
         rest' <- go scope rest
-        Right $ AtDecl dec (mconcat cs) : rest'
+        Right $ TopAtDecl dec (mconcat cs) : rest'
     go scope (TopBlock b:rest) = do
         b' <- goBlock scope b
         rest' <- go scope rest
-        Right $ map Css b' ++ rest'
+        Right $ map TopBlock b' ++ rest'
     go scope (TopAtBlock name m' bs:rest) = do
         let scope' = map goScope scope
             render = error "luciusRT has no URLs"
         m <- mapM (contentToBuilderRT scope' render) m'
         bs' <- mapM (goBlock scope) bs
         rest' <- go scope rest
-        Right $ AtBlock name (mconcat m) (concat bs') : rest'
+        Right $ TopAtBlock name (mconcat m) (concat bs') : rest'
     go scope (TopVar k v:rest) = go ((pack k, pack v):scope) rest
 
-    goBlock :: [(Text, Text)] -> Block -> Either String [Css']
+    goBlock :: [(Text, Text)]
+            -> Block Unresolved
+            -> Either String [Block Resolved]
     goBlock scope =
         either Left (Right . ($[])) . blockRuntime scope' (error "luciusRT has no URLs")
       where
@@ -298,3 +322,15 @@
 -- creating systems like yesod devel.
 luciusUsedIdentifiers :: String -> [(Deref, VarType)]
 luciusUsedIdentifiers = cssUsedIdentifiers False parseTopLevels
+
+luciusMixin :: QuasiQuoter
+luciusMixin = QuasiQuoter { quoteExp = luciusMixinFromString }
+
+luciusMixinFromString :: String -> Q Exp
+luciusMixinFromString s' = do
+    r <- newName "_render"
+    case fmap compressBlock $ parse parseBlock s s of
+        Left e -> error $ show e
+        Right block -> blockToMixin r [] block
+  where
+    s = concat ["mixin{", s', "}"]
diff --git a/shakespeare-css.cabal b/shakespeare-css.cabal
--- a/shakespeare-css.cabal
+++ b/shakespeare-css.cabal
@@ -1,5 +1,5 @@
 name:            shakespeare-css
-version:         1.0.3
+version:         1.0.4
 license:         MIT
 license-file:    LICENSE
 author:          Michael Snoyman <michael@snoyman.com>
diff --git a/test/ShakespeareCssTest.hs b/test/ShakespeareCssTest.hs
--- a/test/ShakespeareCssTest.hs
+++ b/test/ShakespeareCssTest.hs
@@ -396,6 +396,39 @@
     }
 }
 |]
+    it "mixins" $ do
+        let bins = [luciusMixin|
+                   bin:bin2;
+                   /* FIXME not currently implementing sublocks in mixins
+                   foo2 {
+                       x: y
+                   }
+                   */
+                   |] :: Mixin
+        -- No sublocks celper "foo{bar:baz;bin:bin2}foo foo2{x:y}" [lucius|
+        celper "foo{bar:baz;bin:bin2}" [lucius|
+            foo {
+                bar: baz;
+                ^{bins}
+            }
+        |]
+    it "more complicated mixins" $ do
+        let transition val =
+                [luciusMixin|
+                    -webkit-transition: #{val};
+                    -moz-transition: #{val};
+                    -ms-transition: #{val};
+                    -o-transition: #{val};
+                    transition: #{val};
+                |]
+
+        celper ".some-class{-webkit-transition:all 4s ease;-moz-transition:all 4s ease;-ms-transition:all 4s ease;-o-transition:all 4s ease;transition:all 4s ease}"
+                [lucius|
+                    .some-class {
+                        ^{transition "all 4s ease"}
+                    }
+                |]
+
 
 data Url = Home | Sub SubUrl
 data SubUrl = SubUrl
