diff --git a/ash/public/cpp/system/anchored_nudge_data.h b/ash/public/cpp/system/anchored_nudge_data.h
index 0faeeb0a6eeea..20af1132c5ed4 100644
--- a/ash/public/cpp/system/anchored_nudge_data.h
+++ b/ash/public/cpp/system/anchored_nudge_data.h
@@ -81,6 +81,10 @@ struct ASH_PUBLIC_EXPORT AnchoredNudgeData {
   // anchor view is deleted, user locks session, etc.)
   bool has_infinite_duration = false;
 
+  // If true, `arrow` will be set based on the current shelf alignment, and the
+  // nudge will listen to shelf alignment changes to readjust its `arrow`.
+  bool anchored_to_shelf = false;
+
   // Nudge action callbacks.
   HoverStateChangeCallback hover_state_change_callback;
   AnchoredNudgeClickCallback nudge_click_callback;
diff --git a/ash/system/phonehub/phone_hub_nudge_controller.cc b/ash/system/phonehub/phone_hub_nudge_controller.cc
index 8ddf96ff496ae..8551b6e243ce2 100644
--- a/ash/system/phonehub/phone_hub_nudge_controller.cc
+++ b/ash/system/phonehub/phone_hub_nudge_controller.cc
@@ -27,6 +27,7 @@ void PhoneHubNudgeController::ShowNudge(views::View* anchor_view,
   }
   AnchoredNudgeData nudge_data = {kPhoneHubNudgeId, NudgeCatalogName::kPhoneHub,
                                   text, anchor_view};
+  nudge_data.anchored_to_shelf = true;
   AnchoredNudgeManager::Get()->Show(nudge_data);
 }
 
diff --git a/ash/system/toast/anchored_nudge.cc b/ash/system/toast/anchored_nudge.cc
index c469f074dce33..6bf5e6d731b79 100644
--- a/ash/system/toast/anchored_nudge.cc
+++ b/ash/system/toast/anchored_nudge.cc
@@ -8,6 +8,8 @@
 
 #include "ash/constants/ash_features.h"
 #include "ash/public/cpp/shelf_types.h"
+#include "ash/shelf/shelf.h"
+#include "ash/shell.h"
 #include "ash/system/toast/system_nudge_view.h"
 #include "base/functional/bind.h"
 #include "base/functional/callback.h"
@@ -28,6 +30,7 @@ AnchoredNudge::AnchoredNudge(const AnchoredNudgeData& nudge_data)
                                       nudge_data.arrow,
                                       views::BubbleBorder::NO_SHADOW),
       id_(nudge_data.id),
+      anchored_to_shelf_(nudge_data.anchored_to_shelf),
       nudge_click_callback_(std::move(nudge_data.nudge_click_callback)),
       nudge_dismiss_callback_(std::move(nudge_data.nudge_dimiss_callback)) {
   DCHECK(features::IsSystemNudgeV2Enabled());
@@ -39,12 +42,20 @@ AnchoredNudge::AnchoredNudge(const AnchoredNudgeData& nudge_data)
   SetLayoutManager(std::make_unique<views::FlexLayout>());
   system_nudge_view_ =
       AddChildView(std::make_unique<SystemNudgeView>(nudge_data));
+
+  if (anchored_to_shelf_) {
+    Shell::Get()->AddShellObserver(this);
+  }
 }
 
 AnchoredNudge::~AnchoredNudge() {
   if (!nudge_dismiss_callback_.is_null()) {
     std::move(nudge_dismiss_callback_).Run();
   }
+
+  if (anchored_to_shelf_) {
+    Shell::Get()->RemoveShellObserver(this);
+  }
 }
 
 views::ImageView* AnchoredNudge::GetImageView() {
@@ -89,6 +100,12 @@ AnchoredNudge::CreateNonClientFrameView(views::Widget* widget) {
   return frame;
 }
 
+void AnchoredNudge::AddedToWidget() {
+  if (anchored_to_shelf_) {
+    SetArrowFromShelf();
+  }
+}
+
 bool AnchoredNudge::OnMousePressed(const ui::MouseEvent& event) {
   return true;
 }
@@ -118,6 +135,31 @@ void AnchoredNudge::OnGestureEvent(ui::GestureEvent* event) {
   }
 }
 
+void AnchoredNudge::OnShelfAlignmentChanged(aura::Window* root_window,
+                                            ShelfAlignment old_alignment) {
+  if (!GetWidget()) {
+    return;
+  }
+
+  if (Shelf::ForWindow(GetWidget()->GetNativeWindow()) ==
+      Shelf::ForWindow(root_window)) {
+    SetArrowFromShelf();
+  }
+}
+
+void AnchoredNudge::SetArrowFromShelf() {
+  if (!GetWidget()) {
+    return;
+  }
+
+  auto arrow =
+      Shelf::ForWindow(GetWidget()->GetNativeWindow())
+          ->SelectValueForShelfAlignment(views::BubbleBorder::BOTTOM_CENTER,
+                                         views::BubbleBorder::LEFT_CENTER,
+                                         views::BubbleBorder::RIGHT_CENTER);
+  SetArrow(arrow);
+}
+
 BEGIN_METADATA(AnchoredNudge, views::BubbleDialogDelegateView)
 END_METADATA
 
