diff --git a/assets/icons/ZulipIcons.ttf b/assets/icons/ZulipIcons.ttf
index fec63cceaf..edfb88410b 100644
Binary files a/assets/icons/ZulipIcons.ttf and b/assets/icons/ZulipIcons.ttf differ
diff --git a/assets/icons/message_checked.svg b/assets/icons/message_checked.svg
new file mode 100644
index 0000000000..5c598ae87e
--- /dev/null
+++ b/assets/icons/message_checked.svg
@@ -0,0 +1,4 @@
+
diff --git a/assets/l10n/app_en.arb b/assets/l10n/app_en.arb
index ec0de4dd8f..b0c9404bc8 100644
--- a/assets/l10n/app_en.arb
+++ b/assets/l10n/app_en.arb
@@ -136,6 +136,10 @@
   "@actionSheetOptionUnstarMessage":  {
     "description": "Label for unstar button on action sheet."
   },
+  "actionSheetOptionMarkTopicAsRead": "Mark topic as read",
+  "@actionSheetOptionMarkTopicAsRead": {
+    "description": "Option to mark a specific topic as read in the action sheet."
+  },
   "errorWebAuthOperationalErrorTitle": "Something went wrong",
   "@errorWebAuthOperationalErrorTitle": {
     "description": "Error title when third-party authentication has an operational error (not necessarily caused by invalid credentials)."
diff --git a/lib/generated/l10n/zulip_localizations.dart b/lib/generated/l10n/zulip_localizations.dart
index 1b339ce823..38fa4c6a1d 100644
--- a/lib/generated/l10n/zulip_localizations.dart
+++ b/lib/generated/l10n/zulip_localizations.dart
@@ -309,6 +309,12 @@ abstract class ZulipLocalizations {
   /// **'Unstar message'**
   String get actionSheetOptionUnstarMessage;
 
+  /// Option to mark a specific topic as read in the action sheet.
+  ///
+  /// In en, this message translates to:
+  /// **'Mark topic as read'**
+  String get actionSheetOptionMarkTopicAsRead;
+
   /// Error title when third-party authentication has an operational error (not necessarily caused by invalid credentials).
   ///
   /// In en, this message translates to:
diff --git a/lib/generated/l10n/zulip_localizations_ar.dart b/lib/generated/l10n/zulip_localizations_ar.dart
index 890bf68596..19967cf591 100644
--- a/lib/generated/l10n/zulip_localizations_ar.dart
+++ b/lib/generated/l10n/zulip_localizations_ar.dart
@@ -112,6 +112,9 @@ class ZulipLocalizationsAr extends ZulipLocalizations {
   @override
   String get actionSheetOptionUnstarMessage => 'Unstar message';
 
+  @override
+  String get actionSheetOptionMarkTopicAsRead => 'Mark topic as read';
+
   @override
   String get errorWebAuthOperationalErrorTitle => 'Something went wrong';
 
diff --git a/lib/generated/l10n/zulip_localizations_en.dart b/lib/generated/l10n/zulip_localizations_en.dart
index 72b7e9ad69..9e34bdd7fe 100644
--- a/lib/generated/l10n/zulip_localizations_en.dart
+++ b/lib/generated/l10n/zulip_localizations_en.dart
@@ -112,6 +112,9 @@ class ZulipLocalizationsEn extends ZulipLocalizations {
   @override
   String get actionSheetOptionUnstarMessage => 'Unstar message';
 
+  @override
+  String get actionSheetOptionMarkTopicAsRead => 'Mark topic as read';
+
   @override
   String get errorWebAuthOperationalErrorTitle => 'Something went wrong';
 
diff --git a/lib/generated/l10n/zulip_localizations_ja.dart b/lib/generated/l10n/zulip_localizations_ja.dart
index 5bfacf60d9..d9d90a8ad0 100644
--- a/lib/generated/l10n/zulip_localizations_ja.dart
+++ b/lib/generated/l10n/zulip_localizations_ja.dart
@@ -112,6 +112,9 @@ class ZulipLocalizationsJa extends ZulipLocalizations {
   @override
   String get actionSheetOptionUnstarMessage => 'Unstar message';
 
+  @override
+  String get actionSheetOptionMarkTopicAsRead => 'Mark topic as read';
+
   @override
   String get errorWebAuthOperationalErrorTitle => 'Something went wrong';
 
diff --git a/lib/generated/l10n/zulip_localizations_nb.dart b/lib/generated/l10n/zulip_localizations_nb.dart
index 9698d16c96..28cb2769e8 100644
--- a/lib/generated/l10n/zulip_localizations_nb.dart
+++ b/lib/generated/l10n/zulip_localizations_nb.dart
@@ -112,6 +112,9 @@ class ZulipLocalizationsNb extends ZulipLocalizations {
   @override
   String get actionSheetOptionUnstarMessage => 'Unstar message';
 
+  @override
+  String get actionSheetOptionMarkTopicAsRead => 'Mark topic as read';
+
   @override
   String get errorWebAuthOperationalErrorTitle => 'Something went wrong';
 
diff --git a/lib/generated/l10n/zulip_localizations_pl.dart b/lib/generated/l10n/zulip_localizations_pl.dart
index 01bee756da..2eaadcd704 100644
--- a/lib/generated/l10n/zulip_localizations_pl.dart
+++ b/lib/generated/l10n/zulip_localizations_pl.dart
@@ -112,6 +112,9 @@ class ZulipLocalizationsPl extends ZulipLocalizations {
   @override
   String get actionSheetOptionUnstarMessage => 'Odbierz gwiazdkę';
 
+  @override
+  String get actionSheetOptionMarkTopicAsRead => 'Mark topic as read';
+
   @override
   String get errorWebAuthOperationalErrorTitle => 'Coś poszło nie tak';
 
diff --git a/lib/generated/l10n/zulip_localizations_ru.dart b/lib/generated/l10n/zulip_localizations_ru.dart
index 2c01dddb61..061c3f29f3 100644
--- a/lib/generated/l10n/zulip_localizations_ru.dart
+++ b/lib/generated/l10n/zulip_localizations_ru.dart
@@ -112,6 +112,9 @@ class ZulipLocalizationsRu extends ZulipLocalizations {
   @override
   String get actionSheetOptionUnstarMessage => 'Снять отметку с сообщения';
 
+  @override
+  String get actionSheetOptionMarkTopicAsRead => 'Mark topic as read';
+
   @override
   String get errorWebAuthOperationalErrorTitle => 'Что-то пошло не так';
 
diff --git a/lib/generated/l10n/zulip_localizations_sk.dart b/lib/generated/l10n/zulip_localizations_sk.dart
index a69cfc2d8a..7378638f4a 100644
--- a/lib/generated/l10n/zulip_localizations_sk.dart
+++ b/lib/generated/l10n/zulip_localizations_sk.dart
@@ -112,6 +112,9 @@ class ZulipLocalizationsSk extends ZulipLocalizations {
   @override
   String get actionSheetOptionUnstarMessage => 'Odhviezdičkovať správu';
 
+  @override
+  String get actionSheetOptionMarkTopicAsRead => 'Mark topic as read';
+
   @override
   String get errorWebAuthOperationalErrorTitle => 'Niečo sa pokazilo';
 
diff --git a/lib/widgets/action_sheet.dart b/lib/widgets/action_sheet.dart
index aa81461421..c5068384f1 100644
--- a/lib/widgets/action_sheet.dart
+++ b/lib/widgets/action_sheet.dart
@@ -255,6 +255,14 @@ void showTopicActionSheet(BuildContext context, {
       someMessageIdInTopic: someMessageIdInTopic));
   }
 
+  final unreadCount = store.unreads.countInTopicNarrow(channelId, topic);
+  if (unreadCount > 0) {
+    optionButtons.add(MarkTopicAsReadButton(
+      channelId: channelId,
+      topic: topic,
+      pageContext: context));
+  }
+
   if (optionButtons.isEmpty) {
     // TODO(a11y): This case makes a no-op gesture handler; as a consequence,
     //   we're presenting some UI (to people who use screen-reader software) as
@@ -461,6 +469,29 @@ class ResolveUnresolveButton extends ActionSheetMenuItemButton {
   }
 }
 
+class MarkTopicAsReadButton extends ActionSheetMenuItemButton {
+  const MarkTopicAsReadButton({
+    super.key,
+    required this.channelId,
+    required this.topic,
+    required super.pageContext,
+  });
+
+  final int channelId;
+  final TopicName topic;
+
+  @override IconData get icon => ZulipIcons.message_checked;
+
+  @override
+  String label(ZulipLocalizations zulipLocalizations) {
+    return zulipLocalizations.actionSheetOptionMarkTopicAsRead;
+  }
+
+  @override void onPressed() async {
+    await ZulipAction.markNarrowAsRead(pageContext, TopicNarrow(channelId, topic));
+  }
+}
+
 /// Show a sheet of actions you can take on a message in the message list.
 ///
 /// Must have a [MessageListPage] ancestor.
diff --git a/lib/widgets/icons.dart b/lib/widgets/icons.dart
index 6c6934ebd7..da50c70665 100644
--- a/lib/widgets/icons.dart
+++ b/lib/widgets/icons.dart
@@ -102,44 +102,47 @@ abstract final class ZulipIcons {
   /// The Zulip custom icon "menu".
   static const IconData menu = IconData(0xf11a, fontFamily: "Zulip Icons");
 
+  /// The Zulip custom icon "message_checked".
+  static const IconData message_checked = IconData(0xf11b, fontFamily: "Zulip Icons");
+
   /// The Zulip custom icon "message_feed".
-  static const IconData message_feed = IconData(0xf11b, fontFamily: "Zulip Icons");
+  static const IconData message_feed = IconData(0xf11c, fontFamily: "Zulip Icons");
 
   /// The Zulip custom icon "mute".
-  static const IconData mute = IconData(0xf11c, fontFamily: "Zulip Icons");
+  static const IconData mute = IconData(0xf11d, fontFamily: "Zulip Icons");
 
   /// The Zulip custom icon "read_receipts".
-  static const IconData read_receipts = IconData(0xf11d, fontFamily: "Zulip Icons");
+  static const IconData read_receipts = IconData(0xf11e, fontFamily: "Zulip Icons");
 
   /// The Zulip custom icon "send".
-  static const IconData send = IconData(0xf11e, fontFamily: "Zulip Icons");
+  static const IconData send = IconData(0xf11f, fontFamily: "Zulip Icons");
 
   /// The Zulip custom icon "share".
-  static const IconData share = IconData(0xf11f, fontFamily: "Zulip Icons");
+  static const IconData share = IconData(0xf120, fontFamily: "Zulip Icons");
 
   /// The Zulip custom icon "share_ios".
-  static const IconData share_ios = IconData(0xf120, fontFamily: "Zulip Icons");
+  static const IconData share_ios = IconData(0xf121, fontFamily: "Zulip Icons");
 
   /// The Zulip custom icon "smile".
-  static const IconData smile = IconData(0xf121, fontFamily: "Zulip Icons");
+  static const IconData smile = IconData(0xf122, fontFamily: "Zulip Icons");
 
   /// The Zulip custom icon "star".
-  static const IconData star = IconData(0xf122, fontFamily: "Zulip Icons");
+  static const IconData star = IconData(0xf123, fontFamily: "Zulip Icons");
 
   /// The Zulip custom icon "star_filled".
-  static const IconData star_filled = IconData(0xf123, fontFamily: "Zulip Icons");
+  static const IconData star_filled = IconData(0xf124, fontFamily: "Zulip Icons");
 
   /// The Zulip custom icon "three_person".
-  static const IconData three_person = IconData(0xf124, fontFamily: "Zulip Icons");
+  static const IconData three_person = IconData(0xf125, fontFamily: "Zulip Icons");
 
   /// The Zulip custom icon "topic".
-  static const IconData topic = IconData(0xf125, fontFamily: "Zulip Icons");
+  static const IconData topic = IconData(0xf126, fontFamily: "Zulip Icons");
 
   /// The Zulip custom icon "unmute".
-  static const IconData unmute = IconData(0xf126, fontFamily: "Zulip Icons");
+  static const IconData unmute = IconData(0xf127, fontFamily: "Zulip Icons");
 
   /// The Zulip custom icon "user".
-  static const IconData user = IconData(0xf127, fontFamily: "Zulip Icons");
+  static const IconData user = IconData(0xf128, fontFamily: "Zulip Icons");
 
   // END GENERATED ICON DATA
 }
diff --git a/lib/widgets/message_list.dart b/lib/widgets/message_list.dart
index b8557162e9..681d9cae61 100644
--- a/lib/widgets/message_list.dart
+++ b/lib/widgets/message_list.dart
@@ -829,7 +829,7 @@ class _MarkAsReadWidgetState extends State {
                 backgroundColor: WidgetStatePropertyAll(messageListTheme.unreadMarker),
               ),
               onPressed: _loading ? null : () => _handlePress(context),
-              icon: const Icon(Icons.playlist_add_check),
+              icon: const Icon(ZulipIcons.message_checked),
               label: Text(zulipLocalizations.markAllAsReadLabel))))));
   }
 }
diff --git a/test/widgets/action_sheet_test.dart b/test/widgets/action_sheet_test.dart
index 5a4c22c604..94ddd64cc3 100644
--- a/test/widgets/action_sheet_test.dart
+++ b/test/widgets/action_sheet_test.dart
@@ -10,6 +10,7 @@ import 'package:http/http.dart' as http;
 import 'package:zulip/api/model/events.dart';
 import 'package:zulip/api/model/initial_snapshot.dart';
 import 'package:zulip/api/model/model.dart';
+import 'package:zulip/api/model/narrow.dart';
 import 'package:zulip/api/route/channels.dart';
 import 'package:zulip/api/route/messages.dart';
 import 'package:zulip/model/binding.dart';
@@ -562,6 +563,50 @@ void main() {
           expectedTitle: 'Failed to mark topic as unresolved');
       });
     });
