diff --git a/src/librustdoc/clean/mod.rs b/src/librustdoc/clean/mod.rs
index 3c9e0914b59d7..0f84b43866290 100644
--- a/src/librustdoc/clean/mod.rs
+++ b/src/librustdoc/clean/mod.rs
@@ -1844,8 +1844,8 @@ pub(crate) fn clean_ty<'tcx>(ty: &hir::Ty<'tcx>, cx: &mut DocContext<'tcx>) -> T
             DynTrait(bounds, lifetime)
         }
         TyKind::BareFn(barefn) => BareFunction(Box::new(clean_bare_fn_ty(barefn, cx))),
-        TyKind::UnsafeBinder(..) => {
-            unimplemented!("unsafe binders are not supported yet")
+        TyKind::UnsafeBinder(unsafe_binder_ty) => {
+            UnsafeBinder(Box::new(clean_unsafe_binder_ty(unsafe_binder_ty, cx)))
         }
         // Rustdoc handles `TyKind::Err`s by turning them into `Type::Infer`s.
         TyKind::Infer
@@ -2075,6 +2075,11 @@ pub(crate) fn clean_middle_ty<'tcx>(
                 abi: sig.abi(),
             }))
         }
+        ty::UnsafeBinder(inner) => {
+            let generic_params = clean_bound_vars(inner.bound_vars());
+            let ty = clean_middle_ty(inner.into(), cx, None, None);
+            UnsafeBinder(Box::new(UnsafeBinderTy { generic_params, ty }))
+        }
         ty::Adt(def, args) => {
             let did = def.did();
             let kind = match def.adt_kind() {
@@ -2253,7 +2258,6 @@ pub(crate) fn clean_middle_ty<'tcx>(
             }
         }
 
-        ty::UnsafeBinder(_) => todo!("FIXME(unsafe_binders)"),
         ty::Closure(..) => panic!("Closure"),
         ty::CoroutineClosure(..) => panic!("CoroutineClosure"),
         ty::Coroutine(..) => panic!("Coroutine"),
@@ -2564,6 +2568,21 @@ fn clean_bare_fn_ty<'tcx>(
     BareFunctionDecl { safety: bare_fn.safety, abi: bare_fn.abi, decl, generic_params }
 }
 
