diff --git a/CHANGELOG.md b/CHANGELOG.md
index 1c72579610aad043ee849100a4351a218e6f6ab9..29a6ae958df76c0068ce6fba10fd36060222cc5f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -20,6 +20,10 @@ current (development)
     - multiple directions.
     - multiple colors.
     - various values (value, min, max, increment).
+- Feature: Define `ScreenInteractive::Exit()`.
+- Feature: Add `Loop` to give developers a better control on the main loop. This
+  can be used to integrate FTXUI into another main loop, without taking the full
+  control.
 - Feature: `Input` supports CTRL+Left and CTRL+Right
 - Improvement: The `Menu` keeps the focus when an entry is selected with the
   mouse.
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 2a884350950942e5c0ab25822f0ce215003aa455..4f885630f41c6c371b6ae1cdc80fd018ddfe7ce0 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -93,6 +93,7 @@ add_library(component
   include/ftxui/component/component_base.hpp
   include/ftxui/component/component_options.hpp
   include/ftxui/component/event.hpp
+  include/ftxui/component/loop.hpp
   include/ftxui/component/mouse.hpp
   include/ftxui/component/receiver.hpp
   include/ftxui/component/screen_interactive.hpp
@@ -108,9 +109,10 @@ add_library(component
   src/ftxui/component/dropdown.cpp
   src/ftxui/component/event.cpp
   src/ftxui/component/input.cpp
+  src/ftxui/component/loop.cpp
   src/ftxui/component/maybe.cpp
-  src/ftxui/component/modal.cpp
   src/ftxui/component/menu.cpp
+  src/ftxui/component/modal.cpp
   src/ftxui/component/radiobox.cpp
   src/ftxui/component/radiobox.cpp
   src/ftxui/component/renderer.cpp
diff --git a/examples/component/CMakeLists.txt b/examples/component/CMakeLists.txt
index e89111039690dbcec116ac73c7be52c95db2b52c..4eb971e68cb29e6861355115090540d64c7ac02e 100644
--- a/examples/component/CMakeLists.txt
+++ b/examples/component/CMakeLists.txt
@@ -9,6 +9,7 @@ example(checkbox)
 example(checkbox_in_frame)
 example(collapsible)
 example(composition)
+example(custom_loop)
 example(dropdown)
 example(flexbox_gallery)
 example(focus)
diff --git a/examples/component/custom_loop.cpp b/examples/component/custom_loop.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..50702d038294aa0f4ca4c1bc8a232fc16e37acac
--- /dev/null
+++ b/examples/component/custom_loop.cpp
@@ -0,0 +1,55 @@
+#include <stdlib.h>                   // for EXIT_SUCCESS
+#include <chrono>                     // for milliseconds
+#include <ftxui/component/event.hpp>  // for Event
+#include <ftxui/dom/elements.hpp>  // for text, separator, Element, operator|, vbox, border
+#include <memory>                  // for shared_ptr
+#include <string>                  // for operator+, to_string, allocator
+#include <thread>                  // for sleep_for
+
+#include "ftxui/component/captured_mouse.hpp"  // for ftxui
+#include "ftxui/component/component.hpp"  // for CatchEvent, Renderer, operator|=
+#include "ftxui/component/loop.hpp"       // for Loop
+#include "ftxui/component/screen_interactive.hpp"  // for ScreenInteractive
+
+int main(int argc, const char* argv[]) {
+  using namespace ftxui;
+  auto screen = ScreenInteractive::FitComponent();
+
+  // Create a component counting the number of frames drawn and event handled.
+  int custom_loop_count = 0;
+  int frame_count = 0;
+  int event_count = 0;
+  auto component = Renderer([&] {
+    frame_count++;
+    return vbox({
+               text("This demonstrates using a custom ftxui::Loop. It "),
+               text("runs at 100 iterations per seconds. The FTXUI events "),
+               text("are all processed once per iteration and a new frame "),
+               text("is rendered as needed"),
+               separator(),
+               text("ftxui event count: " + std::to_string(event_count)),
+               text("ftxui frame count: " + std::to_string(frame_count)),
+               text("Custom loop count: " + std::to_string(custom_loop_count)),
+           }) |
+           border;
+  });
+
+  component |= CatchEvent([&](Event) -> bool {
+    event_count++;
+    return false;
+  });
+
+  Loop loop(&screen, component);
+
+  while (!loop.HasQuitted()) {
+    custom_loop_count++;
+    loop.RunOnce();
+    std::this_thread::sleep_for(std::chrono::milliseconds(10));
+  }
+
+  return EXIT_SUCCESS;
+}
+
+// Copyright 2020 Arthur Sonzogni. All rights reserved.
+// Use of this source code is governed by the MIT license that can be found in
+// the LICENSE file.
diff --git a/include/ftxui/component/loop.hpp b/include/ftxui/component/loop.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..63200bed599ea12137b3caabebb6e5b3a1737747
--- /dev/null
+++ b/include/ftxui/component/loop.hpp
@@ -0,0 +1,39 @@
+#ifndef FTXUI_COMPONENT_LOOP_HPP
+#define FTXUI_COMPONENT_LOOP_HPP
+
+#include <memory>  // for shared_ptr
+
+#include "ftxui/component/component_base.hpp"  // for ComponentBase
+
+namespace ftxui {
+class ComponentBase;
+
+using Component = std::shared_ptr<ComponentBase>;
+class ScreenInteractive;
+
+class Loop {
+ public:
+  Loop(ScreenInteractive* screen, Component component);
+  ~Loop();
+
+  bool HasQuitted();
+  void RunOnce();
+  void RunOnceBlocking();
+  void Run();
+
+ private:
+  // This class is non copyable.
+  Loop(const ScreenInteractive&) = delete;
+  Loop& operator=(const Loop&) = delete;
+
+  ScreenInteractive* screen_;
+  Component component_;
+};
+
+}  // namespace ftxui
+
+#endif  // FTXUI_COMPONENT_LOOP_HPP
+
+// Copyright 2022 Arthur Sonzogni. All rights reserved.
+// Use of this source code is governed by the MIT license that can be found in
+// the LICENSE file.
diff --git a/include/ftxui/component/receiver.hpp b/include/ftxui/component/receiver.hpp
index d2579889d1f93a23cf966a712bff907db6f3d2fd..1d487644f7d96fa4879339395de9a5e87e708634 100644
--- a/include/ftxui/component/receiver.hpp
+++ b/include/ftxui/component/receiver.hpp
@@ -86,11 +86,25 @@ class ReceiverImpl {
     return false;
   }
 
+  bool ReceiveNonBlocking(T* t) {
+    std::unique_lock<std::mutex> lock(mutex_);
+    if (queue_.empty())
+      return false;
+    *t = queue_.front();
+    queue_.pop();
+    return true;
+  }
+
   bool HasPending() {
     std::unique_lock<std::mutex> lock(mutex_);
     return !queue_.empty();
   }
 
+  bool HasQuitted() {
+    std::unique_lock<std::mutex> lock(mutex_);
+    return queue_.empty() && !senders_;
+  }
+
  private:
   friend class SenderImpl<T>;
 
diff --git a/include/ftxui/component/screen_interactive.hpp b/include/ftxui/component/screen_interactive.hpp
index ab3420741a98b826500e37f19ebef69bd19eef88..9aed718f78aa452cdf9d67aefdd61d8a3eda59d1 100644
--- a/include/ftxui/component/screen_interactive.hpp
+++ b/include/ftxui/component/screen_interactive.hpp
@@ -12,11 +12,12 @@
 #include "ftxui/component/animation.hpp"       // for TimePoint
 #include "ftxui/component/captured_mouse.hpp"  // for CapturedMouse
 #include "ftxui/component/event.hpp"           // for Event
-#include "ftxui/component/task.hpp"            // for Closure, Task
+#include "ftxui/component/task.hpp"            // for Task, Closure
 #include "ftxui/screen/screen.hpp"             // for Screen
 
 namespace ftxui {
 class ComponentBase;
+class Loop;
 struct Event;
 
 using Component = std::shared_ptr<ComponentBase>;
@@ -33,9 +34,12 @@ class ScreenInteractive : public Screen {
   // Return the currently active screen, nullptr if none.
   static ScreenInteractive* Active();
 
+  // Start/Stop the main loop.
   void Loop(Component);
+  void Exit();
   Closure ExitLoopClosure();
 
+  // Post tasks to be executed by the loop.
   void Post(Task task);
   void PostEvent(Event event);
   void RequestAnimationFrame();
@@ -51,9 +55,16 @@ class ScreenInteractive : public Screen {
   void Install();
   void Uninstall();
 
-  void Main(Component component);
+  void PreMain();
+  void PostMain();
 
+  bool HasQuitted();
+  void RunOnce(Component component);
+  void RunOnceBlocking(Component component);
+
+  void HandleTask(Component component, Task& task);
   void Draw(Component component);
+
   void SigStop();
 
   ScreenInteractive* suspended_screen_ = nullptr;
@@ -80,7 +91,7 @@ class ScreenInteractive : public Screen {
   std::thread event_listener_;
   std::thread animation_listener_;
   bool animation_requested_ = false;
-  animation::TimePoint previous_animation_time;
+  animation::TimePoint previous_animation_time_;
 
   int cursor_x_ = 1;
   int cursor_y_ = 1;
@@ -88,6 +99,10 @@ class ScreenInteractive : public Screen {
   bool mouse_captured = false;
   bool previous_frame_resized_ = false;
 
+  bool frame_valid_ = false;
+
+  friend class Loop;
+
  public:
   class Private {
    public:
diff --git a/src/ftxui/component/loop.cpp b/src/ftxui/component/loop.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..264642880ffe4c6da96ed58d958dee07e4793223
--- /dev/null
+++ b/src/ftxui/component/loop.cpp
@@ -0,0 +1,44 @@
+#include "ftxui/component/loop.hpp"
+#include "ftxui/component/screen_interactive.hpp"
+
+namespace ftxui {
+
+Loop::Loop(ScreenInteractive* screen, Component component)
+    : screen_(screen), component_(component) {
+  screen_->PreMain();
+}
+
+Loop::~Loop() {
+  screen_->PostMain();
+}
+
+bool Loop::HasQuitted() {
+  return screen_->HasQuitted();
+}
+
+/// @brief Execute the loop. Make the `component` to process every pending
+/// tasks/events. A new frame might be drawn if the previous was invalidated.
+/// Return true until the loop hasn't completed.
+void Loop::RunOnce() {
+  screen_->RunOnce(component_);
+}
+
+/// @brief Wait for at least one event to be handled and execute
+/// `Loop::RunOnce()`.
+void Loop::RunOnceBlocking() {
+  screen_->RunOnceBlocking(component_);
+}
+
+/// Execute the loop, blocking the current thread, up until the loop has
+/// quitted.
+void Loop::Run() {
+  while (!HasQuitted()) {
+    RunOnceBlocking();
+  }
+}
+
+}  // namespace ftxui
+
+// Copyright 2022 Arthur Sonzogni. All rights reserved.
+// Use of this source code is governed by the MIT license that can be found in
+// the LICENSE file.
diff --git a/src/ftxui/component/screen_interactive.cpp b/src/ftxui/component/screen_interactive.cpp
index 32d7aabc034021202a391433c10e3596ba2418fb..278c53cd3906756d2b9cbc2d8a1bc905edb84576 100644
--- a/src/ftxui/component/screen_interactive.cpp
+++ b/src/ftxui/component/screen_interactive.cpp
@@ -20,6 +20,7 @@
 #include "ftxui/component/captured_mouse.hpp"  // for CapturedMouse, CapturedMouseInterface
 #include "ftxui/component/component_base.hpp"  // for ComponentBase
 #include "ftxui/component/event.hpp"           // for Event
+#include "ftxui/component/loop.hpp"            // for Loop
 #include "ftxui/component/receiver.hpp"  // for Sender, ReceiverImpl, MakeReceiver, SenderImpl, Receiver
 #include "ftxui/component/screen_interactive.hpp"
 #include "ftxui/component/terminal_input_parser.hpp"  // for TerminalInputParser
@@ -350,8 +351,8 @@ void ScreenInteractive::RequestAnimationFrame() {
   animation_requested_ = true;
   auto now = animation::Clock::now();
   const auto time_histeresis = std::chrono::milliseconds(33);
-  if (now - previous_animation_time >= time_histeresis) {
-    previous_animation_time = now;
+  if (now - previous_animation_time_ >= time_histeresis) {
+    previous_animation_time_ = now;
   }
 }
 
@@ -365,6 +366,15 @@ CapturedMouse ScreenInteractive::CaptureMouse() {
 }
 
 void ScreenInteractive::Loop(Component component) {  // NOLINT
+  class Loop loop(this, component);
+  loop.Run();
+}
+
+bool ScreenInteractive::HasQuitted() {
+  return task_receiver_->HasQuitted();
+}
+
+void ScreenInteractive::PreMain() {
   // Suspend previously active screen:
   if (g_active_screen) {
     std::swap(suspended_screen_, g_active_screen);
@@ -378,7 +388,11 @@ void ScreenInteractive::Loop(Component component) {  // NOLINT
   // This screen is now active:
   g_active_screen = this;
   g_active_screen->Install();
-  g_active_screen->Main(std::move(component));
+
+  previous_animation_time_ = animation::Clock::now();
+}
+
+void ScreenInteractive::PostMain() {
   g_active_screen->Uninstall();
   g_active_screen = nullptr;
 
@@ -531,81 +545,78 @@ void ScreenInteractive::Uninstall() {
 }
 
 // NOLINTNEXTLINE
-void ScreenInteractive::Main(Component component) {
-  previous_animation_time = animation::Clock::now();
+void ScreenInteractive::RunOnceBlocking(Component component) {
+  Task task;
+  if (task_receiver_->Receive(&task)) {
+    HandleTask(component, task);
+  }
 
-  auto draw = [&] {
-    Draw(component);
-    std::cout << ToString() << set_cursor_position;
-    Flush();
-    Clear();
-  };
+  RunOnce(component);
+}
 
-  bool attempt_draw = true;
-  while (!quit_) {
-    if (attempt_draw && !task_receiver_->HasPending()) {
-      draw();
-      attempt_draw = false;
-    }
+void ScreenInteractive::RunOnce(Component component) {
+  Task task;
+  while (task_receiver_->ReceiveNonBlocking(&task)) {
+    HandleTask(component, task);
+  }
+  Draw(component);
+}
 
-    Task task;
-    if (!task_receiver_->Receive(&task)) {
-      break;
-    }
+void ScreenInteractive::HandleTask(Component component, Task& task) {
+  // clang-format off
+  std::visit([&](auto&& arg) {
+    using T = std::decay_t<decltype(arg)>;
 
-    // clang-format off
-    std::visit([&](auto&& arg) {
-      using T = std::decay_t<decltype(arg)>;
-
-      // Handle Event.
-      if constexpr (std::is_same_v<T, Event>) {
-        if (arg.is_cursor_reporting()) {
-          cursor_x_ = arg.cursor_x();
-          cursor_y_ = arg.cursor_y();
-          return;
-        }
-
-        if (arg.is_mouse()) {
-          arg.mouse().x -= cursor_x_;
-          arg.mouse().y -= cursor_y_;
-        }
-
-        arg.screen_ = this;
-        component->OnEvent(arg);
-        attempt_draw = true;
+    // Handle Event.
+    if constexpr (std::is_same_v<T, Event>) {
+      if (arg.is_cursor_reporting()) {
+        cursor_x_ = arg.cursor_x();
+        cursor_y_ = arg.cursor_y();
         return;
       }
 
-      // Handle callback
-      if constexpr (std::is_same_v<T, Closure>) {
-        arg();
-        return;
+      if (arg.is_mouse()) {
+        arg.mouse().x -= cursor_x_;
+        arg.mouse().y -= cursor_y_;
       }
 
-      // Handle Animation
-      if constexpr (std::is_same_v<T, AnimationTask>) {
-        if (!animation_requested_) {
-          return;
-        }
+      arg.screen_ = this;
+      component->OnEvent(arg);
+      frame_valid_ = false;
+      return;
+    }
 
-        animation_requested_ = false;
-        animation::TimePoint now = animation::Clock::now();
-        animation::Duration delta = now - previous_animation_time;
-        previous_animation_time = now;
+    // Handle callback
+    if constexpr (std::is_same_v<T, Closure>) {
+      arg();
+      return;
+    }
 
-        animation::Params params(delta);
-        component->OnAnimation(params);
-        attempt_draw = true;
+    // Handle Animation
+    if constexpr (std::is_same_v<T, AnimationTask>) {
+      if (!animation_requested_) {
         return;
       }
-    },
-    task);
-    // clang-format on
-  }
+
+      animation_requested_ = false;
+      animation::TimePoint now = animation::Clock::now();
+      animation::Duration delta = now - previous_animation_time_;
+      previous_animation_time_ = now;
+
+      animation::Params params(delta);
+      component->OnAnimation(params);
+      frame_valid_ = false;
+      return;
+    }
+  },
+  task);
+  // clang-format on
 }
 
 // NOLINTNEXTLINE
 void ScreenInteractive::Draw(Component component) {
+  if (frame_valid_)
+    return;
   auto document = component->Render();
   int dimx = 0;
   int dimy = 0;
@@ -685,13 +696,22 @@ void ScreenInteractive::Draw(Component component) {
     set_cursor_position += "\x1B[" + std::to_string(dy) + "A";
     reset_cursor_position += "\x1B[" + std::to_string(dy) + "B";
   }
+
+  std::cout << ToString() << set_cursor_position;
+  Flush();
+  Clear();
+  frame_valid_ = true;
 }
 
 Closure ScreenInteractive::ExitLoopClosure() {
-  return [this] {
+  return [this] { Exit(); };
+}
+
+void ScreenInteractive::Exit() {
+  Post([this] {
     quit_ = true;
     task_sender_.reset();
-  };
+  });
 }
 
 void ScreenInteractive::SigStop() {