diff --git a/maud/src/lib.rs b/maud/src/lib.rs
index a23c2b1..9c4a180 100644
--- a/maud/src/lib.rs
+++ b/maud/src/lib.rs
@@ -340,3 +340,54 @@ mod axum_support {
         }
     }
 }
+
+#[doc(hidden)]
+pub mod macro_private {
+    use crate::{display, Render};
+    use alloc::string::String;
+    use core::fmt::Display;
+
+    #[doc(hidden)]
+    #[macro_export]
+    macro_rules! render_to {
+        ($x:expr, $buffer:expr) => {{
+            use $crate::macro_private::*;
+            match ChooseRenderOrDisplay($x) {
+                x => (&&x).implements_render_or_display().render_to(x.0, $buffer),
+            }
+        }};
+    }
+
+    pub use render_to;
+
+    pub struct ChooseRenderOrDisplay<T>(pub T);
+
+    pub struct ViaRenderTag;
+    pub struct ViaDisplayTag;
+
+    pub trait ViaRender {
+        fn implements_render_or_display(&self) -> ViaRenderTag {
+            ViaRenderTag
+        }
+    }
+    pub trait ViaDisplay {
+        fn implements_render_or_display(&self) -> ViaDisplayTag {
+            ViaDisplayTag
+        }
+    }
+
+    impl<T: Render> ViaRender for &ChooseRenderOrDisplay<T> {}
+    impl<T: Display> ViaDisplay for ChooseRenderOrDisplay<T> {}
+
+    impl ViaRenderTag {
+        pub fn render_to<T: Render + ?Sized>(self, value: &T, buffer: &mut String) {
+            value.render_to(buffer);
+        }
+    }
+
+    impl ViaDisplayTag {
+        pub fn render_to<T: Display + ?Sized>(self, value: &T, buffer: &mut String) {
+            display(value).render_to(buffer);
+        }
+    }
+}
diff --git a/maud/tests/misc.rs b/maud/tests/misc.rs
index 741af42..b9aae52 100644
--- a/maud/tests/misc.rs
+++ b/maud/tests/misc.rs
@@ -83,3 +83,50 @@ fn issue_97() {
 
     assert_eq!(html! { (Pinkie) }.into_string(), "42");
 }
+
+#[test]
+fn only_display() {
+    use core::fmt::Display;
+
+    struct OnlyDisplay;
+    impl Display for OnlyDisplay {
+        fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+            write!(f, "<hello>")
+        }
+    }
+
+    assert_eq!(html! { (OnlyDisplay) }.into_string(), "&lt;hello&gt;");
+    assert_eq!(html! { (&OnlyDisplay) }.into_string(), "&lt;hello&gt;");
+    assert_eq!(html! { (&&OnlyDisplay) }.into_string(), "&lt;hello&gt;");
+    assert_eq!(html! { (&&&OnlyDisplay) }.into_string(), "&lt;hello&gt;");
+    assert_eq!(html! { (&&&&OnlyDisplay) }.into_string(), "&lt;hello&gt;");
+}
+
+#[test]
+fn prefer_render_over_display() {
+    use core::fmt::Display;
+    use maud::Render;
+
+    struct RenderAndDisplay;
+    impl Display for RenderAndDisplay {
+        fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+            write!(f, "<display>")
+        }
+    }
+    impl Render for RenderAndDisplay {
+        fn render_to(&self, buffer: &mut String) {
+            buffer.push_str("<render>");
+        }
+    }
+
+    assert_eq!(html! { (RenderAndDisplay) }.into_string(), "<render>");
+    assert_eq!(html! { (&RenderAndDisplay) }.into_string(), "<render>");
+    assert_eq!(html! { (&&RenderAndDisplay) }.into_string(), "<render>");
+    assert_eq!(html! { (&&&RenderAndDisplay) }.into_string(), "<render>");
+    assert_eq!(html! { (&&&&RenderAndDisplay) }.into_string(), "<render>");
+
+    assert_eq!(
+        html! { (maud::display(RenderAndDisplay)) }.into_string(),
+        "&lt;display&gt;"
+    );
+}
diff --git a/maud_macros/src/generate.rs b/maud_macros/src/generate.rs
index ad8a088..c9ba9fb 100644
--- a/maud_macros/src/generate.rs
+++ b/maud_macros/src/generate.rs
@@ -103,7 +103,7 @@ impl Generator {
 
     fn splice(&self, expr: TokenStream, build: &mut Builder) {
         let output_ident = self.output_ident.clone();
-        build.push_tokens(quote!(maud::Render::render_to(&#expr, &mut #output_ident);));
+        build.push_tokens(quote!(maud::macro_private::render_to!(&#expr, &mut #output_ident);));
     }
 
     fn element(&self, name: TokenStream, attrs: Vec<Attr>, body: ElementBody, build: &mut Builder) {