diff --git a/maud/src/lib.rs b/maud/src/lib.rs
index b08d53a..d326843 100644
--- a/maud/src/lib.rs
+++ b/maud/src/lib.rs
@@ -5,6 +5,70 @@
 //!
 //! [book]: http://lfairy.gitbooks.io/maud/content/
 
+use std::fmt;
+use std::io;
+
+/// Wraps a `std::io::Write` in a `std::fmt::Write`.
+///
+/// Most I/O libraries work with binary data (`[u8]`), but Maud outputs
+/// Unicode strings (`str`). This adapter links them together by
+/// encoding the output as UTF-8.
+///
+/// # Example
+///
+/// ```rust,ignore
+/// use std::io;
+/// let writer = Utf8Writer::new(io::stdout());
+/// let _ = html!(writer, p { "Hello, " $name "!" });
+/// let result = writer.into_result();
+/// result.unwrap();
+/// ```
+pub struct Utf8Writer<W: io::Write> {
+    inner: W,
+    result: io::Result<()>,
+}
+
+impl<W: io::Write> Utf8Writer<W> {
+    /// Creates a `Utf8Writer` from a `std::io::Write`.
+    pub fn new(inner: W) -> Utf8Writer<W> {
+        Utf8Writer {
+            inner: inner,
+            result: Ok(()),
+        }
+    }
+
+    pub fn into_inner(self) -> (W, io::Result<()>) {
+        let Utf8Writer { inner, result } = self;
+        (inner, result)
+    }
+
+    pub fn into_result(self) -> io::Result<()> {
+        self.result
+    }
+}
+
+impl<W: io::Write> fmt::Write for Utf8Writer<W> {
+    fn write_str(&mut self, s: &str) -> fmt::Result {
+        match io::Write::write_all(&mut self.inner, s.as_bytes()) {
+            Ok(()) => Ok(()),
+            Err(e) => {
+                self.result = Err(e);
+                Err(fmt::Error)
+            }
+        }
+    }
+
+    fn write_fmt(&mut self, args: fmt::Arguments) -> fmt::Result {
+        match io::Write::write_fmt(&mut self.inner, args) {
+            Ok(()) => Ok(()),
+            Err(e) => {
+                self.result = Err(e);
+                Err(fmt::Error)
+            }
+        }
+    }
+}
+
 /// Escapes an HTML value.
 pub fn escape(s: &str) -> String {
     use std::fmt::Write;
diff --git a/maud_macros/tests/tests.rs b/maud_macros/tests/tests.rs
index 24011b7..a1f0eb1 100644
--- a/maud_macros/tests/tests.rs
+++ b/maud_macros/tests/tests.rs
@@ -248,3 +248,13 @@ mod control {
                 "</ul>"));
     }
 }
+
+#[test]
+fn utf8_writer() {
+    use maud::Utf8Writer;
+    let mut w = Utf8Writer::new(vec![]);
+    let _ = html!(w, p "hello");
+    let (buf, r) = w.into_inner();
+    r.unwrap();
+    assert_eq!(buf, b"<p>hello</p>");
+}