diff --git a/package.yaml b/package.yaml
index 2083e409abc62373d23fd690db4e095885eaa994..24c033f065c884f386af724cdc7e291f6e06f9ec 100644
--- a/package.yaml
+++ b/package.yaml
@@ -29,6 +29,7 @@ dependencies:
 - concurrent-extra
 - directory
 - filepath
+- lens
 - mtl
 - network
 - optparse-applicative
diff --git a/src/QBar/BlockOutput.hs b/src/QBar/BlockOutput.hs
deleted file mode 100644
index 23cc2508624ae2b5f59583c6f1c92963c4d464c2..0000000000000000000000000000000000000000
--- a/src/QBar/BlockOutput.hs
+++ /dev/null
@@ -1,39 +0,0 @@
-module BlockOutput where
-
-import qualified Data.Text.Lazy as T
-
-newtype BlockText = BlockText [BlockTextSegment]
-instance Semigroup BlockText where
-  (BlockText a) <> (BlockText b) = BlockText (a <> b)
-instance Monoid BlockText where
-  mempty = BlockText []
-
-data BlockTextSegment = BlockTextSegment {
-    active :: Bool,
-    importance :: Importance,
-    text :: T.Text
-  }
-  | PangoTextSegment T.Text
-
-type Importance = Float
-
-mkText :: Bool -> Importance -> T.Text -> BlockText
-mkText active importance text = BlockText [BlockTextSegment { text, active, importance }]
-
-activeImportantText :: Importance -> T.Text -> BlockText
-activeImportantText = mkText True
-
-importantText :: Importance -> T.Text -> BlockText
-importantText = mkText False
-
-activeText :: T.Text -> BlockText
-activeText = mkText True 0
-
-normalText :: T.Text -> BlockText
-normalText = mkText False 0
-
-pangoText :: T.Text -> BlockText
-pangoText pango = BlockText [PangoTextSegment pango]
-
-surroundWith :: (T.Text -> BlockText) -> T.Text -> T.Text -> BlockText -> BlockText
-surroundWith format left right middle = (format left) <> middle <> (format right)
diff --git a/src/QBar/BlockText.hs b/src/QBar/BlockText.hs
new file mode 100644
index 0000000000000000000000000000000000000000..7f5f1c63d68dbcbca49b33c86b72c511260593b2
--- /dev/null
+++ b/src/QBar/BlockText.hs
@@ -0,0 +1,122 @@
+module QBar.BlockText where
+
+import qualified Data.Text.Lazy as T
+import Data.Int (Int64)
+import QBar.Pango
+
+newtype BlockText = BlockText [BlockTextSegment]
+  deriving (Show)
+instance Semigroup BlockText where
+  (BlockText a) <> (BlockText b) = BlockText (a <> b)
+instance Monoid BlockText where
+  mempty = BlockText []
+
+data BlockTextSegment = BlockTextSegment {
+    active :: Bool,
+    importance :: Importance,
+    text :: T.Text
+  }
+  | PangoTextSegment T.Text
+  deriving (Show)
+
+type Importance = Float
+
+normalImportant :: Importance
+normalImportant = 0
+warnImportant :: Importance
+warnImportant = 1
+errorImportant :: Importance
+errorImportant = 2
+criticalImportant :: Importance
+criticalImportant = 3
+
+isCritical :: Importance -> Bool
+isCritical i
+  | i >= criticalImportant = True
+  | otherwise = False
+isError :: Importance -> Bool
+isError i
+  | isCritical i = False
+  | i >= errorImportant = True
+  | otherwise = False
+isWarning :: Importance -> Bool
+isWarning i
+  | isCritical i = False
+  | isError i = False
+  | i >= warnImportant = True
+  | otherwise = False
+isNormal :: Importance -> Bool
+isNormal i
+  | isCritical i = False
+  | isError i = False
+  | isWarning i = False
+  | otherwise = True
+
+removePango :: BlockText -> T.Text
+removePango (BlockText b) = foldr ((<>) . removePangoFromSegment) "" b
+  where
+    removePangoFromSegment :: BlockTextSegment -> T.Text
+    removePangoFromSegment BlockTextSegment { active=_active, importance=_importance, text } = text
+    removePangoFromSegment (PangoTextSegment text) =
+      case parsePango text of
+        Left _ -> text
+        Right parsed -> removeFormatting parsed
+
+printedLength :: BlockText -> Int64
+printedLength (BlockText b) = foldr ((+) . printedLength') 0 b
+  where
+    printedLength' :: BlockTextSegment -> Int64
+    printedLength' BlockTextSegment { text, active=_, importance=_ } = T.length text
+    printedLength' (PangoTextSegment _) = 0
+
+mkText :: Bool -> Importance -> T.Text -> BlockText
+mkText active importance text = BlockText [BlockTextSegment { text = pangoFriendly text, active, importance }]
+  where
+    pangoFriendly :: T.Text -> T.Text
+    pangoFriendly = T.replace "<" "&lt;" . T.replace ">" "&gt;" . T.replace "&" "&amp;"
+
+activeImportantText :: Importance -> T.Text -> BlockText
+activeImportantText = mkText True
+
+importantText :: Importance -> T.Text -> BlockText
+importantText = mkText False
+
+activeText :: T.Text -> BlockText
+activeText = mkText True normalImportant
+
+normalText :: T.Text -> BlockText
+normalText = mkText False normalImportant
+
+pangoText :: T.Text -> BlockText
+pangoText pango = BlockText [PangoTextSegment pango]
+
+surroundWith :: (T.Text -> BlockText) -> T.Text -> T.Text -> BlockText -> BlockText
+surroundWith format left right middle = format left <> middle <> format right
+
+data Color = ColorRGB Float Float Float | ColorRGBA Float Float Float Float
+colorToHex :: Color -> T.Text
+colorToHex = colorToHex'
+  where
+    colorToHex' :: Color -> T.Text
+    colorToHex' (ColorRGB r g b) = "#" <> (toDualHex . floor) (r * 255) <> (toDualHex . floor) (g * 255)  <> (toDualHex . floor) (b * 255)
+    colorToHex' (ColorRGBA r g b a) = "#" <> (toDualHex . floor) (r * 255) <> (toDualHex . floor) (g * 255)  <> (toDualHex . floor) (b * 255) <> (toDualHex . floor) (a * 255)
+    toHex :: Int -> T.Text
+    toHex 0 = "0"
+    toHex 1 = "1"
+    toHex 2 = "2"
+    toHex 3 = "3"
+    toHex 4 = "4"
+    toHex 5 = "5"
+    toHex 6 = "6"
+    toHex 7 = "7"
+    toHex 8 = "8"
+    toHex 9 = "9"
+    toHex 10 = "A"
+    toHex 11 = "B"
+    toHex 12 = "C"
+    toHex 13 = "D"
+    toHex 14 = "E"
+    toHex 15 = "F"
+    toHex x = toHex $ mod x 16
+    toDualHex :: Int -> T.Text
+    toDualHex x = toHex (div x 16) <> toHex x
diff --git a/src/QBar/Blocks.hs b/src/QBar/Blocks.hs
index 2a953f7eee2cb369da254c36bba6774b336903f9..794ecdb8388059ee0bbacfef7bcadb94c373d1d1 100644
--- a/src/QBar/Blocks.hs
+++ b/src/QBar/Blocks.hs
@@ -4,12 +4,15 @@ module QBar.Blocks where
 
 import QBar.Core
 import QBar.Time
+import QBar.BlockText
 
 import qualified Data.Text.Lazy as T
 import Data.Time.Format
 import Data.Time.LocalTime
 import Pipes
 
+import Control.Lens
+
 dateBlock :: PushBlock
 dateBlock = do
   yield =<< liftIO dateBlockOutput
@@ -21,5 +24,5 @@ dateBlockOutput = do
   zonedTime <- getZonedTime
   let date = T.pack (formatTime defaultTimeLocale "%a %F" zonedTime)
   let time = T.pack (formatTime defaultTimeLocale "%R" zonedTime)
-  let text = (T.pack "📅 ") <> date <> " " <> (coloredText activeColor time)
-  return $ setBlockName "date" $ pangoMarkup $ createBlock text
+  let text = normalText ("📅 " <> date <> " ") <> activeText time
+  return $ blockName ?~ "date" $ createBlock text
diff --git a/src/QBar/ControlSocket.hs b/src/QBar/ControlSocket.hs
index a0411368b02adf4ac43169f7694a5913858ef00e..eb1f20d4fd2b68386e1d3b1b49c9a7ccba34ac08 100644
--- a/src/QBar/ControlSocket.hs
+++ b/src/QBar/ControlSocket.hs
@@ -7,6 +7,7 @@ import QBar.Cli (MainOptions(..))
 import QBar.Core
 -- TODO: remove dependency?
 import QBar.Filter
+import QBar.BlockText
 
 import Control.Monad (forever, void, when)
 import Control.Monad.STM (atomically)
@@ -112,5 +113,5 @@ handleBlockStream producer = do
   where
     handleParsedBlock :: Producer ByteString IO () -> String -> PushBlock
     handleParsedBlock leftovers update = do
-      yield $ createBlock $ TL.pack update
-      handleBlockStream leftovers
\ No newline at end of file
+      yield $ createBlock . normalText $ TL.pack update
+      handleBlockStream leftovers
diff --git a/src/QBar/Core.hs b/src/QBar/Core.hs
index 9b25be1396d685676600c677cf73a042065311a6..7615b4d0eae4a9527c66f2616a7bab0f31898059 100644
--- a/src/QBar/Core.hs
+++ b/src/QBar/Core.hs
@@ -3,7 +3,7 @@
 
 module QBar.Core where
 
-import QBar.Pango
+import QBar.BlockText
 
 import Control.Exception (catch, finally, IOException)
 import Control.Monad (forever)
@@ -15,21 +15,19 @@ import Control.Concurrent.MVar
 import Control.Concurrent.STM.TChan (TChan, writeTChan)
 import Data.Aeson.TH
 import qualified Data.ByteString.Lazy.Char8 as C8
-import qualified Data.HashMap.Lazy as HM
 import Data.Int (Int64)
-import Data.Maybe (fromMaybe, catMaybes)
+import Data.Maybe (catMaybes)
 import qualified Data.Text.Lazy as T
 import qualified Data.Text.Lazy.Encoding as E
 import qualified Data.Text.Lazy.IO as TIO
-import Numeric (showHex)
 import Pipes
 import Pipes.Concurrent
 import qualified Pipes.Prelude as PP
 import System.Exit
 import System.IO
 import System.Process.Typed (shell, withProcessWait, setStdin, setStdout, getStdout, closed, createPipe, readProcessStdout)
+import Control.Lens
 
-import Data.Colour.RGBSpace
 
 data Click = Click {
   name :: T.Text,
@@ -37,12 +35,14 @@ data Click = Click {
 } deriving Show
 $(deriveJSON defaultOptions ''Click)
 
-data BlockOutput = BlockOutput {
-  values :: HM.HashMap T.Text T.Text,
-  clickAction :: Maybe (Click -> BarIO ())
-}
-instance Show BlockOutput where
-  show BlockOutput{values} = show values
+data BlockOutput = BlockOutput
+  { _fullText :: BlockText
+  , _shortText :: Maybe BlockText
+  , _blockName :: Maybe T.Text
+  , _clickAction :: Maybe (Click -> BarIO ())
+  , _invalid :: Bool
+  }
+
 
 data PushMode = PushMode
 data PullMode = PullMode
@@ -60,10 +60,6 @@ type CachedBlock = Block CachedMode
 
 class IsBlock a where
   toCachedBlock :: a -> CachedBlock
-instance IsBlock PushBlock where
-  toCachedBlock = cachePushBlock
-instance IsBlock CachedBlock where
-  toCachedBlock = id
 
 class IsBlockMode a where
   exitBlock :: Block a
@@ -81,90 +77,34 @@ data Bar = Bar {
   requestBarUpdate :: IO (),
   newBlockChan :: TChan CachedBlock
 }
+makeLenses ''BlockOutput
+
+instance IsBlock PushBlock where
+  toCachedBlock = cachePushBlock
+instance IsBlock CachedBlock where
+  toCachedBlock = id
 
 data BarUpdateChannel = BarUpdateChannel (IO ())
 type BarUpdateEvent = Event.Event
 
 
-defaultColor :: T.Text
-defaultColor = "#969896"
-
-activeColor :: T.Text
-activeColor = "#ffffff"
-
-updatingColor :: T.Text
---updatingColor = "#444444"
-updatingColor = "#96989677"
-
-createBlock :: T.Text -> BlockOutput
-createBlock text = setColor defaultColor $ BlockOutput {
-  values = HM.singleton "full_text" text,
-  clickAction = Nothing
-}
+createBlock :: BlockText -> BlockOutput
+createBlock text = BlockOutput
+  { _fullText = text
+  , _shortText = Nothing
+  , _blockName = Nothing
+  , _clickAction = Nothing
+  , _invalid = False
+  }
 
 createErrorBlock :: T.Text -> BlockOutput
-createErrorBlock = setColor "ff0000" . createBlock
-
-setValue :: T.Text -> T.Text -> BlockOutput -> BlockOutput
-setValue key val block = block {
-  values = HM.insert key val (values block)
-}
-
-getValue :: T.Text -> BlockOutput -> Maybe T.Text
-getValue key block = HM.lookup key (values block)
-
-adjustValue :: (T.Text -> T.Text) -> T.Text -> BlockOutput -> BlockOutput
-adjustValue f k block = block {
-  values = HM.adjust f k (values block)
-}
+createErrorBlock = createBlock . importantText criticalImportant
 
 emptyBlock :: BlockOutput
-emptyBlock = createBlock ""
-
-shortText :: T.Text -> BlockOutput -> BlockOutput
-shortText = setValue "short_text"
-
-fullText :: T.Text -> BlockOutput -> BlockOutput
-fullText = setValue "full_text"
-
-getFullText :: BlockOutput -> T.Text
-getFullText = fromMaybe "" . getValue "full_text"
-
-setColor :: T.Text -> BlockOutput -> BlockOutput
-setColor = setValue "color"
-
-setBlockName :: T.Text -> BlockOutput -> BlockOutput
-setBlockName = setValue "name"
-
-getBlockName :: BlockOutput -> Maybe T.Text
-getBlockName = getValue "name"
-
-pangoMarkup :: BlockOutput -> BlockOutput
-pangoMarkup = setValue "markup" "pango"
-
-adjustText :: (T.Text -> T.Text) -> BlockOutput -> BlockOutput
-adjustText f = adjustValue f "full_text" . adjustValue f "short_text"
-
-coloredText :: T.Text -> T.Text -> T.Text
-coloredText color text = "<span color='" <> color <> "'>" <> text <> "</span>"
+emptyBlock = createBlock mempty
 
 addIcon :: T.Text -> BlockOutput -> BlockOutput
-addIcon icon block = prefixIcon "full_text" $ prefixIcon "short_text" block
-  where
-    prefixIcon = adjustValue ((icon <> " ") <>)
-
-removePango :: BlockOutput -> BlockOutput
-removePango block
-  | getValue "markup" block == Just "pango" = adjustText removePangoFromText $ block {
-      values = HM.delete "markup" (values block)
-    }
-  | otherwise = block
-  where
-    removePangoFromText :: T.Text -> T.Text
-    removePangoFromText text =
-      case parsePango text of
-        Left _ -> text
-        Right parsed -> removeFormatting parsed
+addIcon icon = over fullText $ (<>) . normalText $ icon <> " "
 
 modify :: (BlockOutput -> BlockOutput) -> Pipe BlockOutput BlockOutput BarIO r
 modify = PP.map
@@ -175,16 +115,19 @@ autoPadding = autoPadding' 0 0
     autoPadding' :: Int64 -> Int64 -> Pipe BlockOutput BlockOutput BarIO r
     autoPadding' fullLength shortLength = do
       block <- await
-      let values' = (values block)
-      let fullLength' = T.length $ HM.lookupDefault "" "full_text" values'
-      let shortLength' = T.length $ HM.lookupDefault "" "short_text" values'
-      let values'' = HM.adjust (<> (T.take (fullLength - fullLength') $ T.repeat ' ')) "full_text" values'
-      let values''' = HM.adjust (<> (T.take (shortLength - shortLength') $ T.repeat ' ')) "short_text" values''
-      yield block { values = values''' }
-      autoPadding' (max fullLength fullLength') (max shortLength shortLength')
+      let fullLength' = max fullLength . printedLength $ block^.fullText
+      let shortLength' = max shortLength . printedLength $ block^.shortText._Just -- TODO: ???
+      yield $ padFullText fullLength' . padShortText shortLength' $ block
+      autoPadding' fullLength' shortLength'
+    padString :: Int64 -> BlockText
+    padString len = normalText . T.take len . T.repeat $ ' '
+    padFullText :: Int64 -> BlockOutput -> BlockOutput
+    padFullText len = over fullText $ \s -> padString (len - printedLength s) <> s
+    padShortText :: Int64 -> BlockOutput -> BlockOutput
+    padShortText len = over (shortText._Just) $ \s -> padString (len - printedLength s) <> s
 
 cacheFromInput :: Input BlockOutput -> CachedBlock
-cacheFromInput input = const CachedMode <$> fromInput input
+cacheFromInput input = CachedMode <$ fromInput input
 
 -- | Create a shared interval. Takes a BarUpdateChannel to signal bar updates and an interval (in seconds).Data.Maybe
 -- Returns an IO action that can be used to attach blocks to the shared interval and an async that contains a reference to the scheduler thread.
@@ -223,7 +166,7 @@ sharedInterval seconds = do
           Left _ -> return (exitBlock, False)
           Right (blockOutput, blockProducer') -> do
             success <- atomically $ send output blockOutput {
-              clickAction = Just (updateClickHandler blockOutput)
+              _clickAction = Just (updateClickHandler blockOutput)
             }
             if success
               -- Store new BlockProducer back into MVar
@@ -234,8 +177,8 @@ sharedInterval seconds = do
         updateClickHandler :: BlockOutput -> Click -> BarIO ()
         updateClickHandler block _ = do
           -- Give user feedback that the block is updating
-          let outdatedBlock = setColor updatingColor $ removePango block
-          liftIO $ void $ atomically $ send output $ outdatedBlock
+          let outdatedBlock = block & invalid.~True
+          liftIO $ void $ atomically $ send output outdatedBlock
           -- Notify bar about changed block state to display the feedback
           updateBar
           -- Run a normal block update to update the block to the new value
@@ -262,7 +205,7 @@ sharedInterval seconds = do
       cacheFromInput input
 
 blockScript :: FilePath -> PullBlock
-blockScript path = forever $ yield =<< (lift $ blockScriptAction)
+blockScript path = forever $ yield =<< (lift blockScriptAction)
   where
     blockScriptAction :: BarIO BlockOutput
     blockScriptAction = do
@@ -271,33 +214,36 @@ blockScript path = forever $ yield =<< (lift $ blockScriptAction)
       (exitCode, output) <- liftIO $ readProcessStdout $ shell path
       case exitCode of
         ExitSuccess -> return $ case map E.decodeUtf8 (C8.lines output) of
-          (text:short:color:_) -> setColor color $ shortText short $ createScriptBlock text
-          (text:short:_) -> shortText short $ createScriptBlock text
+          -- TODO: Fix this, but how?
+          --   PangoSegments cannot have external formatting, so either allow that here,
+          --   or duplicate the function into ango and nonPango variants.
+          -- (text:short:color:_) -> setColor color $ shortText short $ createScriptBlock text
+          (text:short:_) -> shortText ?~ pangoText short $ createScriptBlock text
           (text:_) -> createScriptBlock text
           [] -> createScriptBlock "-"
-        (ExitFailure nr) -> return $ createErrorBlock $ "[" <> (T.pack $ show nr) <> "]"
+        (ExitFailure nr) -> return $ createErrorBlock $ "[" <> T.pack (show nr) <> "]"
     createScriptBlock :: T.Text -> BlockOutput
-    createScriptBlock text = pangoMarkup $ setBlockName (T.pack path) $ createBlock text
+    createScriptBlock text = blockName ?~ T.pack path $ createBlock . pangoText $ text
 
 startPersistentBlockScript :: FilePath -> CachedBlock
 -- This is only using 'CachedBlock' because the code was already written and tested
 -- This could probably be massively simplified by using the new 'pushBlock'
 startPersistentBlockScript path = do
-  bar <- lift $ ask
+  bar <- lift ask
   do
-    (output, input, seal) <- liftIO $ spawn' $ latest $ emptyBlock
-    initialDataEvent <- liftIO $ Event.new
+    (output, input, seal) <- liftIO $ spawn' $ latest emptyBlock
+    initialDataEvent <- liftIO Event.new
     task <- liftIO $ async $ do
       let processConfig = setStdin closed $ setStdout createPipe $ shell path
       finally (
         catch (
           withProcessWait processConfig $ \ process -> do
             let handle = getStdout process
-            runEffect $ (fromHandle bar handle) >-> signalFirstBlock initialDataEvent >-> toOutput output
+            runEffect $ fromHandle bar handle >-> signalFirstBlock initialDataEvent >-> toOutput output
           )
           ( \ e ->
             -- output error
-            runEffect $ (yield $ createErrorBlock $ "[" <> (T.pack $ show (e :: IOException)) <> "]") >-> signalFirstBlock initialDataEvent >-> toOutput output
+            runEffect $ yield (createErrorBlock $ "[" <> T.pack (show (e :: IOException)) <> "]") >-> signalFirstBlock initialDataEvent >-> toOutput output
           )
         )
         (atomically seal)
@@ -315,23 +261,9 @@ startPersistentBlockScript path = do
     fromHandle :: Bar -> Handle -> Producer BlockOutput IO ()
     fromHandle bar handle = forever $ do
       line <- lift $ TIO.hGetLine handle
-      yield $ pangoMarkup $ createBlock line
+      yield $ createBlock . pangoText $ line
       lift $ updateBar' bar
 
-pangoColor :: RGB Double -> T.Text
-pangoColor (RGB r g b) =
-  let r' = hexColorComponent r
-      g' = hexColorComponent g
-      b' = hexColorComponent b
-  in "#" <> r' <> g' <> b'
-  where
-    hexColorComponent :: Double -> T.Text
-    hexColorComponent val = paddedHexComponent $ T.pack $ showHex (max 0 $ min 255 $ (truncate (val * 255) :: Int)) ""
-    paddedHexComponent hex =
-      let len = 2 - T.length hex
-          padding = if len == 1 then "0" else ""
-      in padding <> hex
-
 
 addBlock :: IsBlock a => a -> BarIO ()
 addBlock block = do
@@ -370,7 +302,7 @@ cachePushBlock pushBlock = lift (next pushBlock) >>= either (const exitBlock) wi
       -- The void is discarding the boolean result that indicates if the mailbox is sealed
       -- This is ok because a cached block is never sealed from the receiving side
       liftIO $ atomically $ void $ send output $ Just blockOutput
-      lift $ updateBar
+      lift updateBar
     terminateOnMaybe :: Producer (Maybe BlockOutput) BarIO () -> Producer BlockOutput BarIO CachedMode
     terminateOnMaybe p = do
       eitherMaybeValue <- lift $ next p
diff --git a/src/QBar/DefaultConfig.hs b/src/QBar/DefaultConfig.hs
index aeaee53898b518cbbc32de95c45c2c125625345b..b429901ad5e603e705b94af11c4a111b34f3c172 100644
--- a/src/QBar/DefaultConfig.hs
+++ b/src/QBar/DefaultConfig.hs
@@ -5,6 +5,8 @@ import QBar.Core
 
 import Pipes
 
+import Control.Lens
+
 blockLocation :: String -> FilePath
 blockLocation name = "~/.config/qbar/blocks/" <> name
 
@@ -15,7 +17,7 @@ generateDefaultBarConfig = do
   let todo = systemInfoInterval (blockScript $ blockLocation "todo")
   let wifi = systemInfoInterval (blockScript $ blockLocation "wifi2 wlan") >-> modify (addIcon "📡")
   let networkEnvironment = systemInfoInterval (blockScript $ blockLocation "network-environment")
-  let cpu = systemInfoInterval (blockScript $ blockLocation "cpu_usage") >-> modify (setBlockName "cpu" . addIcon "💻") >-> autoPadding
+  let cpu = systemInfoInterval (blockScript $ blockLocation "cpu_usage") >-> modify ((blockName?~"cpu") . addIcon "💻") >-> autoPadding
   let ram = systemInfoInterval (blockScript $ blockLocation "memory") >-> modify (addIcon "🐏") >-> autoPadding
   let temperature = systemInfoInterval (blockScript $ blockLocation "temperature") >-> autoPadding
   let volumeBlock = startPersistentBlockScript $ blockLocation "volume-pulseaudio -S -F3"
@@ -29,4 +31,4 @@ generateDefaultBarConfig = do
   addBlock cpu
   addBlock networkEnvironment
   addBlock wifi
-  addBlock todo
\ No newline at end of file
+  addBlock todo
diff --git a/src/QBar/Filter.hs b/src/QBar/Filter.hs
index c848475c36fbaa73cf02fca2ae489fb0dbe4bc77..699261728a0f23d8e8a1f1fa4366699bcd11e87d 100644
--- a/src/QBar/Filter.hs
+++ b/src/QBar/Filter.hs
@@ -3,11 +3,17 @@
 module QBar.Filter where
 
 import QBar.Core
+import QBar.BlockText
 
 import Control.Monad.State.Lazy (State, evalState, get, put)
 import Data.Aeson.TH
 import Data.Colour.RGBSpace.HSV (hsv)
 import qualified Data.Text.Lazy as T
+import Control.Lens hiding (index)
+
+import Numeric (showHex)
+import Data.Colour.RGBSpace
+
 
 data Filter = StaticFilter StaticFilter
   | AnimatedFilter AnimatedFilter
@@ -34,17 +40,36 @@ applyFilter (AnimatedFilter Rainbow) = rainbow
 static :: a -> Double -> a
 static fn _ = fn
 
+
+coloredText :: T.Text -> T.Text -> T.Text
+coloredText color text = "<span color='" <> color <> "'>" <> text <> "</span>"
+
+
+pangoColor :: RGB Double -> T.Text
+pangoColor (RGB r g b) =
+  let r' = hexColorComponent r
+      g' = hexColorComponent g
+      b' = hexColorComponent b
+  in "#" <> r' <> g' <> b'
+  where
+    hexColorComponent :: Double -> T.Text
+    hexColorComponent val = paddedHexComponent $ T.pack $ showHex (max 0 $ min 255 (truncate (val * 255) :: Int)) ""
+    paddedHexComponent hex =
+      let len = 2 - T.length hex
+          padding = if len == 1 then "0" else ""
+      in padding <> hex
+
+
 rainbow :: Double -> [BlockOutput] -> [BlockOutput]
 rainbow time blocks = reverse $ evalState (mapM rainbowBlock $ reverse blocks) 0
   where
     rainbowBlock :: BlockOutput -> State Integer BlockOutput
     rainbowBlock block = do
-      let cleanBlock = removePango block
-      let text = getFullText cleanBlock
+      let text = removePango $ block^.fullText
       let chars = T.unpack . T.reverse $ text
       coloredChars <- mapM rainbowChar chars
       let rainbowText = T.concat . reverse $ coloredChars
-      return $ pangoMarkup $ fullText rainbowText $ cleanBlock
+      return $ fullText .~ pangoText rainbowText $ block
     rainbowChar :: Char -> State Integer T.Text
     rainbowChar char = do
       color <- nextRainbowColor
@@ -56,6 +81,7 @@ rainbow time blocks = reverse $ evalState (mapM rainbowBlock $ reverse blocks) 0
       put $ index + 1
       return $ rainbowColor (fromInteger index + time * 10)
 
+
 rainbowColor :: Double -> T.Text
 rainbowColor position =
   let hue' = position * 3
diff --git a/src/QBar/Server.hs b/src/QBar/Server.hs
index 9a282a9fc3822fc1ca45fa6b1e6cea25dbccab9b..12c663646312530f0e5825c109ad32d9d6679f2a 100644
--- a/src/QBar/Server.hs
+++ b/src/QBar/Server.hs
@@ -1,5 +1,3 @@
-{-# LANGUAGE OverloadedStrings #-}
-
 module QBar.Server where
 
 import QBar.Blocks
@@ -7,6 +5,8 @@ import QBar.Core
 import QBar.Cli
 import QBar.ControlSocket
 import QBar.Filter
+import QBar.BlockText
+import QBar.Themes
 
 import Control.Monad (forever, when, unless)
 import Control.Monad.Reader (runReaderT, ask)
@@ -15,18 +15,19 @@ import Control.Concurrent (threadDelay, forkFinally)
 import Control.Concurrent.Async
 import Control.Concurrent.Event as Event
 import Control.Concurrent.STM.TChan (TChan, newTChanIO, readTChan, tryReadTChan)
-import Data.Aeson (encode, decode)
+import Data.Aeson (encode, decode, ToJSON, toJSON, object, (.=))
 import Data.ByteString.Lazy (hPut)
 import qualified Data.ByteString.Char8 as BSSC8
 import qualified Data.ByteString.Lazy as BS
 import qualified Data.ByteString.Lazy.Char8 as C8
 import Data.IORef
-import Data.Maybe (isJust, fromJust, catMaybes, mapMaybe)
+import Data.Maybe (catMaybes, mapMaybe, fromMaybe)
 import qualified Data.Text.Lazy as T
 import Data.Time.Clock.POSIX
 import Pipes
 import System.IO (stdin, stdout, stderr, hFlush, hPutStrLn)
 import System.Posix.Signals
+import Control.Lens hiding (each, (.=))
 
 data Handle = Handle {
   handleActionList :: IORef [(T.Text, Click -> BarIO ())],
@@ -35,7 +36,7 @@ data Handle = Handle {
 
 renderIndicator :: CachedBlock
 -- Using 'cachedBlock' is a hack to actually get the block to update on every bar update (by doing this it will not get a cache later in the pipeline).
-renderIndicator = forever $ each $ map createBlock ["/", "-", "\\", "|"]
+renderIndicator = forever $ each $ map (createBlock . normalText) ["/", "-", "\\", "|"]
 
 runBlock :: CachedBlock -> BarIO (Maybe (BlockOutput, CachedBlock))
 runBlock producer = do
@@ -47,6 +48,19 @@ runBlock producer = do
 runBlocks :: [CachedBlock] -> BarIO ([BlockOutput], [CachedBlock])
 runBlocks block = unzip . catMaybes <$> mapM runBlock block
 
+data RenderBlock = RenderBlock T.Text (Maybe T.Text) (Maybe T.Text)
+  deriving(Show)
+instance ToJSON RenderBlock where
+  toJSON (RenderBlock fullText' shortText' blockName') = object $
+    fullText'' <> shortText'' <> blockName'' <> pango''
+    where
+      fullText'' = [ "full_text" .= fullText' ]
+      shortText'' = fromMaybe (\s -> ["short_text".=s]) mempty shortText'
+      blockName'' = fromMaybe (\s -> ["block_name".=s]) mempty blockName'
+      pango'' = [ "markup" .= ("pango" :: T.Text) ]
+
+
+
 renderLoop :: MainOptions -> Handle -> BarUpdateEvent -> BS.ByteString -> TChan CachedBlock -> BarIO ()
 renderLoop options handle@Handle{handleActiveFilter} barUpdateEvent previousBarOutput newBlockChan = renderLoop' previousBarOutput []
   where
@@ -82,7 +96,8 @@ renderLine :: MainOptions -> Handle -> Filter -> [BlockOutput] -> BS.ByteString
 renderLine MainOptions{verbose} Handle{handleActionList} blockFilter blocks previousEncodedOutput = do
   time <- fromRational . toRational <$> getPOSIXTime
   let filteredBlocks = applyFilter blockFilter time blocks
-  let encodedOutput = encode $ map values filteredBlocks
+  -- let encodedOutput = encode $ map values filteredBlocks
+  let encodedOutput = encodeOutput filteredBlocks
   let changed = previousEncodedOutput /= encodedOutput
   when changed $ do
     hPut stdout encodedOutput
@@ -101,15 +116,19 @@ renderLine MainOptions{verbose} Handle{handleActionList} blockFilter blocks prev
 
   return encodedOutput
   where
+    theme :: Theme
+    theme = defaultTheme
+    encodeOutput :: [BlockOutput] -> BS.ByteString
+    encodeOutput bs = encode $ zipWith encodeBlock bs $ theme bs
+    encodeBlock :: BlockOutput -> (T.Text, Maybe T.Text) -> RenderBlock
+    encodeBlock b (fullText', shortText') = RenderBlock fullText' shortText' (b^.blockName)
     clickActionList :: [(T.Text, Click -> BarIO ())]
     clickActionList = mapMaybe getClickAction blocks
     getClickAction :: BlockOutput -> Maybe (T.Text, Click -> BarIO ())
-    getClickAction block = if hasBlockName && hasClickAction then Just (fromJust maybeBlockName, fromJust maybeClickAction) else Nothing
-      where
-        maybeBlockName = getBlockName block
-        hasBlockName = isJust maybeBlockName
-        maybeClickAction = clickAction block
-        hasClickAction = isJust maybeClickAction
+    getClickAction block = do
+      blockName' <- block^.blockName
+      clickAction' <- block^.clickAction
+      return (blockName', clickAction')
 
 createBarUpdateChannel :: IO (IO (), BarUpdateEvent)
 createBarUpdateChannel = do
@@ -162,7 +181,7 @@ renderInitialBlocks options handle blockFilter = do
   date <- dateBlockOutput
   let initialBlocks = [date]
   -- Attach spinner indicator when verbose flag is set
-  let initialBlocks' = if indicator options then initialBlocks <> [createBlock "*"] else initialBlocks
+  let initialBlocks' = if indicator options then initialBlocks <> [createBlock . normalText $ "*"] else initialBlocks
   -- Render initial time block so the bar is not empty after startup
   renderLine options handle blockFilter initialBlocks' ""
 
@@ -234,4 +253,4 @@ runQBar barConfiguration options@MainOptions{barCommand} = runCommand barCommand
   where
     runCommand BarServer = runBarConfiguration barConfiguration options
     runCommand NoFilter = sendIpc options $ SetFilter $ StaticFilter None
-    runCommand RainbowFilter = sendIpc options $ SetFilter $ AnimatedFilter Rainbow
\ No newline at end of file
+    runCommand RainbowFilter = sendIpc options $ SetFilter $ AnimatedFilter Rainbow
diff --git a/src/QBar/Themes.hs b/src/QBar/Themes.hs
new file mode 100644
index 0000000000000000000000000000000000000000..09728fc5da5477028749abeb20b436bc2ec2db15
--- /dev/null
+++ b/src/QBar/Themes.hs
@@ -0,0 +1,65 @@
+module QBar.Themes where
+
+
+import QBar.BlockText
+import QBar.Core
+
+import qualified Data.Text.Lazy as T
+
+import Control.Lens
+
+
+type Theme = [BlockOutput] -> [(T.Text, Maybe T.Text)]
+type SimplifiedTheme = Bool -> Importance -> (Color, Maybe Color)
+type AnimatedTheme = Double -> Theme
+
+
+mkTheme :: SimplifiedTheme -> Theme
+mkTheme theming' = map themeBlock
+  where
+    themeBlock :: BlockOutput -> (T.Text, Maybe T.Text)
+    themeBlock block = (fullText', shortText')
+      where
+        theming :: SimplifiedTheme
+        theming
+          | block^.invalid = invalidSimplifiedTheme
+          | otherwise = theming'
+        fullText' :: T.Text
+        fullText' = themeBlockText theming $ block^.fullText
+        shortText' :: Maybe T.Text
+        shortText' = themeBlockText theming <$> block^.shortText
+    themeBlockText :: SimplifiedTheme -> BlockText -> T.Text
+    themeBlockText theming (BlockText b) = foldr ((<>) . themeSegment theming) "" b
+    themeSegment :: SimplifiedTheme -> BlockTextSegment -> T.Text
+    themeSegment theming BlockTextSegment {active, importance, text} = (applyTheme $ theming active importance) text
+    themeSegment _ (PangoTextSegment text) = text
+    applyTheme :: (Color, Maybe Color) -> T.Text -> T.Text
+    applyTheme (fc, Just bc) s = "<span color='" <> colorToHex fc <> "' background='" <> colorToHex bc <> "'>" <> s <> "</span>"
+    applyTheme (fc, Nothing) s = "<span color='" <> colorToHex fc <> "'>" <> s <> "</span>"
+
+
+invalidColor :: Color
+invalidColor = ColorRGBA (0x96/255) (0x98/255) (0x96/255) (0x77/255)
+
+
+invalidSimplifiedTheme :: SimplifiedTheme
+invalidSimplifiedTheme _ _ = (invalidColor, Nothing)
+
+
+invalidTheme :: Theme
+invalidTheme = mkTheme invalidSimplifiedTheme
+
+
+defaultTheme :: Theme
+defaultTheme = mkTheme defaultTheme'
+  where
+    defaultTheme' :: SimplifiedTheme
+    defaultTheme' active importance
+      | isCritical importance, active = (ColorRGB 0 0 0, Just $ ColorRGB 1 0 0)
+      | isCritical importance         = (ColorRGB 0.8 0.15 0.15, Nothing)
+      | isError importance, active    = (ColorRGB 1 0.3 0, Nothing)
+      | isError importance            = (ColorRGB 0.7 0.35 0.2, Nothing)
+      | isWarning importance,active   = (ColorRGB 1 0.9 0, Nothing)
+      | isWarning importance          = (ColorRGB 0.6 0.6 0, Nothing)
+      | otherwise, active             = (ColorRGB 1 1 1, Nothing)
+      | otherwise                     = (ColorRGB (0x96/255) (0x98/255) (0x96/255), Nothing)