diff --git a/ash/system/toast/anchored_nudge.h b/ash/system/toast/anchored_nudge.h
index 45c9bca778f3b..8da91e5a66289 100644
--- a/ash/system/toast/anchored_nudge.h
+++ b/ash/system/toast/anchored_nudge.h
@@ -9,6 +9,7 @@
 
 #include "ash/ash_export.h"
 #include "ash/public/cpp/system/anchored_nudge_data.h"
+#include "ash/shell_observer.h"
 #include "ui/base/metadata/metadata_header_macros.h"
 #include "ui/views/bubble/bubble_dialog_delegate_view.h"
 
@@ -21,6 +22,10 @@ class GestureEvent;
 class MouseEvent;
 }  // namespace ui
 
+namespace aura {
+class Window;
+}
+
 namespace ash {
 
 class SystemNudgeView;
@@ -28,7 +33,8 @@ class SystemNudgeView;
 // Creates and manages the widget and contents view for an anchored nudge.
 // TODO(b/285988235): `AnchoredNudge` will replace the existing `SystemNudge`
 // and take over its name.
-class ASH_EXPORT AnchoredNudge : public views::BubbleDialogDelegateView {
+class ASH_EXPORT AnchoredNudge : public ShellObserver,
+                                 public views::BubbleDialogDelegateView {
  public:
   METADATA_HEADER(AnchoredNudge);
 
@@ -49,17 +55,28 @@ class ASH_EXPORT AnchoredNudge : public views::BubbleDialogDelegateView {
       views::Widget* widget) override;
 
   // views::View:
+  void AddedToWidget() override;
   bool OnMousePressed(const ui::MouseEvent& event) override;
   bool OnMouseDragged(const ui::MouseEvent& event) override;
   void OnMouseReleased(const ui::MouseEvent& event) override;
   void OnGestureEvent(ui::GestureEvent* event) override;
 
+  // ShellObserver:
+  void OnShelfAlignmentChanged(aura::Window* root_window,
+                               ShelfAlignment old_alignment) override;
+
+  // Sets the arrow of the nudge based on the current shelf's alignment.
+  void SetArrowFromShelf();
+
   const std::string& id() { return id_; }
 
  private:
   // Unique id used to find and dismiss the nudge through the manager.
   const std::string id_;
 
+  // Whether the nudge should set its arrow based on shelf alignment.
+  const bool anchored_to_shelf_;
+
   // Owned by the views hierarchy. Contents view of the anchored nudge.
   raw_ptr<SystemNudgeView> system_nudge_view_ = nullptr;
 
diff --git a/ash/system/toast/anchored_nudge_manager_impl_unittest.cc b/ash/system/toast/anchored_nudge_manager_impl_unittest.cc
index e7431df5725c7..ef79a01cb2897 100644
--- a/ash/system/toast/anchored_nudge_manager_impl_unittest.cc
+++ b/ash/system/toast/anchored_nudge_manager_impl_unittest.cc
@@ -18,6 +18,7 @@
 #include "base/test/metrics/histogram_tester.h"
 #include "base/test/scoped_feature_list.h"
 #include "base/test/task_environment.h"
+#include "ui/views/bubble/bubble_border.h"
 #include "ui/views/controls/label.h"
 #include "ui/views/widget/widget.h"
 
@@ -297,6 +298,80 @@ TEST_F(AnchoredNudgeManagerImplTest, ShowNudge_AnchorViewWithoutWidget) {
   EXPECT_FALSE(GetShownNudges()[id]);
 }
 
+// Tests that a nudge sets the appropriate arrow when it's set to be anchored to
+// the shelf, and updates its arrow whenever the shelf alignment changes.
+TEST_F(AnchoredNudgeManagerImplTest, NudgeAnchoredToShelf) {
+  std::unique_ptr<views::Widget> widget = CreateFramelessTestWidget();
+
+  // Set up nudge data contents.
+  const std::string id = "id";
+  auto* anchor_view = widget->SetContentsView(std::make_unique<views::View>());
+  auto nudge_data = CreateBaseNudgeData(id, anchor_view);
+
+  // Make the nudge set its arrow based on the shelf's position.
+  nudge_data.anchored_to_shelf = true;
+
+  // Set shelf alignment to the left.
+  Shelf* shelf = GetPrimaryShelf();
+  EXPECT_EQ(ShelfAlignment::kBottom, shelf->alignment());
+  EXPECT_EQ(SHELF_VISIBLE, shelf->GetVisibilityState());
+  shelf->SetAlignment(ShelfAlignment::kLeft);
+
+  // Show a nudge, expect its arrow to be aligned with left shelf.
+  anchored_nudge_manager()->Show(nudge_data);
+  EXPECT_TRUE(GetShownNudges()[id]);
+  EXPECT_EQ(views::BubbleBorder::Arrow::LEFT_CENTER,
+            GetShownNudges()[id]->arrow());
+
+  // Cancel the nudge, and show a new nudge with bottom shelf alignment.
+  anchored_nudge_manager()->Cancel(id);
+  shelf->SetAlignment(ShelfAlignment::kBottom);
+  anchored_nudge_manager()->Show(nudge_data);
+  EXPECT_EQ(views::BubbleBorder::Arrow::BOTTOM_CENTER,
+            GetShownNudges()[id]->arrow());
+
+  // Change the shelf alignment to the right while the nudge is still open,
+  // nudge arrow should be updated.
+  shelf->SetAlignment(ShelfAlignment::kRight);
+  EXPECT_EQ(views::BubbleBorder::Arrow::RIGHT_CENTER,
+            GetShownNudges()[id]->arrow());
+}
+
+// Tests that a nudge that is anchored to the shelf is not affected by shelf
+// alignment changes of a display where the nudge does not exist.
+TEST_F(AnchoredNudgeManagerImplTest,
+       NudgeAnchoredToShelf_WithASecondaryDisplay) {
+  // Add a secondary display.
+  UpdateDisplay("800x700,800x700");
+  RootWindowController* const secondary_root_window_controller =
+      Shell::GetRootWindowControllerWithDisplayId(GetSecondaryDisplay().id());
+  Shelf* shelf = GetPrimaryShelf();
+
+  // Set up nudge data contents.
+  const std::string id = "id";
+  auto* anchor_view = shelf->status_area_widget()->unified_system_tray();
+  auto nudge_data = CreateBaseNudgeData(id, anchor_view);
+
+  // Make the nudge set its arrow based on the shelf's position.
+  nudge_data.anchored_to_shelf = true;
+
+  // Set shelf alignment to the left.
+  shelf->SetAlignment(ShelfAlignment::kLeft);
+
+  // Show a nudge, expect its arrow to be aligned with left shelf.
+  anchored_nudge_manager()->Show(nudge_data);
+  EXPECT_TRUE(GetShownNudges()[id]);
+  EXPECT_EQ(views::BubbleBorder::Arrow::LEFT_CENTER,
+            GetShownNudges()[id]->arrow());
+
+  // Test that changing the shelf alignment on the secondary display does not
+  // affect the nudge's arrow, since the nudge lives in the primary display.
+  secondary_root_window_controller->shelf()->SetAlignment(
+      ShelfAlignment::kBottom);
+  EXPECT_EQ(views::BubbleBorder::Arrow::LEFT_CENTER,
+            GetShownNudges()[id]->arrow());
+}
+
 // Tests that a nudge closes if its anchor view is made invisible.
 TEST_F(AnchoredNudgeManagerImplTest, NudgeCloses_WhenAnchorViewIsHiding) {
   std::unique_ptr<views::Widget> widget = CreateFramelessTestWidget();
diff --git a/ash/system/video_conference/video_conference_tray_controller.cc b/ash/system/video_conference/video_conference_tray_controller.cc
index 4064d96cfa5ee..e39b313857a6c 100644
--- a/ash/system/video_conference/video_conference_tray_controller.cc
+++ b/ash/system/video_conference/video_conference_tray_controller.cc
@@ -213,6 +213,7 @@ void VideoConferenceTrayController::MaybeShowSpeakOnMuteOptInNudge(
       weak_ptr_factory_.GetWeakPtr());
 
   nudge_data.has_infinite_duration = true;
+  nudge_data.anchored_to_shelf = true;
 
   AnchoredNudgeManager::Get()->Show(nudge_data);
 
@@ -479,6 +480,7 @@ void VideoConferenceTrayController::OnSpeakOnMuteDetected() {
           ->client()
           ->ShowSpeakOnMuteDetectionSettings();
     });
+    nudge_data.anchored_to_shelf = true;
     AnchoredNudgeManager::Get()->Show(nudge_data);
 
     last_speak_on_mute_notification_time_.emplace(current_time);
@@ -655,6 +657,7 @@ void VideoConferenceTrayController::HandleDeviceUsedWhileDisabled(
   AnchoredNudgeData nudge_data(
       nudge_id, catalog_name,
       l10n_util::GetStringFUTF16(text_id, app_name, device_name), anchor_view);
+  nudge_data.anchored_to_shelf = true;
   AnchoredNudgeManager::Get()->Show(nudge_data);
 }