diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,22 @@
 Brick changelog
 ---------------
 
+2.4
+---
+
+Changes:
+* The `Keybindings` API now normalizes keybindings
+  to lowercase when modifiers are present. (See also
+  https://github.com/jtdaugherty/brick/issues/512) This means that,
+  for example, a constructed binding for `C-X` would be normalized to
+  `C-x`, and a binding from a configuration file written `C-X` would be
+  parsed and then normalized to `C-x`. This is because, in general, when
+  modifiers are present, input events are received for the lowercase
+  version of the character in question. Prior to changing this, Brick
+  would silently parse (or permit the construction of) uppercase-mapped
+  key bindings, but in practice those bindings were unusable because
+  they are not generated by terminals.
+
 2.3.2
 -----
 
diff --git a/brick.cabal b/brick.cabal
--- a/brick.cabal
+++ b/brick.cabal
@@ -1,5 +1,5 @@
 name:                brick
-version:             2.3.2
+version:             2.4
 synopsis:            A declarative terminal user interface library
 description:
   Write terminal user interfaces (TUIs) painlessly with 'brick'! You
@@ -77,6 +77,7 @@
     Brick.Keybindings.KeyConfig
     Brick.Keybindings.KeyEvents
     Brick.Keybindings.KeyDispatcher
+    Brick.Keybindings.Normalize
     Brick.Keybindings.Parse
     Brick.Keybindings.Pretty
     Brick.Focus
@@ -492,22 +493,6 @@
                        vty,
                        text,
                        microlens
-
-executable brick-unix-suspend-demo
-  hs-source-dirs:      programs
-  ghc-options:         -threaded -Wall -Wcompat -O2
-  default-language:    Haskell2010
-  main-is:             UnixSuspendDemo.hs
-  build-depends:       base,
-                       brick,
-                       vty,
-                       text,
-                       vector,
-                       mtl,
-                       microlens >= 0.3.0.0,
-                       microlens-th,
-                       microlens-mtl,
-                       unix
 
 executable brick-edit-demo
   if !flag(demos)
diff --git a/programs/CustomEventDemo.hs b/programs/CustomEventDemo.hs
--- a/programs/CustomEventDemo.hs
+++ b/programs/CustomEventDemo.hs
@@ -54,9 +54,9 @@
     case e of
         VtyEvent (V.EvKey V.KEsc []) -> halt
         VtyEvent _ -> stLastBrickEvent .= (Just e)
-        AppEvent Counter -> do
-            stCounter %= (+1)
-            stLastBrickEvent .= (Just e)
+        -- AppEvent Counter -> do
+        --     stCounter %= (+1)
+        --     stLastBrickEvent .= (Just e)
         _ -> return ()
 
 initialState :: St
diff --git a/programs/UnixSuspendDemo.hs b/programs/UnixSuspendDemo.hs
deleted file mode 100644
--- a/programs/UnixSuspendDemo.hs
+++ /dev/null
@@ -1,33 +0,0 @@
-module Main where
-
-import qualified Graphics.Vty as V
-
-import qualified Brick.Main as M
-import qualified Brick.Types as T
-import Brick.Widgets.Core (str)
-import qualified Brick.AttrMap as A
-import System.Posix.Signals
-
-drawUI :: () -> [T.Widget ()]
-drawUI () = [str "Press C-z to suspend or any other key to quit"]
-
-appEvent :: T.BrickEvent () e -> T.EventM () () ()
-appEvent (T.VtyEvent (V.EvKey (V.KChar 'z') [V.MCtrl])) = do
-    st <- T.get
-    M.suspendAndResume $ do
-        raiseSignal keyboardStop
-        return st
-appEvent _ =
-    M.halt
-
-theApp :: M.App () e ()
-theApp =
-    M.App { M.appDraw = drawUI
-          , M.appChooseCursor = M.neverShowCursor
-          , M.appHandleEvent = appEvent
-          , M.appStartEvent = return ()
-          , M.appAttrMap = const $ A.attrMap V.defAttr []
-          }
-
-main :: IO ()
-main = M.defaultMain theApp ()
diff --git a/src/Brick/Keybindings/KeyConfig.hs b/src/Brick/Keybindings/KeyConfig.hs
--- a/src/Brick/Keybindings/KeyConfig.hs
+++ b/src/Brick/Keybindings/KeyConfig.hs
@@ -47,6 +47,7 @@
 import qualified Graphics.Vty as Vty
 
 import Brick.Keybindings.KeyEvents
+import Brick.Keybindings.Normalize
 
 -- | A key binding.
 --
@@ -67,10 +68,12 @@
             -- ^ The set of modifiers.
             } deriving (Eq, Show, Ord)
 
