diff --git a/Package.swift b/Package.swift
index 85f80eb143901b1b4aacdb6c9a4c21ca34b734e0..eb6b71d06d8ca515abdd0d7040f3a7426c5a2fa4 100644
--- a/Package.swift
+++ b/Package.swift
@@ -665,6 +665,8 @@ let package = Package(
       dependencies: [
         .target(name: "AppResources"),
         .target(name: "Shared"),
+        .product(name: "Dependencies", package: "swift-composable-architecture"),
+        .product(name: "XCTestDynamicOverlay", package: "xctest-dynamic-overlay"),
       ],
       swiftSettings: swiftSettings
     ),
diff --git a/Sources/HUDFeature/HUDManager.swift b/Sources/HUDFeature/HUDManager.swift
new file mode 100644
index 0000000000000000000000000000000000000000..418cd669b8215e55de03cd61023de24767a4339c
--- /dev/null
+++ b/Sources/HUDFeature/HUDManager.swift
@@ -0,0 +1,63 @@
+import Combine
+import Dependencies
+import Foundation
+import XCTestDynamicOverlay
+
+public struct HUDManager {
+  public struct Show {
+    public var run: (HUDModel?) -> Void
+    public func callAsFunction(_ model: HUDModel? = nil) {
+      run(model)
+    }
+  }
+
+  public var show: Show
+  public var hide: () -> Void
+  public var observe: () -> AnyPublisher<HUDModel?, Never>
+}
+
+extension HUDManager {
+  public static func live() -> HUDManager {
+    let subject = PassthroughSubject<HUDModel?, Never>()
+    return HUDManager(
+      show: .init { model in
+        let model = model ?? HUDModel(hasDotAnimation: true)
+        subject.send(model)
+        if model.isAutoDismissable {
+          DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
+            subject.send(nil)
+          }
+        }
+      },
+      hide: {
+        subject.send(nil)
+      },
+      observe: {
+        subject.eraseToAnyPublisher()
+      }
+    )
+  }
+}
+
+extension HUDManager {
+  public static let unimplemented = HUDManager(
+    show: .init(run: XCTestDynamicOverlay.unimplemented("\(Self.self).show")),
+    hide: XCTestDynamicOverlay.unimplemented("\(Self.self).hide"),
+    observe: XCTestDynamicOverlay.unimplemented(
+      "\(Self.self).observe",
+      placeholder: Empty().eraseToAnyPublisher()
+    )
+  )
+}
+
+private enum HUDManagerKey: DependencyKey {
+  static let liveValue: HUDManager = .live()
+  static let testValue: HUDManager = .unimplemented
+}
+
+extension DependencyValues {
+  public var hudManager: HUDManager {
+    get { self[HUDManagerKey.self] }
+    set { self[HUDManagerKey.self] = newValue }
+  }
+}