diff --git a/include/fmt/base.h b/include/fmt/base.h
index fe73e37795aa..79ec762c3764 100644
--- a/include/fmt/base.h
+++ b/include/fmt/base.h
@@ -1030,7 +1030,7 @@ enum {
   pointer_set = set(type::pointer_type)
 };
 
-struct view {};
+template <typename T> struct is_view : std::false_type {};
 
 template <typename Char, typename T> struct named_arg;
 template <typename T> struct is_named_arg : std::false_type {};
@@ -1039,7 +1039,7 @@ template <typename T> struct is_static_named_arg : std::false_type {};
 template <typename Char, typename T>
 struct is_named_arg<named_arg<Char, T>> : std::true_type {};
 
-template <typename Char, typename T> struct named_arg : view {
+template <typename Char, typename T> struct named_arg {
   const Char* name;
   const T& value;
 
@@ -1047,6 +1047,9 @@ template <typename Char, typename T> struct named_arg : view {
   static_assert(!is_named_arg<T>::value, "nested named arguments");
 };
 
+template <typename Char, typename T>
+struct is_view<named_arg<Char, T>> : std::true_type {};
+
 template <bool B = false> constexpr auto count() -> int { return B ? 1 : 0; }
 template <bool B1, bool B2, bool... Tail> constexpr auto count() -> int {
   return (B1 ? 1 : 0) + count<B2, Tail...>();
@@ -2715,7 +2718,7 @@ template <typename... T> struct fstring {
   template <size_t N>
   FMT_CONSTEVAL FMT_ALWAYS_INLINE fstring(const char (&s)[N]) : str(s, N - 1) {
     using namespace detail;
-    static_assert(count<(std::is_base_of<view, remove_reference_t<T>>::value &&
+    static_assert(count<(is_view<remove_cvref_t<T>>::value &&
                          std::is_reference<T>::value)...>() == 0,
                   "passing views as lvalues is disallowed");
     if (FMT_USE_CONSTEVAL) parse_format_string<char>(s, checker(s, arg_pack()));
diff --git a/include/fmt/color.h b/include/fmt/color.h
index 638f15b43f38..ab431eaa6e3f 100644
--- a/include/fmt/color.h
+++ b/include/fmt/color.h
@@ -468,12 +468,14 @@ template <typename Char> inline void reset_color(buffer<Char>& buffer) {
   buffer.append(reset_color.begin(), reset_color.end());
 }
 
-template <typename T> struct styled_arg : view {
+template <typename T> struct styled_arg {
   const T& value;
   text_style style;
   styled_arg(const T& v, text_style s) : value(v), style(s) {}
 };
 
+template <typename T> struct is_view<styled_arg<T>> : std::true_type {};
+
 template <typename Char>
 void vformat_to(buffer<Char>& buf, text_style ts, basic_string_view<Char> fmt,
                 basic_format_args<buffered_context<Char>> args) {
diff --git a/include/fmt/format.h b/include/fmt/format.h
index 287e71631e35..5d2d826eb8f8 100644
--- a/include/fmt/format.h
+++ b/include/fmt/format.h
@@ -3554,13 +3554,17 @@ FMT_CONSTEXPR void handle_dynamic_spec(
 #if FMT_USE_NONTYPE_TEMPLATE_ARGS
 template <typename T, typename Char, size_t N,
           fmt::detail::fixed_string<Char, N> Str>
-struct static_named_arg : view {
+struct static_named_arg {
   static constexpr auto name = Str.data;
 
   const T& value;
   static_named_arg(const T& v) : value(v) {}
 };
 
+template <typename T, typename Char, size_t N,
+          fmt::detail::fixed_string<Char, N> Str>
+struct is_view<static_named_arg<T, Char, N, Str>> : std::true_type {};
+
 template <typename T, typename Char, size_t N,
           fmt::detail::fixed_string<Char, N> Str>
 struct is_named_arg<static_named_arg<T, Char, N, Str>> : std::true_type {};
diff --git a/include/fmt/ranges.h b/include/fmt/ranges.h
index 77d645f8e114..5a0c7b17aa2f 100644
--- a/include/fmt/ranges.h
+++ b/include/fmt/ranges.h
@@ -620,7 +620,7 @@ struct formatter<
 };
 
 template <typename It, typename Sentinel, typename Char = char>
-struct join_view : detail::view {
+struct join_view {
   It begin;
   Sentinel end;
   basic_string_view<Char> sep;
@@ -629,6 +629,13 @@ struct join_view : detail::view {
       : begin(std::move(b)), end(e), sep(s) {}
 };
 
+namespace detail {
+
+template <typename It, typename Sentinel, typename Char>
+struct is_view<join_view<It, Sentinel, Char>> : std::true_type {};
+
+}  // namespace detail
+
 template <typename It, typename Sentinel, typename Char>
 struct formatter<join_view<It, Sentinel, Char>, Char> {
  private:
@@ -670,7 +677,7 @@ struct formatter<join_view<It, Sentinel, Char>, Char> {
   }
 };
 
-template <typename Char, typename Tuple> struct tuple_join_view : detail::view {
+template <typename Char, typename Tuple> struct tuple_join_view {
   const Tuple& tuple;
   basic_string_view<Char> sep;
 
@@ -678,6 +685,13 @@ template <typename Char, typename Tuple> struct tuple_join_view : detail::view {
       : tuple(t), sep{s} {}
 };
 
+namespace detail {
+
+template <typename Char, typename Tuple>
+struct is_view<tuple_join_view<Char, Tuple>> : std::true_type {};
+
+}  // namespace detail
+
 // Define FMT_TUPLE_JOIN_SPECIFIERS to enable experimental format specifiers
 // support in tuple_join. It is disabled by default because of issues with
 // the dynamic width and precision.
diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt
index e1ea260d9adc..64d658ec332b 100644
--- a/test/CMakeLists.txt
+++ b/test/CMakeLists.txt
@@ -53,6 +53,7 @@ add_fmt_test(assert-test)
 add_fmt_test(chrono-test)
 add_fmt_test(color-test)
 add_fmt_test(gtest-extra-test)
+add_fmt_test(incomplete-type-test)
 add_fmt_test(format-test mock-allocator.h)
 if (MSVC)
   target_compile_options(format-test PRIVATE /bigobj)
diff --git a/test/incomplete-type-test.cc b/test/incomplete-type-test.cc
new file mode 100644
index 000000000000..1607711351fc
--- /dev/null
+++ b/test/incomplete-type-test.cc
@@ -0,0 +1,41 @@
+// Formatting library for C++ - formatting library tests
+//
+// Copyright (c) 2012 - present, Victor Zverovich
+// All rights reserved.
+//
+// For the license information refer to format.h.
+
+#include "fmt/format.h"
+#include "fmt/color.h"
+#include "gtest/gtest.h"
+
+// Only defined after all the tests.
+struct incomplete_type;
+extern const incomplete_type& external_instance;
+
+FMT_BEGIN_NAMESPACE
+
+template <> struct formatter<incomplete_type> : formatter<int> {
+  auto format(const incomplete_type& x, context& ctx) const
+      -> decltype(ctx.out());
+};
+
+FMT_END_NAMESPACE
+
+TEST(incomplete_type_test, format) {
+  EXPECT_EQ(fmt::format("{}", external_instance), fmt::format("{}", 42));
+  EXPECT_EQ(fmt::format("{:4}", external_instance), fmt::format("{:4}", 42));
+  EXPECT_EQ(fmt::format("{:4}", fmt::styled(external_instance, fg(fmt::color::red))), fmt::format("{:4}", fmt::styled(42, fg(fmt::color::red))));
+}
+
+struct incomplete_type {
+  int i;
+};
+
+const incomplete_type& external_instance{42};
+
+auto fmt::formatter<incomplete_type>::format(const incomplete_type& x,
+                                             fmt::context& ctx) const
+    -> decltype(ctx.out()) {
+  return fmt::formatter<int>::format(x.i, ctx);
+}