+fn clean_unsafe_binder_ty<'tcx>(
+    unsafe_binder_ty: &hir::UnsafeBinderTy<'tcx>,
+    cx: &mut DocContext<'tcx>,
+) -> UnsafeBinderTy {
+    // NOTE: generics must be cleaned before args
+    let generic_params = unsafe_binder_ty
+        .generic_params
+        .iter()
+        .filter(|p| !is_elided_lifetime(p))
+        .map(|x| clean_generic_param(cx, None, x))
+        .collect();
+    let ty = clean_ty(unsafe_binder_ty.inner_ty, cx);
+    UnsafeBinderTy { generic_params, ty }
+}
+
 pub(crate) fn reexport_chain(
     tcx: TyCtxt<'_>,
     import_def_id: LocalDefId,
diff --git a/src/librustdoc/clean/types.rs b/src/librustdoc/clean/types.rs
index 8c7ab925577df..3c4fad4bca97e 100644
--- a/src/librustdoc/clean/types.rs
+++ b/src/librustdoc/clean/types.rs
@@ -32,7 +32,7 @@ use {rustc_ast as ast, rustc_hir as hir};
 pub(crate) use self::ItemKind::*;
 pub(crate) use self::Type::{
     Array, BareFunction, BorrowedRef, DynTrait, Generic, ImplTrait, Infer, Primitive, QPath,
-    RawPointer, SelfTy, Slice, Tuple,
+    RawPointer, SelfTy, Slice, Tuple, UnsafeBinder,
 };
 use crate::clean::cfg::Cfg;
 use crate::clean::clean_middle_path;
@@ -1511,6 +1511,8 @@ pub(crate) enum Type {
 
     /// An `impl Trait`: `impl TraitA + TraitB + ...`
     ImplTrait(Vec<GenericBound>),
+
+    UnsafeBinder(Box<UnsafeBinderTy>),
 }
 
 impl Type {
@@ -1703,7 +1705,7 @@ impl Type {
             Type::Pat(..) => PrimitiveType::Pat,
             RawPointer(..) => PrimitiveType::RawPointer,
             QPath(box QPathData { ref self_type, .. }) => return self_type.def_id(cache),
-            Generic(_) | SelfTy | Infer | ImplTrait(_) => return None,
+            Generic(_) | SelfTy | Infer | ImplTrait(_) | UnsafeBinder(_) => return None,
         };
         Primitive(t).def_id(cache)
     }
@@ -2343,6 +2345,12 @@ pub(crate) struct BareFunctionDecl {
     pub(crate) abi: ExternAbi,
 }
 
+#[derive(Clone, PartialEq, Eq, Debug, Hash)]
+pub(crate) struct UnsafeBinderTy {
+    pub(crate) generic_params: Vec<GenericParamDef>,
+    pub(crate) ty: Type,
+}
+
 #[derive(Clone, Debug)]
 pub(crate) struct Static {
     pub(crate) type_: Box<Type>,
diff --git a/src/librustdoc/html/format.rs b/src/librustdoc/html/format.rs
index 136002b8e155b..621abd535010f 100644
--- a/src/librustdoc/html/format.rs
+++ b/src/librustdoc/html/format.rs
@@ -282,7 +282,8 @@ pub(crate) fn print_where_clause<'a, 'tcx: 'a>(
 
                     match pred {
                         clean::WherePredicate::BoundPredicate { ty, bounds, bound_params } => {
-                            print_higher_ranked_params_with_space(bound_params, cx).fmt(f)?;
+                            print_higher_ranked_params_with_space(bound_params, cx, "for")
+                                .fmt(f)?;
                             ty.print(cx).fmt(f)?;
                             f.write_str(":")?;
                             if !bounds.is_empty() {
@@ -386,7 +387,7 @@ impl clean::ConstantKind {
 impl clean::PolyTrait {
     fn print<'a, 'tcx: 'a>(&'a self, cx: &'a Context<'tcx>) -> impl Display + 'a + Captures<'tcx> {
         display_fn(move |f| {
-            print_higher_ranked_params_with_space(&self.generic_params, cx).fmt(f)?;
+            print_higher_ranked_params_with_space(&self.generic_params, cx, "for").fmt(f)?;
             self.trait_.print(cx).fmt(f)
         })
     }
@@ -968,10 +969,12 @@ fn tybounds<'a, 'tcx: 'a>(
 fn print_higher_ranked_params_with_space<'a, 'tcx: 'a>(
     params: &'a [clean::GenericParamDef],
     cx: &'a Context<'tcx>,
+    keyword: &'static str,
 ) -> impl Display + 'a + Captures<'tcx> {
     display_fn(move |f| {
         if !params.is_empty() {
-            f.write_str(if f.alternate() { "for<" } else { "for&lt;" })?;
+            f.write_str(keyword)?;
+            f.write_str(if f.alternate() { "<" } else { "&lt;" })?;
             comma_sep(params.iter().map(|lt| lt.print(cx)), true).fmt(f)?;
             f.write_str(if f.alternate() { "> " } else { "&gt; " })?;
         }
@@ -1027,7 +1030,7 @@ fn fmt_type(
             primitive_link(f, prim, format_args!("{}", prim.as_sym().as_str()), cx)
         }
         clean::BareFunction(ref decl) => {
-            print_higher_ranked_params_with_space(&decl.generic_params, cx).fmt(f)?;
+            print_higher_ranked_params_with_space(&decl.generic_params, cx, "for").fmt(f)?;
             decl.safety.print_with_space().fmt(f)?;
             print_abi_with_space(decl.abi).fmt(f)?;
             if f.alternate() {
@@ -1037,6 +1040,10 @@ fn fmt_type(
             }
             decl.decl.print(cx).fmt(f)
         }
+        clean::UnsafeBinder(ref binder) => {
+            print_higher_ranked_params_with_space(&binder.generic_params, cx, "unsafe").fmt(f)?;
+            binder.ty.print(cx).fmt(f)
+        }
         clean::Tuple(ref typs) => match &typs[..] {
             &[] => primitive_link(f, PrimitiveType::Unit, format_args!("()"), cx),
             [one] => {
@@ -1354,7 +1361,7 @@ impl clean::Impl {
             // Hardcoded anchor library/core/src/primitive_docs.rs
             // Link should match `# Trait implementations`
 
-            print_higher_ranked_params_with_space(&bare_fn.generic_params, cx).fmt(f)?;
+            print_higher_ranked_params_with_space(&bare_fn.generic_params, cx, "for").fmt(f)?;
             bare_fn.safety.print_with_space().fmt(f)?;
             print_abi_with_space(bare_fn.abi).fmt(f)?;
             let ellipsis = if bare_fn.decl.c_variadic { ", ..." } else { "" };
diff --git a/src/librustdoc/html/render/search_index.rs b/src/librustdoc/html/render/search_index.rs
index fe2e155c9ba7a..66c52bec4bad7 100644
--- a/src/librustdoc/html/render/search_index.rs
+++ b/src/librustdoc/html/render/search_index.rs
@@ -900,7 +900,8 @@ fn get_index_type_id(
         | clean::Generic(_)
         | clean::SelfTy
         | clean::ImplTrait(_)
-        | clean::Infer => None,
+        | clean::Infer
+        | clean::UnsafeBinder(_) => None,
     }
 }
 
diff --git a/src/librustdoc/json/conversions.rs b/src/librustdoc/json/conversions.rs
index 7f072aa7e2fc4..7fcdfe3fb22ea 100644
--- a/src/librustdoc/json/conversions.rs
+++ b/src/librustdoc/json/conversions.rs
@@ -573,7 +573,7 @@ impl FromClean<clean::Type> for Type {
     fn from_clean(ty: clean::Type, renderer: &JsonRenderer<'_>) -> Self {
         use clean::Type::{
             Array, BareFunction, BorrowedRef, Generic, ImplTrait, Infer, Primitive, QPath,
-            RawPointer, SelfTy, Slice, Tuple,
+            RawPointer, SelfTy, Slice, Tuple, UnsafeBinder,
         };
 
         match ty {
@@ -613,6 +613,8 @@ impl FromClean<clean::Type> for Type {
                 self_type: Box::new(self_type.into_json(renderer)),
                 trait_: trait_.map(|trait_| trait_.into_json(renderer)),
             },
+            // FIXME(unsafe_binder): Implement rustdoc-json.
+            UnsafeBinder(_) => todo!(),
         }
     }
 }
diff --git a/tests/rustdoc/auxiliary/unsafe-binder-dep.rs b/tests/rustdoc/auxiliary/unsafe-binder-dep.rs
new file mode 100644
index 0000000000000..65aa9032fed19
--- /dev/null
+++ b/tests/rustdoc/auxiliary/unsafe-binder-dep.rs
@@ -0,0 +1,4 @@
+#![feature(unsafe_binders)]
+#![allow(incomplete_features)]
+
+pub fn woof() -> unsafe<'a> &'a str { todo!() }
diff --git a/tests/rustdoc/unsafe-binder.rs b/tests/rustdoc/unsafe-binder.rs
new file mode 100644
index 0000000000000..621c3dadc72ee
--- /dev/null
+++ b/tests/rustdoc/unsafe-binder.rs
@@ -0,0 +1,15 @@
+//@ aux-build:unsafe-binder-dep.rs
+
+#![feature(unsafe_binders)]
+#![allow(incomplete_features)]
+
+extern crate unsafe_binder_dep;
+
+//@ has 'unsafe_binder/fn.woof.html' //pre "fn woof() -> unsafe<'a> &'a str"
+pub use unsafe_binder_dep::woof;
+
+//@ has 'unsafe_binder/fn.meow.html' //pre "fn meow() -> unsafe<'a> &'a str"
+pub fn meow() -> unsafe<'a> &'a str { todo!() }
+
+//@ has 'unsafe_binder/fn.meow_squared.html' //pre "fn meow_squared() -> unsafe<'b, 'a> &'a &'b str"
+pub fn meow_squared() -> unsafe<'b, 'a> &'a &'b str { todo!() }