diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,8 @@
+## Stache 2.3.0
+
+* Allowed parsing a wider range of keys in the templates thus making the
+  library's behavior closer to other existing implementations.
+
 ## Stache 2.2.1
 
 * Works with GHC 9.0.1, dropped support for GHC 8.6.x and older.
diff --git a/README.md b/README.md
--- a/README.md
+++ b/README.md
@@ -8,22 +8,20 @@
 
 This is a Haskell implementation of Mustache templates. The implementation
 conforms to the version 1.1.3 of the official [Mustache
-specification](https://github.com/mustache/spec). It is extremely simple and
-straightforward to use with minimal but complete API—three functions to
-compile templates (from directory, from file, and from lazy text) and one to
-render them.
+specification](https://github.com/mustache/spec). It has a minimal but
+complete API—three functions to compile templates (from directory, from
+file, and from lazy text) and one to render them.
 
 The implementation uses the Megaparsec parsing library to parse the
-templates which results in superior quality of error messages.
+templates which results in high-quality error messages.
 
-For rendering you only need to create Aeson's `Value` where you put the data
-to interpolate. Since the library re-uses Aeson's instances and most data
-types in the Haskell ecosystem are instances of classes like
-`Data.Aeson.ToJSON`, the whole process is very simple for the end user.
+For rendering one only needs to create Aeson's `Value` that is used for
+interpolation of template variables. Since the library re-uses Aeson's
+instances and most data types in the Haskell ecosystem are instances of
+classes like `Data.Aeson.ToJSON`, the process is simple for the end user.
 
 Template Haskell helpers for compilation of templates at compile time are
-available in the `Text.Mustache.Compile.TH` module. The helpers currently
-work only with GHC 8 and later.
+available in the `Text.Mustache.Compile.TH` module.
 
 One feature that is not currently supported is lambdas. The feature is
 marked as optional in the spec and can be emulated via processing of parsed
diff --git a/Text/Mustache.hs b/Text/Mustache.hs
--- a/Text/Mustache.hs
+++ b/Text/Mustache.hs
@@ -8,24 +8,22 @@
 -- Portability :  portable
 --
 -- This is a Haskell implementation of Mustache templates. The
--- implementation conforms to the version 1.1.3 of official Mustache
--- specification <https://github.com/mustache/spec>. It is extremely simple
--- and straightforward to use with minimal but complete API—three functions
--- to compile templates (from directory, from file, and from lazy text) and
--- one to render them.
+-- implementation conforms to the version 1.1.3 of the official Mustache
+-- specification <https://github.com/mustache/spec>. It has a minimal but
+-- complete API—three functions to compile templates (from directory, from
+-- file, and from lazy text) and one to render them.
 --
 -- The implementation uses Megaparsec parsing library to parse the templates
--- which results in superior quality of error messages.
+-- which results in high-quality error messages.
 --
--- For rendering you only need to create Aeson's 'Data.Aeson.Value' where
--- you put the data to interpolate. Since the library re-uses Aeson's
--- instances and most data types in the Haskell ecosystem are instances of
--- classes like 'Data.Aeson.ToJSON', the whole process is very simple for
+-- For rendering you only need to create Aeson's 'Data.Aeson.Value' that is
+-- used for interpolation of template variables. Since the library re-uses
+-- Aeson's instances and most data types in the Haskell ecosystem are
+-- instances of classes like 'Data.Aeson.ToJSON', the process is simple for
 -- the end user.
 --
 -- Template Haskell helpers for compilation of templates at compile time are
--- available in the "Text.Mustache.Compile.TH" module. The helpers currently
--- work only with GHC 8 and later.
+-- available in the "Text.Mustache.Compile.TH" module.
 --
 -- One feature that is not currently supported is lambdas. The feature is
 -- marked as optional in the spec and can be emulated via processing of
diff --git a/Text/Mustache/Compile.hs b/Text/Mustache/Compile.hs
--- a/Text/Mustache/Compile.hs
+++ b/Text/Mustache/Compile.hs
@@ -34,9 +34,10 @@
 import Text.Mustache.Parser
 import Text.Mustache.Type
 
--- | Compile all templates in specified directory and select one. Template
--- files should have the extension @mustache@, (e.g. @foo.mustache@) to be
--- recognized. This function /does not/ scan the directory recursively.
+-- | Compile all templates in the specified directory and select one.
+-- Template files should have the extension @mustache@, (e.g.
+-- @foo.mustache@) to be recognized. This function /does not/ scan the
+-- directory recursively.
 --
 -- Note that each template\/partial will get an identifier which consists of
 -- the name of corresponding template file with extension @.mustache@
@@ -80,8 +81,8 @@
       Template _ new <- compileMustacheFile fp
       return (Template undefined (M.union new old))
 
--- | Return a list of templates found in given directory. The returned paths
--- are absolute.
+-- | Return a list of templates found in given a directory. The returned
+-- paths are absolute.
 --
 -- @since 0.2.2
 getMustacheFilesInDir ::
@@ -91,8 +92,8 @@
   m [FilePath]
 getMustacheFilesInDir = getMustacheFilesInDir' isMustacheFile
 
--- | Return a list of templates found via a predicate in given directory.
--- The returned paths are absolute.
+-- | Return a list of templates that satisfy a predicate in a given
+-- directory. The returned paths are absolute.
 --
 -- @since 1.2.0
 getMustacheFilesInDir' ::
@@ -116,7 +117,7 @@
 isMustacheFile :: FilePath -> Bool
 isMustacheFile path = F.takeExtension path == ".mustache"
 
--- | Compile a single Mustache template and select it.
+-- | Compile a Mustache template and select it.
 --
 -- The action can throw 'MustacheParserException' and the same exceptions as
 -- 'T.readFile'.
@@ -132,7 +133,7 @@
     pname = pathToPName path
     compile = fmap (Template pname . M.singleton pname) . parseMustache path
 
--- | Compile Mustache template from a lazy 'Text' value. The cache will
+-- | Compile a Mustache template from a lazy 'Text' value. The cache will
 -- contain only this template named according to given 'PName'.
 compileMustacheText ::
   -- | How to name the template?
@@ -147,12 +148,12 @@
 ----------------------------------------------------------------------------
 -- Helpers
 
--- | Build a 'PName' from given 'FilePath'.
+-- | Build a 'PName' from a given 'FilePath'.
 pathToPName :: FilePath -> PName
 pathToPName = PName . T.pack . F.takeBaseName
 
--- | Throw 'MustacheException' if argument is 'Left' or return the result
--- inside 'Right'.
+-- | Throw 'MustacheException' if the argument is 'Left' or return the
+-- result inside 'Right'.
 withException ::
   -- | Value to process
   Either (ParseErrorBundle Text Void) Template ->
diff --git a/Text/Mustache/Compile/TH.hs b/Text/Mustache/Compile/TH.hs
--- a/Text/Mustache/Compile/TH.hs
+++ b/Text/Mustache/Compile/TH.hs
@@ -14,9 +14,6 @@
 -- Template Haskell helpers to compile Mustache templates at compile time.
 -- This module is not imported as part of "Text.Mustache", so you need to
 -- import it yourself. Qualified import is recommended, but not necessary.
---
--- At the moment, functions in this module only work with GHC 8 (they
--- require at least @template-haskell-2.11@).
 module Text.Mustache.Compile.TH
   ( compileMustacheDir,
     compileMustacheDir',
@@ -36,9 +33,10 @@
 import qualified Text.Mustache.Compile as C
 import Text.Mustache.Type
 
--- | Compile all templates in specified directory and select one. Template
--- files should have the extension @mustache@, (e.g. @foo.mustache@) to be
--- recognized. This function /does not/ scan the directory recursively.
+-- | Compile all templates in the specified directory and select one.
+-- Template files should have the extension @mustache@, (e.g.
+-- @foo.mustache@) to be recognized. This function /does not/ scan the
+-- directory recursively.
 --
 -- This version compiles the templates at compile time.
 --
@@ -71,7 +69,7 @@
   runIO (C.getMustacheFilesInDir' predicate path) >>= mapM_ addDependentFile
   (runIO . try) (C.compileMustacheDir' predicate pname path) >>= handleEither
 
--- | Compile single Mustache template and select it.
+-- | Compile a Mustache template and select it.
 --
 -- This version compiles the template at compile time.
 compileMustacheFile ::
@@ -82,7 +80,7 @@
   runIO (makeAbsolute path) >>= addDependentFile
   (runIO . try) (C.compileMustacheFile path) >>= handleEither
 
--- | Compile Mustache template from 'Text' value. The cache will contain
+-- | Compile a Mustache template from 'Text' value. The cache will contain
 -- only this template named according to given 'Key'.
 --
 -- This version compiles the template at compile time.
@@ -96,7 +94,7 @@
   (handleEither . either (Left . MustacheParserException) Right)
     (C.compileMustacheText pname text)
 
--- | Compile Mustache using QuasiQuoter. Usage:
+-- | Compile a Mustache using a QuasiQuoter. Usage:
 --
 -- > {-# LANGUAGE QuasiQuotes #-}
 -- > import Text.Mustache.Compile.TH (mustache)
@@ -126,10 +124,6 @@
     Left err -> (fail . indentNicely . displayException) err
     Right template -> lift template
   where
-    -- NOTE Since the feature requires GHC 8 anyway, we follow the
-    -- indentation style of that version of compiler. This makes it look
-    -- consistent with other error messages and allows Emacs and similar
-    -- tools to parse the errors correctly.
     indentNicely x' =
       case lines x' of
         [] -> ""
diff --git a/Text/Mustache/Parser.hs b/Text/Mustache/Parser.hs
--- a/Text/Mustache/Parser.hs
+++ b/Text/Mustache/Parser.hs
@@ -19,9 +19,9 @@
 
 import Control.Monad
 import Control.Monad.State.Strict
-import Data.Char (isAlphaNum, isSpace)
+import Data.Char (isSpace)
 import Data.Maybe (catMaybes)
-import Data.Text (Text)
+import Data.Text (Text, stripEnd)
 import qualified Data.Text as T
 import Data.Void
 import Text.Megaparsec
@@ -155,9 +155,14 @@
 pKey = (fmap Key . lexeme . label "key") (implicit <|> other)
   where
     implicit = [] <$ char '.'
-    other = sepBy1 (takeWhile1P (Just lbl) f) (char '.')
-    lbl = "alphanumeric char or '-' or '_'"
-    f x = isAlphaNum x || x == '-' || x == '_'
+    other = do
+      end <- gets closingDel
+      let f x = x `notElem` ('.' : '}' : T.unpack end)
+          lbl = "key-constituent characters"
+      stripLast <$> sepBy1 (takeWhile1P (Just lbl) f) (char '.')
+    stripLast [] = []
+    stripLast [x] = [stripEnd x]
+    stripLast (x0 : x1 : xs) = x0 : stripLast (x1 : xs)
 {-# INLINE pKey #-}
 
 pDelimiter :: Parser Text
diff --git a/Text/Mustache/Render.hs b/Text/Mustache/Render.hs
--- a/Text/Mustache/Render.hs
+++ b/Text/Mustache/Render.hs
@@ -39,9 +39,9 @@
 ----------------------------------------------------------------------------
 -- The rendering monad
 
--- | Synonym for the monad we use for rendering. It allows to share context
--- and accumulate the result as 'B.Builder' data which is then turned into a
--- lazy 'TL.Text'.
+-- | Synonym for the monad we use for rendering. It allows us to share
+-- context and accumulate the result as 'B.Builder' data which is then
+-- turned into a lazy 'TL.Text'.
 type Render a = ReaderT RenderContext (State S) a
 
 data S = S ([MustacheWarning] -> [MustacheWarning]) B.Builder
diff --git a/Text/Mustache/Type.hs b/Text/Mustache/Type.hs
--- a/Text/Mustache/Type.hs
+++ b/Text/Mustache/Type.hs
@@ -43,17 +43,17 @@
 import qualified Language.Haskell.TH.Syntax as TH
 import Text.Megaparsec
 
--- | Mustache template as name of “top-level” template and a collection of
--- all available templates (partials).
+-- | Mustache template as the name of the “top-level” template and a
+-- collection of all available templates (partials).
 --
 -- 'Template' is a 'Semigroup'. This means that you can combine 'Template's
 -- (and their caches) using the @('<>')@ operator, the resulting 'Template'
 -- will have the same currently selected template as the left one. Union of
 -- caches is also left-biased.
 data Template = Template
-  { -- | Name of currently “selected” template (top-level one).
+  { -- | The name of the currently “selected” template.
     templateActual :: PName,
-    -- | Collection of all templates that are available for interpolation
+    -- | A collection of all templates that are available for interpolation
     -- (as partials). The top-level one is also contained here and the
     -- “focus” can be switched easily by modifying 'templateActual'.
     templateCache :: Map PName [Node]
@@ -73,7 +73,7 @@
   liftTyped = TH.unsafeTExpCoerce . TH.lift
 #endif
 
--- | Structural element of template.
+-- | A structural element of a template.
 data Node
   = -- | Plain text contained between tags
     TextBlock Text
@@ -121,7 +121,7 @@
   liftTyped = TH.unsafeTExpCoerce . TH.lift
 #endif
 
--- | Pretty-print a key, this is helpful, for example, if you want to
+-- | Pretty-print a key. This is helpful, for example, if you want to
 -- display an error message.
 --
 -- @since 0.2.0
@@ -167,8 +167,8 @@
 --
 -- @since 1.1.1
 data MustacheWarning
-  = -- | The template contained a variable for which there was no data
-    -- counterpart in the current context.
+  = -- | The template contained a variable for which there was no data in
+    -- the current context.
     MustacheVariableNotFound Key
   | -- | A complex value such as an 'Object' or 'Array' was directly
     -- rendered into the template.
diff --git a/stache.cabal b/stache.cabal
--- a/stache.cabal
+++ b/stache.cabal
@@ -1,6 +1,6 @@
 cabal-version:   1.18
 name:            stache
-version:         2.2.1
+version:         2.3.0
 license:         BSD3
 license-file:    LICENSE.md
 maintainer:      Mark Karpov <markkarpov92@gmail.com>
@@ -109,7 +109,7 @@
         hspec >=2.0 && <3.0,
         hspec-megaparsec >=2.0 && <3.0,
         megaparsec >=7.0 && <10.0,
-        stache -any,
+        stache,
         template-haskell >=2.11 && <2.18,
         text >=1.2 && <1.3
 
@@ -129,10 +129,10 @@
         base >=4.13 && <5.0,
         bytestring >=0.10 && <0.12,
         containers >=0.5 && <0.7,
-        file-embed -any,
+        file-embed,
         hspec >=2.0 && <3.0,
         megaparsec >=7.0 && <10.0,
-        stache -any,
+        stache,
         text >=1.2 && <1.3,
         yaml >=0.8 && <0.12
 
@@ -153,7 +153,7 @@
         criterion >=0.6.2.1 && <1.6,
         deepseq >=1.4 && <1.5,
         megaparsec >=7.0 && <10.0,
-        stache -any,
+        stache,
         text >=1.2 && <1.3
 
     if flag(dev)
diff --git a/tests/Text/Mustache/ParserSpec.hs b/tests/Text/Mustache/ParserSpec.hs
--- a/tests/Text/Mustache/ParserSpec.hs
+++ b/tests/Text/Mustache/ParserSpec.hs
@@ -30,6 +30,10 @@
         p "{{{ name }}}" `shouldParse` [UnescapedVar (key "name")]
       it "parses unescaped {{& variable }}" $
         p "{{& name }}" `shouldParse` [UnescapedVar (key "name")]
+      it "parses escaped {{ nested.variable }}" $
+        p "{{ a.b }}" `shouldParse` [EscapedVar (Key ["a", "b"])]
+      it "parses escaped {{ nested . variable . with . spaces }}" $
+        p "{{   a . b . c   }}" `shouldParse` [EscapedVar (Key ["a ", " b ", " c"])]
     context "without white space" $ do
       it "parses escaped {{variable}}" $
         p "{{name}}" `shouldParse` [EscapedVar (key "name")]
@@ -87,9 +91,10 @@
       p "{{#section}}{{=<< >>=}}<</section>><<var>>"
         `shouldParse` [Section (key "section") [], EscapedVar (key "var")]
   context "when given malformed input" $ do
+    let keyConstErr = "key-constituent characters"
     it "rejects unclosed tags" $ do
       let s = "{{ name "
-      p s `shouldFailWith` err 8 (ueof <> etoks "}}")
-    it "rejects unknown tags" $ do
-      let s = "{{? boo }}"
-      p s `shouldFailWith` err 2 (utoks "?" <> elabel "key")
+      p s `shouldFailWith` err 8 (ueof <> etoks "}}" <> etok '.' <> elabel keyConstErr)
+    it "rejects il-formed expressions" $ do
+      let s = "{{ foo..bar }}"
+      p s `shouldFailWith` err 7 (utoks "." <> elabel keyConstErr)
