diff --git a/idn.cabal b/idn.cabal
--- a/idn.cabal
+++ b/idn.cabal
@@ -1,6 +1,6 @@
 cabal-version: 2.2
 name: idn
-version: 0.1.1.0
+version: 0.1.2.0
 synopsis: Pure Haskell IDN and Punycode implementation
 description: 
     Pure Haskell implementation of Internationalized Domain Names (IDN)
diff --git a/src/Data/Text/IDN.hs b/src/Data/Text/IDN.hs
--- a/src/Data/Text/IDN.hs
+++ b/src/Data/Text/IDN.hs
@@ -50,14 +50,14 @@
 --
 -- Converts each label in the domain name to its ASCII-compatible form.
 -- Unicode labels are encoded using Punycode and prefixed with "xn--".
--- ASCII labels are validated and passed through unchanged.
+-- ASCII labels are normalized to lowercase (case-insensitive per DNS).
 --
 -- === Examples
 --
 -- >>> toASCII "münchen.de"
 -- Right "xn--mnchen-3ya.de"
 --
--- >>> toASCII "example.com"
+-- >>> toASCII "EXAMPLE.COM"
 -- Right "example.com"
 --
 -- === Errors
@@ -113,6 +113,7 @@
 -- | Process a single label to ASCII form.
 processLabelToASCII :: Label -> Either IDNError Label
 processLabelToASCII (ALabel t)
+  -- Labels are already normalized to lowercase by mkLabel
   | "xn--" `T.isPrefixOf` t = do
       -- It's a Punycode A-label, validate it by decoding and re-encoding
       -- This uses processLabelToUnicode which validates Punycode properly
@@ -120,12 +121,12 @@
       -- Then re-process back to ASCII to get a validated A-label
       processLabelToASCII ulabel
   | otherwise =
-      -- Regular ASCII label, just validate
+      -- Regular ASCII label (already normalized), just validate
       validateLabel t >> return (ALabel t)
-  
+
 processLabelToASCII (ULabel t)
   | isAsciiLabel t = do
-      -- Pure ASCII label, validate and return as-is
+      -- Pure ASCII label (already normalized), just validate
       validateLabel t
       return (ULabel t)
   
@@ -144,22 +145,19 @@
 
 -- | Process a single label to Unicode form.
 processLabelToUnicode :: Label -> Either IDNError Label
-processLabelToUnicode (ULabel t) = 
+processLabelToUnicode (ULabel t) =
   -- Already Unicode, validate and return
   validateLabel t >> return (ULabel t)
-  
+
 processLabelToUnicode (ALabel t)
-  | "xn--" `T.isPrefixOf` t = do
+  -- RFC 5891 Section 5.1: A-labels are case-insensitive for matching
+  | "xn--" `T.isPrefixOf` T.toLower t = do
       -- Punycode A-label, decode it
-      -- RFC 5891 Section 5.4: A-labels must be lowercase
+      -- RFC 5891 Section 5.1: Accept case-insensitive input, normalize to lowercase
+      -- Drop the prefix (which might be XN--, Xn--, xN--, or xn--)
       let encoded = T.drop 4 t
           encodedLower = T.toLower encoded
 
-      -- Check if original was already lowercase (uppercase in Punycode is invalid)
-      if encoded /= encodedLower
-        then Left (PunycodeDecodeError encoded "A-label must be lowercase")
-        else pure ()
-
       decoded <- case decodePunycode encodedLower of
         Left err -> Left (PunycodeDecodeError encoded (T.pack (show err)))
         Right d -> Right d
@@ -246,13 +244,15 @@
   case T.uncons label of
     Just ('-', _) -> Left (InvalidHyphenPosition StartsWithHyphen)
     _ -> pure ()
-  
+
   case T.unsnoc label of
     Just (_, '-') -> Left (InvalidHyphenPosition EndsWithHyphen)
     _ -> pure ()
-  
+
   -- Check for "--" in positions 3-4 (unless it's an A-label with xn--)
-  if T.length label >= 4 && not ("xn--" `T.isPrefixOf` label)
+  -- RFC 5891: A-labels are case-insensitive for matching
+  let labelLower = T.toLower label
+  if T.length label >= 4 && not ("xn--" `T.isPrefixOf` labelLower)
     then if T.index label 2 == '-' && T.index label 3 == '-'
       then Left (InvalidHyphenPosition HyphensAt3And4NotPunycode)
       else pure ()
diff --git a/src/Data/Text/IDN/Types.hs b/src/Data/Text/IDN/Types.hs
--- a/src/Data/Text/IDN/Types.hs
+++ b/src/Data/Text/IDN/Types.hs
@@ -185,8 +185,13 @@
 mkLabel text
   | T.null text = Left EmptyLabel
   | T.length text > 63 = Left (LabelTooLong (T.length text) 63)
-  | "xn--" `T.isPrefixOf` text = Right (ALabel text)
-  | otherwise = Right (ULabel text)
+  | otherwise =
+      -- RFC 5891/3490: Domain labels are case-insensitive, normalize to lowercase
+      let normalized = T.toLower text
+      -- RFC 5891 Section 5.1: A-labels start with "xn--"
+      in if "xn--" `T.isPrefixOf` normalized
+           then Right (ALabel normalized)
+           else Right (ULabel normalized)
 
 -- | Smart constructor for 'DomainName' with invariant enforcement.
 --
diff --git a/test/Data/Text/IDNSpec.hs b/test/Data/Text/IDNSpec.hs
--- a/test/Data/Text/IDNSpec.hs
+++ b/test/Data/Text/IDNSpec.hs
@@ -54,8 +54,9 @@
       toASCII "example-.com" `shouldSatisfy` isLeft
       toASCII "ab--cd.com" `shouldSatisfy` isLeft
     
-    it "accepts valid xn-- Punycode prefix" $ do
-      toASCII "xn--test.com" `shouldSatisfy` isRight
+    it "rejects xn-- prefix with pure ASCII (RFC 5891 violation)" $ do
+      -- RFC 5891: Punycode should only be used for labels with non-ASCII characters
+      toASCII "xn--test.com" `shouldSatisfy` isLeft
     
     it "handles multi-label domains" $ do
       toASCII "sub.example.com" `shouldBe` Right "sub.example.com"
