diff --git a/CHANGELOG.md b/CHANGELOG.md
index b8667e0..d715cc2 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,6 +6,8 @@
   [#303](https://github.com/lambda-fairy/maud/pull/303)
 - Add support for `Option<T>` attributes using the `attr=[value]` syntax.
   [#306](https://github.com/lambda-fairy/maud/pull/306)
+- Update to Rust 2021
+  [#309](https://github.com/lambda-fairy/maud/pull/309)
 
 ## [0.22.3] - 2021-09-27
 
diff --git a/docs/content/elements-attributes.md b/docs/content/elements-attributes.md
index 561e851..1c75a3e 100644
--- a/docs/content/elements-attributes.md
+++ b/docs/content/elements-attributes.md
@@ -155,7 +155,25 @@ and mix and match them with other attributes:
 ```rust
 # let _ = maud::
 html! {
-    input#cannon.big.scary.bright-red type="button" value="Launch Party Cannon";
+    input #cannon .big .scary .bright-red type="button" value="Launch Party Cannon";
+}
+# ;
+```
+
+In Rust 2021,
+the `#` symbol must be preceded by a space,
+to avoid conflicts with [reserved syntax]:
+
+[reserved syntax]: https://doc.rust-lang.org/edition-guide/rust-2021/reserving-syntax.html
+
+```rust,edition2018
+# let _ = maud::
+html! {
+    // Works on all Rust editions
+    input #pinkie;
+
+    // Works on Rust 2018 and older only
+    input#pinkie;
 }
 # ;
 ```
@@ -167,7 +185,7 @@ which otherwise wouldn't parse:
 ```rust
 # let _ = maud::
 html! {
-    div."col-sm-2" { "Bootstrap column!" }
+    div ."col-sm-2" { "Bootstrap column!" }
 }
 # ;
 ```
diff --git a/docs/content/splices-toggles.md b/docs/content/splices-toggles.md
index 723e548..fda59b8 100644
--- a/docs/content/splices-toggles.md
+++ b/docs/content/splices-toggles.md
@@ -85,8 +85,8 @@ let name = "rarity";
 let severity = "critical";
 # let _ = maud::
 html! {
-    aside#(name) {
-        p.{ "color-" (severity) } { "This is the worst! Possible! Thing!" }
+    aside #(name) {
+        p .{ "color-" (severity) } { "This is the worst! Possible! Thing!" }
     }
 }
 # ;
@@ -146,7 +146,7 @@ And classes:
 let cuteness = 95;
 # let _ = maud::
 html! {
-    p.cute[cuteness > 50] { "Squee!" }
+    p .cute[cuteness > 50] { "Squee!" }
 }
 # ;
 ```
diff --git a/maud/Cargo.toml b/maud/Cargo.toml
index 955ddb3..7e71039 100644
--- a/maud/Cargo.toml
+++ b/maud/Cargo.toml
@@ -9,7 +9,7 @@ homepage = "https://maud.lambda.xyz/"
 repository = "https://github.com/lambda-fairy/maud"
 description = "Compile-time HTML templates."
 categories = ["template-engine"]
-edition = "2018"
+edition = "2021"
 
 [features]
 default = []
diff --git a/maud/tests/basic_syntax.rs b/maud/tests/basic_syntax.rs
index e72eccf..471894d 100644
--- a/maud/tests/basic_syntax.rs
+++ b/maud/tests/basic_syntax.rs
@@ -189,15 +189,6 @@ fn hyphens_in_attribute_names() {
 
 #[test]
 fn class_shorthand() {
-    let result = html! { p { "Hi, " span.name { "Lyra" } "!" } };
-    assert_eq!(
-        result.into_string(),
-        r#"<p>Hi, <span class="name">Lyra</span>!</p>"#
-    );
-}
-
-#[test]
-fn class_shorthand_with_space() {
     let result = html! { p { "Hi, " span .name { "Lyra" } "!" } };
     assert_eq!(
         result.into_string(),
@@ -205,9 +196,18 @@ fn class_shorthand_with_space() {
     );
 }
 
+#[test]
+fn class_shorthand_without_space() {
+    let result = html! { p { "Hi, " span.name { "Lyra" } "!" } };
+    assert_eq!(
+        result.into_string(),
+        r#"<p>Hi, <span class="name">Lyra</span>!</p>"#
+    );
+}
+
 #[test]
 fn classes_shorthand() {
-    let result = html! { p { "Hi, " span.name.here { "Lyra" } "!" } };
+    let result = html! { p { "Hi, " span .name .here { "Lyra" } "!" } };
     assert_eq!(
         result.into_string(),
         r#"<p>Hi, <span class="name here">Lyra</span>!</p>"#
@@ -216,7 +216,7 @@ fn classes_shorthand() {
 
 #[test]
 fn hyphens_in_class_names() {
-    let result = html! { p.rocks-these.are--my--rocks { "yes" } };
+    let result = html! { p .rocks-these .are--my--rocks { "yes" } };
     assert_eq!(
         result.into_string(),
         r#"<p class="rocks-these are--my--rocks">yes</p>"#
@@ -225,7 +225,7 @@ fn hyphens_in_class_names() {
 
 #[test]
 fn class_string() {
-    let result = html! { h1."pinkie-123" { "Pinkie Pie" } };
+    let result = html! { h1 ."pinkie-123" { "Pinkie Pie" } };
     assert_eq!(
         result.into_string(),
         r#"<h1 class="pinkie-123">Pinkie Pie</h1>"#
@@ -235,7 +235,7 @@ fn class_string() {
 #[test]
 fn toggle_classes() {
     fn test(is_cupcake: bool, is_muffin: bool) -> Markup {
-        html!(p.cupcake[is_cupcake].muffin[is_muffin] { "Testing!" })
+        html!(p .cupcake[is_cupcake] .muffin[is_muffin] { "Testing!" })
     }
     assert_eq!(
         test(true, true).into_string(),
@@ -260,7 +260,7 @@ fn toggle_classes_braces() {
     struct Maud {
         rocks: bool,
     }
-    let result = html! { p.rocks[Maud { rocks: true }.rocks] { "Awesome!" } };
+    let result = html! { p .rocks[Maud { rocks: true }.rocks] { "Awesome!" } };
     assert_eq!(result.into_string(), r#"<p class="rocks">Awesome!</p>"#);
 }
 
@@ -268,14 +268,14 @@ fn toggle_classes_braces() {
 fn toggle_classes_string() {
     let is_cupcake = true;
     let is_muffin = false;
-    let result = html! { p."cupcake"[is_cupcake]."is_muffin"[is_muffin] { "Testing!" } };
+    let result = html! { p ."cupcake"[is_cupcake] ."is_muffin"[is_muffin] { "Testing!" } };
     assert_eq!(result.into_string(), r#"<p class="cupcake">Testing!</p>"#);
 }
 
 #[test]
 fn mixed_classes() {
     fn test(is_muffin: bool) -> Markup {
-        html!(p.cupcake.muffin[is_muffin].lamington { "Testing!" })
+        html!(p .cupcake .muffin[is_muffin] .lamington { "Testing!" })
     }
     assert_eq!(
         test(true).into_string(),
@@ -289,7 +289,7 @@ fn mixed_classes() {
 
 #[test]
 fn id_shorthand() {
-    let result = html! { p { "Hi, " span#thing { "Lyra" } "!" } };
+    let result = html! { p { "Hi, " span #thing { "Lyra" } "!" } };
     assert_eq!(
         result.into_string(),
         r#"<p>Hi, <span id="thing">Lyra</span>!</p>"#
@@ -298,7 +298,7 @@ fn id_shorthand() {
 
 #[test]
 fn id_string() {
-    let result = html! { h1#"pinkie-123" { "Pinkie Pie" } };
+    let result = html! { h1 #"pinkie-123" { "Pinkie Pie" } };
     assert_eq!(
         result.into_string(),
         r#"<h1 id="pinkie-123">Pinkie Pie</h1>"#
@@ -307,7 +307,7 @@ fn id_string() {
 
 #[test]
 fn classes_attrs_ids_mixed_up() {
-    let result = html! { p { "Hi, " span.name.here lang="en" #thing { "Lyra" } "!" } };
+    let result = html! { p { "Hi, " span .name .here lang="en" #thing { "Lyra" } "!" } };
     assert_eq!(
         result.into_string(),
         r#"<p>Hi, <span class="name here" id="thing" lang="en">Lyra</span>!</p>"#
diff --git a/maud/tests/splices.rs b/maud/tests/splices.rs
index 6a58661..5c32bb8 100644
--- a/maud/tests/splices.rs
+++ b/maud/tests/splices.rs
@@ -40,21 +40,21 @@ fn attributes() {
 #[test]
 fn class_shorthand() {
     let pinkie_class = "pinkie";
-    let result = html! { p.(pinkie_class) { "Fun!" } };
+    let result = html! { p .(pinkie_class) { "Fun!" } };
     assert_eq!(result.into_string(), r#"<p class="pinkie">Fun!</p>"#);
 }
 
 #[test]
 fn class_shorthand_block() {
     let class_prefix = "pinkie-";
-    let result = html! { p.{ (class_prefix) "123" } { "Fun!" } };
+    let result = html! { p .{ (class_prefix) "123" } { "Fun!" } };
     assert_eq!(result.into_string(), r#"<p class="pinkie-123">Fun!</p>"#);
 }
 
 #[test]
 fn id_shorthand() {
     let pinkie_id = "pinkie";
-    let result = html! { p#(pinkie_id) { "Fun!" } };
+    let result = html! { p #(pinkie_id) { "Fun!" } };
     assert_eq!(result.into_string(), r#"<p id="pinkie">Fun!</p>"#);
 }
 
diff --git a/maud_htmlescape/Cargo.toml b/maud_htmlescape/Cargo.toml
index e4e2ff9..82514fb 100644
--- a/maud_htmlescape/Cargo.toml
+++ b/maud_htmlescape/Cargo.toml
@@ -8,7 +8,7 @@ documentation = "https://docs.rs/maud_htmlescape/"
 homepage = "https://maud.lambda.xyz/"
 repository = "https://github.com/lambda-fairy/maud"
 description = "Internal support code used by Maud."
-edition = "2018"
+edition = "2021"
 
 [lib]
 path = "lib.rs"
diff --git a/maud_macros/Cargo.toml b/maud_macros/Cargo.toml
index 834a34e..7e18d51 100644
--- a/maud_macros/Cargo.toml
+++ b/maud_macros/Cargo.toml
@@ -8,7 +8,7 @@ documentation = "https://docs.rs/maud_macros/"
 homepage = "https://maud.lambda.xyz/"
 repository = "https://github.com/lambda-fairy/maud"
 description = "Compile-time HTML templates."
-edition = "2018"
+edition = "2021"
 
 [dependencies]
 syn = "1.0.8"