--- | Construct a 'Binding'. Modifier order is ignored.
+-- | Construct a 'Binding'. Modifier order is ignored. If modifiers
+-- are given and the binding is for a character key, it is forced to
+-- lowercase.
 binding :: Vty.Key -> [Vty.Modifier] -> Binding
 binding k mods =
-    Binding { kbKey = k
+    Binding { kbKey = normalizeKey mods k
             , kbMods = S.fromList mods
             }
 
@@ -230,17 +233,23 @@
 addModifier :: (ToBinding a) => Vty.Modifier -> a -> Binding
 addModifier m val =
     let b = bind val
-    in b { kbMods = S.insert m (kbMods b) }
+        newMods = S.insert m $ kbMods b
+    in b { kbMods = newMods
+         , kbKey = normalizeKey (S.toList newMods) $ kbKey b
+         }
 
--- | Add Meta to a binding.
+-- | Add Meta to a binding. If the binding is for a character key, force
+-- it to lowercase.
 meta :: (ToBinding a) => a -> Binding
 meta = addModifier Vty.MMeta
 
--- | Add Ctrl to a binding.
+-- | Add Ctrl to a binding. If the binding is for a character key, force
+-- it to lowercase.
 ctrl :: (ToBinding a) => a -> Binding
 ctrl = addModifier Vty.MCtrl
 
--- | Add Shift to a binding.
+-- | Add Shift to a binding. If the binding is for a character key, force
+-- it to lowercase.
 shift :: (ToBinding a) => a -> Binding
 shift = addModifier Vty.MShift
 
diff --git a/src/Brick/Keybindings/Normalize.hs b/src/Brick/Keybindings/Normalize.hs
new file mode 100644
--- /dev/null
+++ b/src/Brick/Keybindings/Normalize.hs
@@ -0,0 +1,14 @@
+module Brick.Keybindings.Normalize
+  ( normalizeKey
+  )
+where
+
+import Data.Char (toLower)
+import qualified Graphics.Vty as Vty
+
+-- | A keybinding involving modifiers should have its key character
+-- normalized to lowercase since it's impossible to get uppercase keys
+-- from the terminal when modifiers are present.
+normalizeKey :: [Vty.Modifier] -> Vty.Key -> Vty.Key
+normalizeKey (_:_) (Vty.KChar c) = Vty.KChar $ toLower c
+normalizeKey _ k = k
diff --git a/src/Brick/Keybindings/Parse.hs b/src/Brick/Keybindings/Parse.hs
--- a/src/Brick/Keybindings/Parse.hs
+++ b/src/Brick/Keybindings/Parse.hs
@@ -5,6 +5,7 @@
 module Brick.Keybindings.Parse
   ( parseBinding
   , parseBindingList
+  , normalizeKey
 
   , keybindingsFromIni
   , keybindingsFromFile
@@ -14,7 +15,6 @@
 
 import Control.Monad (forM)
 import Data.Maybe (catMaybes)
-import qualified Data.Set as S
 import qualified Data.Text as T
 import qualified Data.Text.IO as T
 import qualified Graphics.Vty as Vty
@@ -23,6 +23,7 @@
 
 import Brick.Keybindings.KeyEvents
 import Brick.Keybindings.KeyConfig
+import Brick.Keybindings.Normalize
 
 -- | Parse a key binding list into a 'BindingState'.
 --
@@ -83,7 +84,7 @@
 parseBinding s = go (T.splitOn "-" $ T.toLower s) []
   where go [k] mods = do
           k' <- pKey k
-          return Binding { kbMods = S.fromList mods, kbKey = k' }
+          return $ binding k' mods
         go (k:ks) mods = do
           m <- case k of
             "s"       -> return Vty.MShift
