diff --git a/haphviz.cabal b/haphviz.cabal
--- a/haphviz.cabal
+++ b/haphviz.cabal
@@ -2,9 +2,15 @@
 -- documentation, see http://haskell.org/cabal/users-guide/
 
 name:                haphviz
-version:             0.1.0.1
+version:             0.1.0.2
 synopsis:            Graphviz code generation with Haskell
-description:         Graphviz code generation with Haskell
+description:
+  There are multiple ways to describe this package:
+
+  - "A Turing-complete encoding system for graphviz"
+  - "A graph visualization code generation EDSL"
+  - "An easy way to draw graphs"
+
 author:              Tom Sydney Kerckhove
 maintainer:          syd.kerckhove@gmail.com
 copyright:           Tom Sydney Kerckhove 2015
@@ -20,11 +26,12 @@
 
 library
   exposed-modules:     Text.Dot
+                       Text.Dot.Gen.FSA
   other-modules:       Text.Dot.Gen
                        Text.Dot.Render
                        Text.Dot.Types.Internal
   -- other-extensions:    
-  build-depends:       base         >= 4.6   && <  4.9
+  build-depends:       base         >= 4.6   && <  4.10
                      , text         >= 1.2   && <  1.3
                      , mtl          >= 2.2   && <  2.3
   hs-source-dirs:      src
diff --git a/src/Text/Dot/Gen/FSA.hs b/src/Text/Dot/Gen/FSA.hs
new file mode 100644
--- /dev/null
+++ b/src/Text/Dot/Gen/FSA.hs
@@ -0,0 +1,55 @@
+{-# LANGUAGE OverloadedStrings #-}
+module Text.Dot.Gen.FSA where
+
+import           Text.Dot
+
+import           Control.Monad (forM, forM_, when)
+import           Data.Maybe    (fromMaybe, isNothing)
+
+import           Data.Text
+import qualified Data.Text     as T
+import qualified Data.Text.IO  as T
+
+-- | An easy way to generate an FSA visualization
+fsaGraph :: [Text] -- ^ Set of states
+          -> Text -- Initial state
+          -> [Text] -- Accepting states
+          -> [(Text, Text, Text)] -- ^ Edges: From, To, Symbol
+          -> DotGraph
+fsaGraph states initial accepting edges = graph_ directed $ do
+    nodeDec [width =: "0", height =: "0"] -- Nodes as small as possible
+    rankdir leftRight
+
+    stateNodes <- forM states $ \s -> do
+        n <- newNode
+        genNode n $
+            if s `elem` accepting
+                then [label =: s, shape =: "doublecircle"]
+                else [label =: s]
+        return (s, n)
+
+    -- Check that accepting states are actually states
+    forM_ accepting $ \s -> do
+        when
+            (isNothing $ lookup s stateNodes)
+            (error $ "Accepting state is not in the set of states: " ++ T.unpack s)
+
+    -- Draw an edge from an invisible state to the initial state
+    case lookup initial stateNodes of
+        Nothing -> error "Initial state is not in the set of states"
+        Just initialNode -> do
+            n <- newNode
+            genNode n ["style" =: "invis"]
+            n --> initialNode
+
+    -- Draw the edges
+    forM_ edges $ \(from, to, symbol) -> do
+        let fromNode = fromMaybe
+                    (error $ "From node not found: " ++ T.unpack from)
+                    (lookup from stateNodes)
+        let toNode = fromMaybe
+                    (error $ "To node not found: " ++ T.unpack to)
+                    (lookup to stateNodes)
+        genEdge fromNode toNode [label =: symbol]
+
+
