diff --git a/libcxx/docs/FeatureTestMacroTable.rst b/libcxx/docs/FeatureTestMacroTable.rst
--- a/libcxx/docs/FeatureTestMacroTable.rst
+++ b/libcxx/docs/FeatureTestMacroTable.rst
@@ -320,6 +320,8 @@
     ------------------------------------------------- -----------------
     ``__cpp_lib_expected``                            ``202202L``
     ------------------------------------------------- -----------------
+    ``__cpp_lib_formatters``                          *unimplemented*
+    ------------------------------------------------- -----------------
     ``__cpp_lib_forward_like``                        ``202207L``
     ------------------------------------------------- -----------------
     ``__cpp_lib_invoke_r``                            ``202106L``
diff --git a/libcxx/docs/ReleaseNotes.rst b/libcxx/docs/ReleaseNotes.rst
--- a/libcxx/docs/ReleaseNotes.rst
+++ b/libcxx/docs/ReleaseNotes.rst
@@ -39,6 +39,7 @@
 ------------------
 
 - P1328R1 - ``constexpr type_info::operator==()``
+- P1413R3 - Formatting ``thread::id`` (the ``stacktrace`` is not done yet)
 
 Improvements and New Features
 -----------------------------
diff --git a/libcxx/docs/Status/Cxx2b.rst b/libcxx/docs/Status/Cxx2b.rst
--- a/libcxx/docs/Status/Cxx2b.rst
+++ b/libcxx/docs/Status/Cxx2b.rst
@@ -42,6 +42,9 @@
    .. [#note-P0533R9] P0533R9: ``isfinite``, ``isinf``, ``isnan`` and ``isnormal`` are implemented.
    .. [#note-P1413R3] P1413R3: ``std::aligned_storage_t`` and ``std::aligned_union_t`` are marked deprecated, but
       clang doesn't issue a diagnostic for deprecated using template declarations.
+   .. [#note-P2693R1] P1413R3: The formatter for ``std::thread::id`` is implemented.
+      The formatter for ``stacktrace`` is not implemented, since ``stacktrace`` is
+      not implemented yet.
 
 .. _issues-status-cxx2b:
 
diff --git a/libcxx/docs/Status/Cxx2bPapers.csv b/libcxx/docs/Status/Cxx2bPapers.csv
--- a/libcxx/docs/Status/Cxx2bPapers.csv
+++ b/libcxx/docs/Status/Cxx2bPapers.csv
@@ -113,7 +113,7 @@
 "`P2713R1 <https://wg21.link/P2713R1>`__","LWG", "Escaping improvements in ``std::format``","February 2023","","","|format|"
 "`P2675R1 <https://wg21.link/P2675R1>`__","LWG", "``format``'s width estimation is too approximate and not forward compatible","February 2023","","","|format|"
 "`P2572R1 <https://wg21.link/P2572R1>`__","LWG", "``std::format`` fill character allowances","February 2023","","","|format|"
-"`P2693R1 <https://wg21.link/P2693R1>`__","LWG", "Formatting ``thread::id`` and ``stacktrace``","February 2023","","","|format|"
+"`P2693R1 <https://wg21.link/P2693R1>`__","LWG", "Formatting ``thread::id`` and ``stacktrace``","February 2023","|Partial| [#note-P2693R1]_","","|format|"
 "`P2679R2 <https://wg21.link/P2679R2>`__","LWG", "Fixing ``std::start_lifetime_as`` for arrays","February 2023","","",""
 "`P2674R1 <https://wg21.link/P2674R1>`__","LWG", "A trait for implicit lifetime types","February 2023","","",""
 "`P2655R3 <https://wg21.link/P2655R3>`__","LWG", "``common_reference_t`` of ``reference_wrapper`` Should Be a Reference Type","February 2023","","",""
diff --git a/libcxx/docs/Status/FormatPaper.csv b/libcxx/docs/Status/FormatPaper.csv
--- a/libcxx/docs/Status/FormatPaper.csv
+++ b/libcxx/docs/Status/FormatPaper.csv
@@ -40,5 +40,5 @@
 `[format.range.fmtstr] <https://wg21.link/format.range.fmtstr>`_,"Formatting for ranges: strings",,Mark de Wever,|In Progress|,
 `[format.range.fmtstr] <https://wg21.link/format.range.fmtstr>`_,"Formatting for ranges: strings",,Mark de Wever,|In Progress|,
 "`P2693R1 <https://wg21.link/P2693R1>`__","Formatting ``thread::id`` and ``stacktrace``"
-`[thread.thread.id] <https://wg21.link/thread.thread.id>`_,"Formatting ``thread::id``",,Mark de Wever,|In Progress|,
+`[thread.thread.id] <https://wg21.link/thread.thread.id>`_,"Formatting ``thread::id``",,Mark de Wever,|Complete|,Clang 17
 `[stacktrace.format] <https://wg21.link/stacktrace.format>`_,"Formatting ``stacktrace``",A ``<stacktrace>`` implementation,Mark de Wever,,
diff --git a/libcxx/include/__format/parser_std_format_spec.h b/libcxx/include/__format/parser_std_format_spec.h
--- a/libcxx/include/__format/parser_std_format_spec.h
+++ b/libcxx/include/__format/parser_std_format_spec.h
@@ -142,6 +142,7 @@
 #  if _LIBCPP_STD_VER >= 23
 inline constexpr __fields __fields_tuple{.__use_range_fill_ = true};
 inline constexpr __fields __fields_range{.__use_range_fill_ = true};
+inline constexpr __fields __fields_fill_align_width{};
 #  endif
 
 enum class _LIBCPP_ENUM_VIS __alignment : uint8_t {
diff --git a/libcxx/include/__threading_support b/libcxx/include/__threading_support
--- a/libcxx/include/__threading_support
+++ b/libcxx/include/__threading_support
@@ -643,6 +643,8 @@
     _LIBCPP_INLINE_VISIBILITY
     __thread_id(__libcpp_thread_id __id) : __id_(__id) {}
 
+    _LIBCPP_HIDE_FROM_ABI friend __libcpp_thread_id __get_underlying_id(const __thread_id __id) { return __id.__id_; }
+
     friend __thread_id this_thread::get_id() _NOEXCEPT;
     friend class _LIBCPP_TYPE_VIS thread;
     friend struct _LIBCPP_TEMPLATE_VIS hash<__thread_id>;
diff --git a/libcxx/include/thread b/libcxx/include/thread
--- a/libcxx/include/thread
+++ b/libcxx/include/thread
@@ -64,6 +64,9 @@
 basic_ostream<charT, traits>&
 operator<<(basic_ostream<charT, traits>& out, thread::id id);
 
+template<class charT>
+struct formatter<thread::id, charT>;
+
 namespace this_thread
 {
 
@@ -84,7 +87,14 @@
 */
 
 #include <__assert> // all public C++ headers provide the assertion handler
+#include <__availability>
+#include <__concepts/arithmetic.h>
 #include <__config>
+#include <__format/concepts.h>
+#include <__format/format_parse_context.h>
+#include <__format/formatter.h>
+#include <__format/formatter_integral.h>
+#include <__format/parser_std_format_spec.h>
 #include <__functional/hash.h>
 #include <__memory/unique_ptr.h>
 #include <__mutex_base>
@@ -219,6 +229,48 @@
 operator<<(basic_ostream<_CharT, _Traits>& __os, __thread_id __id)
 {return __os << __id.__id_;}
 
+#if _LIBCPP_STD_VER > 20
+template <__fmt_char_type _CharT>
+struct _LIBCPP_TEMPLATE_VIS _LIBCPP_AVAILABILITY_FORMAT formatter<__thread_id, _CharT> {
+  public:
+    _LIBCPP_HIDE_FROM_ABI constexpr auto parse(basic_format_parse_context<_CharT>& __parse_ctx)
+        -> decltype(__parse_ctx.begin()) {
+        return __parser_.__parse(__parse_ctx, __format_spec::__fields_fill_align_width);
+    }
+
+    _LIBCPP_HIDE_FROM_ABI auto format(__thread_id __id, auto& __ctx) const -> decltype(__ctx.out()) {
+        // In __threading_support __libcpp_thread_id is either a
+        // unsigned long long or a pthread_t.
+        //
+        // The type of pthread_t is left unspecified in POSIX so it can be any
+        // type. The most logical types are an integral or pointer.
+        // On Linux systems pthread_t is an unsigned long long.
+        // On Apple systems pthread_t is a pointer type.
+        //
+        // Note the output should match what the stream operator does. Since
+        // the ostream operator has been shipped years before this formatter
+        // was added to the Standard, this formatter does what the stream
+        // operator does. This may require platform specific changes.
+
+        using _Tp = decltype(__get_underlying_id(__id));
+
+        if constexpr (integral<_Tp>)
+          return __formatter::__format_integer(
+              __get_underlying_id(__id), __ctx, __parser_.__get_parsed_std_specifications(__ctx));
+        else if constexpr (is_pointer_v<_Tp>) {
+          __format_spec::__parsed_specifications<_CharT> __specs = __parser_.__get_parsed_std_specifications(__ctx);
+          __specs.__std_.__alternate_form_                       = true;
+          __specs.__std_.__type_                                 = __format_spec::__type::__hexadecimal_lower_case;
+          return __formatter::__format_integer(reinterpret_cast<uintptr_t>(__get_underlying_id(__id)), __ctx, __specs);
+        } else
+          // TODO FMT use static_assert(false, ...);
+          static_assert(sizeof(_CharT) == 0, "unsupported thread::id type, please file a bug report");
+    }
+
+    __format_spec::__parser<_CharT> __parser_{.__alignment_ = __format_spec::__alignment::__right};
+};
+#endif // _LIBCPP_STD_VER
+
 class _LIBCPP_TYPE_VIS thread
 {
     __libcpp_thread_t __t_;
diff --git a/libcxx/include/version b/libcxx/include/version
--- a/libcxx/include/version
+++ b/libcxx/include/version
@@ -85,6 +85,7 @@
 __cpp_lib_expected                                      202202L <expected>
 __cpp_lib_filesystem                                    201703L <filesystem>
 __cpp_lib_format                                        202106L <format>
+__cpp_lib_formatters                                    202302L <stacktrace> <thread>
 __cpp_lib_forward_like                                  202207L <utility>
 __cpp_lib_gcd_lcm                                       201606L <numeric>
 __cpp_lib_generic_associative_lookup                    201304L <map> <set>
@@ -394,6 +395,9 @@
 # define __cpp_lib_constexpr_memory                     202202L
 # define __cpp_lib_constexpr_typeinfo                   202106L
 # define __cpp_lib_expected                             202202L
+# if !defined(_LIBCPP_AVAILABILITY_DISABLE_FTM___cpp_lib_format) && !defined(_LIBCPP_HAS_NO_INCOMPLETE_FORMAT)
+// #   define __cpp_lib_formatters                         202302L
+# endif
 # define __cpp_lib_forward_like                         202207L
 # define __cpp_lib_invoke_r                             202106L
 # define __cpp_lib_is_scoped_enum                       202011L
diff --git a/libcxx/test/libcxx/transitive_includes/cxx03.csv b/libcxx/test/libcxx/transitive_includes/cxx03.csv
--- a/libcxx/test/libcxx/transitive_includes/cxx03.csv
+++ b/libcxx/test/libcxx/transitive_includes/cxx03.csv
@@ -812,20 +812,24 @@
 system_error string
 system_error type_traits
 system_error version
+thread array
+thread charconv
 thread chrono
 thread compare
 thread cstddef
 thread cstdint
-thread cstring
-thread ctime
+thread cstdlib
 thread functional
 thread iosfwd
 thread limits
-thread new
-thread ratio
+thread locale
+thread stdexcept
+thread string
+thread string_view
 thread system_error
 thread tuple
 thread type_traits
+thread vector
 thread version
 tuple compare
 tuple cstddef
diff --git a/libcxx/test/libcxx/transitive_includes/cxx11.csv b/libcxx/test/libcxx/transitive_includes/cxx11.csv
--- a/libcxx/test/libcxx/transitive_includes/cxx11.csv
+++ b/libcxx/test/libcxx/transitive_includes/cxx11.csv
@@ -813,20 +813,24 @@
 system_error string
 system_error type_traits
 system_error version
+thread array
+thread charconv
 thread chrono
 thread compare
 thread cstddef
 thread cstdint
-thread cstring
-thread ctime
+thread cstdlib
 thread functional
 thread iosfwd
 thread limits
-thread new
-thread ratio
+thread locale
+thread stdexcept
+thread string
+thread string_view
 thread system_error
 thread tuple
 thread type_traits
+thread vector
 thread version
 tuple compare
 tuple cstddef
diff --git a/libcxx/test/libcxx/transitive_includes/cxx14.csv b/libcxx/test/libcxx/transitive_includes/cxx14.csv
--- a/libcxx/test/libcxx/transitive_includes/cxx14.csv
+++ b/libcxx/test/libcxx/transitive_includes/cxx14.csv
@@ -822,20 +822,24 @@
 system_error string
 system_error type_traits
 system_error version
+thread array
+thread charconv
 thread chrono
 thread compare
 thread cstddef
 thread cstdint
-thread cstring
-thread ctime
+thread cstdlib
 thread functional
 thread iosfwd
 thread limits
-thread new
-thread ratio
+thread locale
+thread stdexcept
+thread string
+thread string_view
 thread system_error
 thread tuple
 thread type_traits
+thread vector
 thread version
 tuple compare
 tuple cstddef
diff --git a/libcxx/test/libcxx/transitive_includes/cxx17.csv b/libcxx/test/libcxx/transitive_includes/cxx17.csv
--- a/libcxx/test/libcxx/transitive_includes/cxx17.csv
+++ b/libcxx/test/libcxx/transitive_includes/cxx17.csv
@@ -822,20 +822,24 @@
 system_error string
 system_error type_traits
 system_error version
+thread array
+thread charconv
 thread chrono
 thread compare
 thread cstddef
 thread cstdint
-thread cstring
-thread ctime
+thread cstdlib
 thread functional
 thread iosfwd
 thread limits
-thread new
-thread ratio
+thread locale
+thread stdexcept
+thread string
+thread string_view
 thread system_error
 thread tuple
 thread type_traits
+thread vector
 thread version
 tuple compare
 tuple cstddef
diff --git a/libcxx/test/libcxx/transitive_includes/cxx20.csv b/libcxx/test/libcxx/transitive_includes/cxx20.csv
--- a/libcxx/test/libcxx/transitive_includes/cxx20.csv
+++ b/libcxx/test/libcxx/transitive_includes/cxx20.csv
@@ -829,19 +829,23 @@
 system_error string
 system_error type_traits
 system_error version
+thread array
+thread charconv
 thread compare
 thread cstddef
 thread cstdint
-thread cstring
-thread ctime
+thread cstdlib
 thread functional
 thread iosfwd
 thread limits
-thread new
-thread ratio
+thread locale
+thread stdexcept
+thread string
+thread string_view
 thread system_error
 thread tuple
 thread type_traits
+thread vector
 thread version
 tuple compare
 tuple cstddef
diff --git a/libcxx/test/libcxx/transitive_includes/cxx2b.csv b/libcxx/test/libcxx/transitive_includes/cxx2b.csv
--- a/libcxx/test/libcxx/transitive_includes/cxx2b.csv
+++ b/libcxx/test/libcxx/transitive_includes/cxx2b.csv
@@ -624,18 +624,23 @@
 system_error string
 system_error type_traits
 system_error version
+thread array
+thread charconv
 thread compare
 thread cstddef
 thread cstdint
-thread cstring
-thread ctime
+thread cstdlib
+thread initializer_list
 thread iosfwd
 thread limits
-thread new
-thread ratio
+thread locale
+thread stdexcept
+thread string
+thread string_view
 thread system_error
 thread tuple
 thread type_traits
+thread vector
 thread version
 tuple compare
 tuple cstddef
diff --git a/libcxx/test/std/language.support/support.limits/support.limits.general/thread.version.compile.pass.cpp b/libcxx/test/std/language.support/support.limits/support.limits.general/thread.version.compile.pass.cpp
--- a/libcxx/test/std/language.support/support.limits/support.limits.general/thread.version.compile.pass.cpp
+++ b/libcxx/test/std/language.support/support.limits/support.limits.general/thread.version.compile.pass.cpp
@@ -17,8 +17,9 @@
 
 // Test the feature test macros defined by <thread>
 
-/*  Constant             Value
-    __cpp_lib_jthread    201911L [C++20]
+/*  Constant                Value
+    __cpp_lib_formatters    202302L [C++2b]
+    __cpp_lib_jthread       201911L [C++20]
 */
 
 #include <thread>
@@ -26,24 +27,40 @@
 
 #if TEST_STD_VER < 14
 
+# ifdef __cpp_lib_formatters
+#   error "__cpp_lib_formatters should not be defined before c++2b"
+# endif
+
 # ifdef __cpp_lib_jthread
 #   error "__cpp_lib_jthread should not be defined before c++20"
 # endif
 
 #elif TEST_STD_VER == 14
 
+# ifdef __cpp_lib_formatters
+#   error "__cpp_lib_formatters should not be defined before c++2b"
+# endif
+
 # ifdef __cpp_lib_jthread
 #   error "__cpp_lib_jthread should not be defined before c++20"
 # endif
 
 #elif TEST_STD_VER == 17
 
+# ifdef __cpp_lib_formatters
+#   error "__cpp_lib_formatters should not be defined before c++2b"
+# endif
+
 # ifdef __cpp_lib_jthread
 #   error "__cpp_lib_jthread should not be defined before c++20"
 # endif
 
 #elif TEST_STD_VER == 20
 
+# ifdef __cpp_lib_formatters
+#   error "__cpp_lib_formatters should not be defined before c++2b"
+# endif
+
 # if !defined(_LIBCPP_VERSION)
 #   ifndef __cpp_lib_jthread
 #     error "__cpp_lib_jthread should be defined in c++20"
@@ -59,6 +76,19 @@
 
 #elif TEST_STD_VER > 20
 
+# if !defined(_LIBCPP_VERSION)
+#   ifndef __cpp_lib_formatters
+#     error "__cpp_lib_formatters should be defined in c++2b"
+#   endif
+#   if __cpp_lib_formatters != 202302L
+#     error "__cpp_lib_formatters should have the value 202302L in c++2b"
+#   endif
+# else // _LIBCPP_VERSION
+#   ifdef __cpp_lib_formatters
+#     error "__cpp_lib_formatters should not be defined because it is unimplemented in libc++!"
+#   endif
+# endif
+
 # if !defined(_LIBCPP_VERSION)
 #   ifndef __cpp_lib_jthread
 #     error "__cpp_lib_jthread should be defined in c++2b"
diff --git a/libcxx/test/std/language.support/support.limits/support.limits.general/version.version.compile.pass.cpp b/libcxx/test/std/language.support/support.limits/support.limits.general/version.version.compile.pass.cpp
--- a/libcxx/test/std/language.support/support.limits/support.limits.general/version.version.compile.pass.cpp
+++ b/libcxx/test/std/language.support/support.limits/support.limits.general/version.version.compile.pass.cpp
@@ -79,6 +79,7 @@
     __cpp_lib_expected                             202202L [C++2b]
     __cpp_lib_filesystem                           201703L [C++17]
     __cpp_lib_format                               202106L [C++20]
+    __cpp_lib_formatters                           202302L [C++2b]
     __cpp_lib_forward_like                         202207L [C++2b]
     __cpp_lib_gcd_lcm                              201606L [C++17]
     __cpp_lib_generic_associative_lookup           201304L [C++14]
@@ -429,6 +430,10 @@
 #   error "__cpp_lib_format should not be defined before c++20"
 # endif
 
+# ifdef __cpp_lib_formatters
+#   error "__cpp_lib_formatters should not be defined before c++2b"
+# endif
+
 # ifdef __cpp_lib_forward_like
 #   error "__cpp_lib_forward_like should not be defined before c++2b"
 # endif
@@ -1076,6 +1081,10 @@
 #   error "__cpp_lib_format should not be defined before c++20"
 # endif
 
+# ifdef __cpp_lib_formatters
+#   error "__cpp_lib_formatters should not be defined before c++2b"
+# endif
+
 # ifdef __cpp_lib_forward_like
 #   error "__cpp_lib_forward_like should not be defined before c++2b"
 # endif
@@ -1837,6 +1846,10 @@
 #   error "__cpp_lib_format should not be defined before c++20"
 # endif
 
+# ifdef __cpp_lib_formatters
+#   error "__cpp_lib_formatters should not be defined before c++2b"
+# endif
+
 # ifdef __cpp_lib_forward_like
 #   error "__cpp_lib_forward_like should not be defined before c++2b"
 # endif
@@ -2877,6 +2890,10 @@
 #   endif
 # endif
 
+# ifdef __cpp_lib_formatters
+#   error "__cpp_lib_formatters should not be defined before c++2b"
+# endif
+
 # ifdef __cpp_lib_forward_like
 #   error "__cpp_lib_forward_like should not be defined before c++2b"
 # endif
@@ -4103,6 +4120,19 @@
 #   endif
 # endif
 
+# if !defined(_LIBCPP_VERSION)
+#   ifndef __cpp_lib_formatters
+#     error "__cpp_lib_formatters should be defined in c++2b"
+#   endif
+#   if __cpp_lib_formatters != 202302L
+#     error "__cpp_lib_formatters should have the value 202302L in c++2b"
+#   endif
+# else // _LIBCPP_VERSION
+#   ifdef __cpp_lib_formatters
+#     error "__cpp_lib_formatters should not be defined because it is unimplemented in libc++!"
+#   endif
+# endif
+
 # ifndef __cpp_lib_forward_like
 #   error "__cpp_lib_forward_like should be defined in c++2b"
 # endif
diff --git a/libcxx/test/std/thread/thread.threads/thread.thread.class/thread.thread.id/format.functions.format.pass.cpp b/libcxx/test/std/thread/thread.threads/thread.thread.class/thread.thread.id/format.functions.format.pass.cpp
new file mode 100644
--- /dev/null
+++ b/libcxx/test/std/thread/thread.threads/thread.thread.class/thread.thread.id/format.functions.format.pass.cpp
@@ -0,0 +1,59 @@
+//===----------------------------------------------------------------------===//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17, c++20
+// UNSUPPORTED: no-threads
+// UNSUPPORTED: libcpp-has-no-incomplete-format
+
+// TODO FMT Fix this test using GCC, it currently times out.
+// UNSUPPORTED: gcc-12
+
+// This test requires the dylib support introduced in D92214.
+// XFAIL: use_system_cxx_lib && target={{.+}}-apple-macosx10.{{.+}}
+// XFAIL: use_system_cxx_lib && target={{.+}}-apple-macosx11.{{.+}}
+
+// <thread>
+
+// template<class charT>
+// struct formatter<thread::id, charT>;
+
+// template<class... Args>
+//   string format(format_string<Args...> fmt, Args&&... args);
+// template<class... Args>
+//   wstring format(wformat_string<Args...> fmt, Args&&... args);
+
+#include <format>
+#include <cassert>
+
+#include "assert_macros.h"
+#include "concat_macros.h"
+#include "format.functions.tests.h"
+#include "test_format_string.h"
+#include "test_macros.h"
+
+auto test = []<class CharT, class... Args>(
+                std::basic_string_view<CharT> expected, test_format_string<CharT, Args...> fmt, Args&&... args) {
+  std::basic_string<CharT> out = std::format(fmt, std::forward<Args>(args)...);
+  TEST_REQUIRE(out == expected,
+               TEST_WRITE_CONCATENATED(
+                   "\nFormat string   ", fmt, "\nExpected output ", expected, "\nActual output   ", out, '\n'));
+};
+
+auto test_exception = []<class CharT, class... Args>(std::string_view, std::basic_string_view<CharT>, Args&&...) {
+  // After P2216 most exceptions thrown by std::format become ill-formed.
+  // Therefore this tests does nothing.
+};
+
+int main(int, char**) {
+  format_tests<char>(test, test_exception);
+
+#ifndef TEST_HAS_NO_WIDE_CHARACTERS
+  format_tests<wchar_t>(test, test_exception);
+#endif
+
+  return 0;
+}
diff --git a/libcxx/test/std/thread/thread.threads/thread.thread.class/thread.thread.id/format.functions.tests.h b/libcxx/test/std/thread/thread.threads/thread.thread.class/thread.thread.id/format.functions.tests.h
new file mode 100644
--- /dev/null
+++ b/libcxx/test/std/thread/thread.threads/thread.thread.class/thread.thread.id/format.functions.tests.h
@@ -0,0 +1,78 @@
+//===----------------------------------------------------------------------===//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef TEST_STD_THREAD_THREAD_THREADS_THREAD_THREAD_CLASS_THREAD_THREAD_ID_FORMAT_FUNCTIONS_TESTS_H
+#define TEST_STD_THREAD_THREAD_THREADS_THREAD_THREAD_CLASS_THREAD_THREAD_ID_FORMAT_FUNCTIONS_TESTS_H
+
+#include <thread>
+
+#include "format.functions.common.h"
+#include "test_macros.h"
+
+template <class CharT, class TestFunction, class ExceptionTest>
+void format_tests(TestFunction check, ExceptionTest check_exception) {
+  // Note the output of std::thread::id is unspecified. The output text is the
+  // same as the stream operator. Since that format is already released this
+  // test follows the practice on existing systems.
+  std::thread::id input{};
+
+  /***** Test the type specific part *****/
+#ifndef __APPLE__
+  check(SV("0"), SV("{}"), input);
+
+  // *** align-fill & width ***
+  check(SV("    0"), SV("{:5}"), input);
+  check(SV("0****"), SV("{:*<5}"), input);
+  check(SV("__0__"), SV("{:_^5}"), input);
+  check(SV("::::0"), SV("{::>5}"), input); // This is not a range, so : is allowed as fill character.
+
+  check(SV("    0"), SV("{:{}}"), input, 5);
+  check(SV("0****"), SV("{:*<{}}"), input, 5);
+  check(SV("__0__"), SV("{:_^{}}"), input, 5);
+  check(SV("####0"), SV("{:#>{}}"), input, 5);
+#else  // __APPLE__
+  check(SV("0x0"), SV("{}"), input);
+
+  // *** align-fill & width ***
+  check(SV("    0x0"), SV("{:7}"), input);
+  check(SV("0x0****"), SV("{:*<7}"), input);
+  check(SV("__0x0__"), SV("{:_^7}"), input);
+  check(SV("::::0x0"), SV("{::>7}"), input); // This is not a range, so : is allowed as fill character.
+
+  check(SV("    0x0"), SV("{:{}}"), input, 7);
+  check(SV("0x0****"), SV("{:*<{}}"), input, 7);
+  check(SV("__0x0__"), SV("{:_^{}}"), input, 7);
+  check(SV("####0x0"), SV("{:#>{}}"), input, 7);
+#endif // __APPLE__
+
+  /***** Test the type generic part *****/
+  check_exception("The format-spec fill field contains an invalid character", SV("{:}<}"), input);
+  check_exception("The format-spec fill field contains an invalid character", SV("{:{<}"), input);
+
+  // *** sign ***
+  check_exception("The replacement field misses a terminating '}'", SV("{:-}"), input);
+  check_exception("The replacement field misses a terminating '}'", SV("{:+}"), input);
+  check_exception("The replacement field misses a terminating '}'", SV("{: }"), input);
+
+  // *** alternate form ***
+  check_exception("The replacement field misses a terminating '}'", SV("{:#}"), input);
+
+  // *** zero-padding ***
+  check_exception("A format-spec width field shouldn't have a leading zero", SV("{:0}"), input);
+
+  // *** precision ***
+  check_exception("The replacement field misses a terminating '}'", SV("{:.}"), input);
+
+  // *** locale-specific form ***
+  check_exception("The replacement field misses a terminating '}'", SV("{:L}"), input);
+
+  // *** type ***
+  for (std::basic_string_view<CharT> fmt : fmt_invalid_types<CharT>(""))
+    check_exception("The replacement field misses a terminating '}'", fmt, input);
+}
+
+#endif // TEST_STD_THREAD_THREAD_THREADS_THREAD_THREAD_CLASS_THREAD_THREAD_ID_FORMAT_FUNCTIONS_TESTS_H
diff --git a/libcxx/test/std/thread/thread.threads/thread.thread.class/thread.thread.id/format.functions.vformat.pass.cpp b/libcxx/test/std/thread/thread.threads/thread.thread.class/thread.thread.id/format.functions.vformat.pass.cpp
new file mode 100644
--- /dev/null
+++ b/libcxx/test/std/thread/thread.threads/thread.thread.class/thread.thread.id/format.functions.vformat.pass.cpp
@@ -0,0 +1,67 @@
+//===----------------------------------------------------------------------===//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17, c++20
+// UNSUPPORTED: no-threads
+// UNSUPPORTED: libcpp-has-no-incomplete-format
+
+// TODO FMT Fix this test using GCC, it currently times out.
+// UNSUPPORTED: gcc-12
+
+// This test requires the dylib support introduced in D92214.
+// XFAIL: use_system_cxx_lib && target={{.+}}-apple-macosx10.{{.+}}
+// XFAIL: use_system_cxx_lib && target={{.+}}-apple-macosx11.{{.+}}
+
+// <thread>
+
+// template<class charT>
+// struct formatter<thread::id, charT>;
+
+// string vformat(string_view fmt, format_args args);
+// wstring vformat(wstring_view fmt, wformat_args args);
+
+#include <format>
+#include <cassert>
+
+#include "assert_macros.h"
+#include "concat_macros.h"
+#include "format.functions.tests.h"
+#include "test_macros.h"
+
+auto test = []<class CharT, class... Args>(
+                std::basic_string_view<CharT> expected, std::basic_string_view<CharT> fmt, Args&&... args) {
+  std::basic_string<CharT> out = std::vformat(fmt, std::make_format_args<context_t<CharT>>(args...));
+  TEST_REQUIRE(out == expected,
+               TEST_WRITE_CONCATENATED(
+                   "\nFormat string   ", fmt, "\nExpected output ", expected, "\nActual output   ", out, '\n'));
+};
+
+auto test_exception =
+    []<class CharT, class... Args>(
+        [[maybe_unused]] std::string_view what,
+        [[maybe_unused]] std::basic_string_view<CharT> fmt,
+        [[maybe_unused]] Args&&... args) {
+      TEST_VALIDATE_EXCEPTION(
+          std::format_error,
+          [&]([[maybe_unused]] const std::format_error& e) {
+            TEST_LIBCPP_REQUIRE(
+                e.what() == what,
+                TEST_WRITE_CONCATENATED(
+                    "\nFormat string   ", fmt, "\nExpected exception ", what, "\nActual exception   ", e.what(), '\n'));
+          },
+          TEST_IGNORE_NODISCARD std::vformat(fmt, std::make_format_args<context_t<CharT>>(args...)));
+    };
+
+int main(int, char**) {
+  format_tests<char>(test, test_exception);
+
+#ifndef TEST_HAS_NO_WIDE_CHARACTERS
+  format_tests<wchar_t>(test, test_exception);
+#endif
+
+  return 0;
+}
diff --git a/libcxx/test/std/thread/thread.threads/thread.thread.class/thread.thread.id/format.pass.cpp b/libcxx/test/std/thread/thread.threads/thread.thread.class/thread.thread.id/format.pass.cpp
new file mode 100644
--- /dev/null
+++ b/libcxx/test/std/thread/thread.threads/thread.thread.class/thread.thread.id/format.pass.cpp
@@ -0,0 +1,77 @@
+//===----------------------------------------------------------------------===//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17, c++20
+// UNSUPPORTED: no-threads
+// UNSUPPORTED: libcpp-has-no-incomplete-format
+
+// TODO FMT Fix this test using GCC, it currently times out.
+// UNSUPPORTED: gcc-12
+
+// This test requires the dylib support introduced in D92214.
+// XFAIL: use_system_cxx_lib && target={{.+}}-apple-macosx10.{{.+}}
+// XFAIL: use_system_cxx_lib && target={{.+}}-apple-macosx11.{{.+}}
+
+// <thread>
+
+// template<class charT>
+// struct formatter<thread::id, charT>;
+
+// template<class FormatContext>
+//   typename FormatContext::iterator
+//     format(const T& r, FormatContext& ctx) const;
+
+// Note this tests the basics of this function. It's tested in more detail in
+// the format functions test.
+
+#include <cassert>
+#include <concepts>
+#include <thread>
+
+#include "test_format_context.h"
+#include "test_macros.h"
+#include "make_string.h"
+
+#define SV(S) MAKE_STRING_VIEW(CharT, S)
+
+template <class StringViewT>
+void test_format(StringViewT expected, std::thread::id arg) {
+  using CharT      = typename StringViewT::value_type;
+  using String     = std::basic_string<CharT>;
+  using OutIt      = std::back_insert_iterator<String>;
+  using FormatCtxT = std::basic_format_context<OutIt, CharT>;
+
+  const std::formatter<std::thread::id, CharT> formatter;
+
+  String result;
+  OutIt out             = std::back_inserter(result);
+  FormatCtxT format_ctx = test_format_context_create<OutIt, CharT>(out, std::make_format_args<FormatCtxT>(arg));
+  formatter.format(arg, format_ctx);
+  assert(result == expected);
+}
+
+template <class CharT>
+void test_fmt() {
+#ifndef __APPLE__
+  test_format(SV("0"), std::thread::id());
+#else
+  test_format(SV("0x0"), std::thread::id());
+#endif
+}
+
+void test() {
+  test_fmt<char>();
+#ifndef TEST_HAS_NO_WIDE_CHARACTERS
+  test_fmt<wchar_t>();
+#endif
+}
+
+int main(int, char**) {
+  test();
+
+  return 0;
+}
diff --git a/libcxx/test/std/thread/thread.threads/thread.thread.class/thread.thread.id/parse.pass.cpp b/libcxx/test/std/thread/thread.threads/thread.thread.class/thread.thread.id/parse.pass.cpp
new file mode 100644
--- /dev/null
+++ b/libcxx/test/std/thread/thread.threads/thread.thread.class/thread.thread.id/parse.pass.cpp
@@ -0,0 +1,75 @@
+//===----------------------------------------------------------------------===//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17, c++20
+// UNSUPPORTED: no-threads
+// UNSUPPORTED: libcpp-has-no-incomplete-format
+
+// TODO FMT Fix this test using GCC, it currently times out.
+// UNSUPPORTED: gcc-12
+
+// This test requires the dylib support introduced in D92214.
+// XFAIL: use_system_cxx_lib && target={{.+}}-apple-macosx10.{{.+}}
+// XFAIL: use_system_cxx_lib && target={{.+}}-apple-macosx11.{{.+}}
+
+// <thread>
+
+// template<class charT>
+// struct formatter<thread::id, charT>;
+
+// template<class ParseContext>
+//   constexpr typename ParseContext::iterator
+//     parse(ParseContext& ctx);
+
+// Note this tests the basics of this function. It's tested in more detail in
+// the format functions test.
+
+#include <cassert>
+#include <concepts>
+#include <thread>
+
+#include "test_format_context.h"
+#include "test_macros.h"
+#include "make_string.h"
+
+#define SV(S) MAKE_STRING_VIEW(CharT, S)
+
+template <class StringViewT>
+constexpr void test_parse(StringViewT fmt) {
+  using CharT    = typename StringViewT::value_type;
+  auto parse_ctx = std::basic_format_parse_context<CharT>(fmt);
+  std::formatter<std::thread::id, CharT> formatter;
+  static_assert(std::semiregular<decltype(formatter)>);
+
+  std::same_as<typename StringViewT::iterator> auto it = formatter.parse(parse_ctx);
+  assert(it == fmt.end() - (!fmt.empty() && fmt.back() == '}'));
+}
+
+template <class CharT>
+constexpr void test_fmt() {
+  test_parse(SV(""));
+  test_parse(SV("1"));
+
+  test_parse(SV("}"));
+  test_parse(SV("1}"));
+}
+
+constexpr bool test() {
+  test_fmt<char>();
+#ifndef TEST_HAS_NO_WIDE_CHARACTERS
+  test_fmt<wchar_t>();
+#endif
+
+  return true;
+}
+
+int main(int, char**) {
+  test();
+  static_assert(test());
+
+  return 0;
+}
diff --git a/libcxx/test/std/thread/thread.threads/thread.thread.class/thread.thread.id/stream.pass.cpp b/libcxx/test/std/thread/thread.threads/thread.thread.class/thread.thread.id/stream.pass.cpp
--- a/libcxx/test/std/thread/thread.threads/thread.thread.class/thread.thread.id/stream.pass.cpp
+++ b/libcxx/test/std/thread/thread.threads/thread.thread.class/thread.thread.id/stream.pass.cpp
@@ -18,16 +18,36 @@
 // operator<<(basic_ostream<charT, traits>& out, thread::id id);
 
 #include <thread>
+#include <format>
 #include <sstream>
 #include <cassert>
 
+#include "make_string.h"
 #include "test_macros.h"
 
-int main(int, char**)
-{
-    std::thread::id id0 = std::this_thread::get_id();
-    std::ostringstream os;
-    os << id0;
+template <class CharT>
+static void test() {
+  std::thread::id id0 = std::this_thread::get_id();
+  std::basic_ostringstream<CharT> os;
+  os << id0;
+
+#if TEST_STD_VER > 20 && !defined(_LIBCPP_HAS_NO_INCOMPLETE_FORMAT)
+  // C++23 added a formatter specialization for thread::id.
+  // This changed the requirement of ostream to have a
+  //   text representation for charT
+  // this definition is used for both streaming and formatting.
+  //
+  // Test whether the output is identical.
+  std::basic_string<CharT> s = std::format(MAKE_STRING_VIEW(CharT, "{}"), id0);
+  assert(s == os.str());
+#endif
+}
+
+int main(int, char**) {
+  test<char>();
+#ifndef TEST_HAS_NO_WIDE_CHARACTERS
+  test<wchar_t>();
+#endif
 
   return 0;
 }
diff --git a/libcxx/test/std/utilities/format/format.formattable/concept.formattable.compile.pass.cpp b/libcxx/test/std/utilities/format/format.formattable/concept.formattable.compile.pass.cpp
--- a/libcxx/test/std/utilities/format/format.formattable/concept.formattable.compile.pass.cpp
+++ b/libcxx/test/std/utilities/format/format.formattable/concept.formattable.compile.pass.cpp
@@ -174,7 +174,8 @@
 // Tests for P1636 Formatters for library types
 //
 // The paper hasn't been voted in so currently all formatters are disabled.
-// TODO validate whether the test is correct after the paper has been accepted.
+// Note the paper has been abandoned, the types are kept since other papers may
+// introduce these formatters.
 template <class CharT>
 void test_P1636() {
   assert_is_not_formattable<std::basic_streambuf<CharT>, CharT>();
@@ -190,7 +191,7 @@
     assert_is_not_formattable<std::sub_match<CharT*>, CharT>();
 #endif
 #ifndef TEST_HAS_NO_THREADS
-  assert_is_not_formattable<std::thread::id, CharT>();
+  assert_is_formattable<std::thread::id, CharT>();
 #endif
   assert_is_not_formattable<std::unique_ptr<int>, CharT>();
 }
diff --git a/libcxx/utils/generate_feature_test_macro_components.py b/libcxx/utils/generate_feature_test_macro_components.py
--- a/libcxx/utils/generate_feature_test_macro_components.py
+++ b/libcxx/utils/generate_feature_test_macro_components.py
@@ -329,6 +329,13 @@
     "test_suite_guard": "!defined(_LIBCPP_AVAILABILITY_DISABLE_FTM___cpp_lib_format) && !defined(_LIBCPP_HAS_NO_INCOMPLETE_FORMAT)",
     "libcxx_guard": "!defined(_LIBCPP_AVAILABILITY_DISABLE_FTM___cpp_lib_format) && !defined(_LIBCPP_HAS_NO_INCOMPLETE_FORMAT)",
     "unimplemented": True,
+  }, {
+    "name": "__cpp_lib_formatters",
+    "values": { "c++2b": 202302 },
+    "headers": ["stacktrace", "thread"],
+    "test_suite_guard": "!defined(_LIBCPP_AVAILABILITY_DISABLE_FTM___cpp_lib_format) && !defined(_LIBCPP_HAS_NO_INCOMPLETE_FORMAT)",
+    "libcxx_guard": "!defined(_LIBCPP_AVAILABILITY_DISABLE_FTM___cpp_lib_format) && !defined(_LIBCPP_HAS_NO_INCOMPLETE_FORMAT)",
+    "unimplemented": True,
   }, {
     "name": "__cpp_lib_forward_like",
     "values": { "c++2b": 202207 },