diff --git a/commands/msg/mark.go b/commands/msg/mark.go
index 13d6c92..939d456 100644
--- a/commands/msg/mark.go
+++ b/commands/msg/mark.go
@@ -32,19 +32,23 @@ func (Mark) Execute(aerc *widgets.Aerc, args []string) error {
 		return err
 	}
 	marker := store.Marker()
-	opts, _, err := getopt.Getopts(args, "atv")
+	opts, _, err := getopt.Getopts(args, "atvV")
 	if err != nil {
 		return err
 	}
 	var all bool
 	var toggle bool
 	var visual bool
+	var clearVisual bool
 	for _, opt := range opts {
 		switch opt.Option {
 		case 'a':
 			all = true
 		case 'v':
 			visual = true
+			clearVisual = true
+		case 'V':
+			visual = true
 		case 't':
 			toggle = true
 		}
@@ -70,7 +74,7 @@ func (Mark) Execute(aerc *widgets.Aerc, args []string) error {
 			}
 			return nil
 		case visual:
-			marker.ToggleVisualMark()
+			marker.ToggleVisualMark(clearVisual)
 			return nil
 		default:
 			modFunc(selected.Uid)
diff --git a/doc/aerc.1.scd b/doc/aerc.1.scd
index 17e25c2..b5edc32 100644
--- a/doc/aerc.1.scd
+++ b/doc/aerc.1.scd
@@ -412,6 +412,8 @@ message list, the message in the message viewer, etc).
 
 	*-v*: Enter / leave visual mark mode
 
+	*-V*: Same as -v but does not clear existing selection
+
 *unmark* [-at]
 	Unmarks messages. The flags below can be combined as needed.
 
diff --git a/lib/marker/marker.go b/lib/marker/marker.go
index 46400f1..21c151f 100644
--- a/lib/marker/marker.go
+++ b/lib/marker/marker.go
@@ -8,7 +8,7 @@ type Marker interface {
 	Remark()
 	Marked() []uint32
 	IsMarked(uint32) bool
-	ToggleVisualMark()
+	ToggleVisualMark(bool)
 	UpdateVisualMark()
 	ClearVisualMark()
 }
@@ -25,6 +25,7 @@ type controller struct {
 	lastMarked     map[uint32]struct{}
 	visualStartUID uint32
 	visualMarkMode bool
+	visualBase     map[uint32]struct{}
 }
 
 // New returns a new Marker
@@ -114,15 +115,21 @@ func (mc *controller) Marked() []uint32 {
 }
 
 // ToggleVisualMark enters or leaves the visual marking mode
-func (mc *controller) ToggleVisualMark() {
+func (mc *controller) ToggleVisualMark(clear bool) {
 	mc.visualMarkMode = !mc.visualMarkMode
 	if mc.visualMarkMode {
 		// just entered visual mode, reset whatever marking was already done
-		mc.resetMark()
+		if clear {
+			mc.resetMark()
+		}
 		uids := mc.uidProvider.Uids()
 		if idx := mc.uidProvider.SelectedIndex(); idx >= 0 && idx < len(uids) {
 			mc.visualStartUID = uids[idx]
 			mc.marked[mc.visualStartUID] = struct{}{}
+			mc.visualBase = make(map[uint32]struct{})
+			for key, value := range mc.marked {
+				mc.visualBase[key] = value
+			}
 		}
 	}
 }
@@ -160,7 +167,10 @@ func (mc *controller) UpdateVisualMark() {
 	} else {
 		visUids = uids[selectedIdx : startIdx+1]
 	}
-	mc.resetMark()
+	mc.marked = make(map[uint32]struct{})
+	for uid := range mc.visualBase {
+		mc.marked[uid] = struct{}{}
+	}
 	for _, uid := range visUids {
 		mc.marked[uid] = struct{}{}
 	}
diff --git a/lib/marker/marker_test.go b/lib/marker/marker_test.go
index 1611623..df9eb2a 100644
--- a/lib/marker/marker_test.go
+++ b/lib/marker/marker_test.go
@@ -90,7 +90,7 @@ func TestMarker_VisualMode(t *testing.T) {
 	m, up := createMarker()
 
 	// activate visual mode
-	m.ToggleVisualMark()
+	m.ToggleVisualMark(false)
 
 	// marking should now fail silently because we're in visual mode
 	m.Mark(1)