+
+    group('MarkTopicAsReadButton', () {
+      testWidgets('visible if topic has unread messages', (tester) async {
+        await prepare();
+        final message = eg.streamMessage(stream: someChannel, topic: someTopic,
+          flags: []);
+        await store.addMessage(message);
+        await showFromAppBar(tester, messages: [message]);
+        check(find.text('Mark topic as read')).findsOne();
+      });
+
+      testWidgets('not visible if topic has no unread messages', (tester) async {
+        await prepare();
+        final message = eg.streamMessage(stream: someChannel, topic: someTopic,
+          flags: [MessageFlag.read]);
+        await store.addMessage(message);
+        await showFromAppBar(tester, messages: [message]);
+        check(find.text('Mark topic as read')).findsNothing();
+      });
+
+      testWidgets('marks topic as read when pressed', (tester) async {
+        await prepare();
+        final message = eg.streamMessage(stream: someChannel, topic: someTopic,
+          flags: []);
+        await store.addMessage(message);
+        await showFromAppBar(tester, messages: [message]);
+
+        connection.prepare(json: UpdateMessageFlagsForNarrowResult(
+          processedCount: 1, updatedCount: 1,
+          firstProcessedId: message.id, lastProcessedId: message.id,
+          foundOldest: true, foundNewest: true).toJson());
+        await tester.tap(find.text('Mark topic as read'));
+        await tester.pumpAndSettle();
+
+        check(connection.lastRequest).isA()
+          ..url.path.equals('/api/v1/messages/flags/narrow')
+          ..bodyFields['narrow'].equals(jsonEncode([
+              ...eg.topicNarrow(someChannel.streamId, someTopic).apiEncode(),
+              ApiNarrowIs(IsOperand.unread),
+            ]))
+          ..bodyFields['op'].equals('add')
+          ..bodyFields['flag'].equals('read');
+      });
+    });
   });
 
   group('message action sheet', () {