diff --git a/assets/voxygen/element/ui/bag/buttons/swap_equipped_weapons.png b/assets/voxygen/element/ui/bag/buttons/swap_equipped_weapons.png
new file mode 100644
index 0000000000..8c718d8b72
--- /dev/null
+++ b/assets/voxygen/element/ui/bag/buttons/swap_equipped_weapons.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:75e7cf295ea4ba4f8b80fab7498fb7106c142c1b100af284ee14f7e126df5a86
+size 856
diff --git a/assets/voxygen/element/ui/bag/buttons/swap_equipped_weapons_hover.png b/assets/voxygen/element/ui/bag/buttons/swap_equipped_weapons_hover.png
new file mode 100644
index 0000000000..974d1382f5
--- /dev/null
+++ b/assets/voxygen/element/ui/bag/buttons/swap_equipped_weapons_hover.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:9fc5bd3785f311e832ebb744751fd477ccbbc8df8cc7679a9e31e6e9ee775e3f
+size 877
diff --git a/assets/voxygen/element/ui/bag/buttons/swap_equipped_weapons_press.png b/assets/voxygen/element/ui/bag/buttons/swap_equipped_weapons_press.png
new file mode 100644
index 0000000000..186e315bae
--- /dev/null
+++ b/assets/voxygen/element/ui/bag/buttons/swap_equipped_weapons_press.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:94548ae5b84e4a9e9d0b25ad1f558f08787c3d93d40790af0c51f37d6bfc6ffa
+size 877
diff --git a/assets/voxygen/i18n/en/hud/bag.ron b/assets/voxygen/i18n/en/hud/bag.ron
index e10c54ddcd..b8a0271369 100644
--- a/assets/voxygen/i18n/en/hud/bag.ron
+++ b/assets/voxygen/i18n/en/hud/bag.ron
@@ -24,6 +24,10 @@
         "hud.bag.feet": "Feet",
         "hud.bag.mainhand": "Mainhand",
         "hud.bag.offhand": "Offhand",
+        "hud.bag.inactive_mainhand": "Inactive Mainhand",
+        "hud.bag.inactive_offhand": "Inactive Offhand",
+        "hud.bag.swap_equipped_weapons_title": "Swap equipped weapons",
+        "hud.bag.swap_equipped_weapons_desc": "Press {key}",
         "hud.bag.bag": "Bag",
         "hud.bag.health": "Health",
         "hud.bag.stamina": "Stamina",
diff --git a/common/src/combat.rs b/common/src/combat.rs
index d57e219e96..4755f3d170 100644
--- a/common/src/combat.rs
+++ b/common/src/combat.rs
@@ -805,8 +805,8 @@ fn equipped_item_and_tool(inv: &Inventory, slot: EquipSlot) -> Option<(&Item, &T
 #[cfg(not(target_arch = "wasm32"))]
 pub fn get_weapons(inv: &Inventory) -> (Option<ToolKind>, Option<ToolKind>) {
     (
-        equipped_item_and_tool(inv, EquipSlot::Mainhand).map(|(_, tool)| tool.kind),
-        equipped_item_and_tool(inv, EquipSlot::Offhand).map(|(_, tool)| tool.kind),
+        equipped_item_and_tool(inv, EquipSlot::ActiveMainhand).map(|(_, tool)| tool.kind),
+        equipped_item_and_tool(inv, EquipSlot::ActiveOffhand).map(|(_, tool)| tool.kind),
     )
 }
 
@@ -845,14 +845,14 @@ fn weapon_skills(inventory: &Inventory, skill_set: &SkillSet) -> f32 {
 
 fn get_weapon_rating(inventory: &Inventory, msm: &MaterialStatManifest) -> f32 {
     let mainhand_rating =
-        if let Some((item, _)) = equipped_item_and_tool(inventory, EquipSlot::Mainhand) {
+        if let Some((item, _)) = equipped_item_and_tool(inventory, EquipSlot::ActiveMainhand) {
             weapon_rating(item, msm)
         } else {
             0.0
         };
 
     let offhand_rating =
-        if let Some((item, _)) = equipped_item_and_tool(inventory, EquipSlot::Offhand) {
+        if let Some((item, _)) = equipped_item_and_tool(inventory, EquipSlot::ActiveOffhand) {
             weapon_rating(item, msm)
         } else {
             0.0
diff --git a/common/src/comp/inventory/loadout.rs b/common/src/comp/inventory/loadout.rs
index e095e401e2..fbcc81d0c6 100644
--- a/common/src/comp/inventory/loadout.rs
+++ b/common/src/comp/inventory/loadout.rs
@@ -1,6 +1,6 @@
 use crate::comp::{
     inventory::{
-        item::ItemKind,
+        item::{Hands, ItemKind, Tool},
         slot::{ArmorSlot, EquipSlot},
         InvSlot,
     },
@@ -75,8 +75,10 @@ impl Loadout {
                 (EquipSlot::Armor(ArmorSlot::Bag2), "bag2".to_string()),
                 (EquipSlot::Armor(ArmorSlot::Bag3), "bag3".to_string()),
                 (EquipSlot::Armor(ArmorSlot::Bag4), "bag4".to_string()),
-                (EquipSlot::Mainhand, "active_item".to_string()),
-                (EquipSlot::Offhand, "second_item".to_string()),
+                (EquipSlot::ActiveMainhand, "active_mainhand".to_string()),
+                (EquipSlot::ActiveOffhand, "active_offhand".to_string()),
+                (EquipSlot::InactiveMainhand, "inactive_mainhand".to_string()),
+                (EquipSlot::InactiveOffhand, "inactive_offhand".to_string()),
             ]
             .into_iter()
             .map(|(equip_slot, persistence_key)| LoadoutSlot::new(equip_slot, persistence_key))
@@ -166,23 +168,17 @@ impl Loadout {
         }
 
         let item_a = self.swap(equip_slot_a, None);
-        let item_b = self.swap(equip_slot_b, None);
+        let item_b = self.swap(equip_slot_b, item_a);
+        assert_eq!(self.swap(equip_slot_a, item_b), None);
 
-        // Check if items can go in the other slots
-        if item_a
-            .as_ref()
-            .map_or(true, |i| equip_slot_b.can_hold(&i.kind()))
-            && item_b
-                .as_ref()
-                .map_or(true, |i| equip_slot_a.can_hold(&i.kind()))
+        // Check if items are valid in their new positions
+        if !self.slot_can_hold(equip_slot_a, self.equipped(equip_slot_b).map(|x| x.kind()))
+            || !self.slot_can_hold(equip_slot_b, self.equipped(equip_slot_a).map(|x| x.kind()))
         {
-            // Swap
-            self.swap(equip_slot_b, item_a).unwrap_none();
-            self.swap(equip_slot_a, item_b).unwrap_none();
-        } else {
-            // Otherwise put the items back
-            self.swap(equip_slot_a, item_a).unwrap_none();
-            self.swap(equip_slot_b, item_b).unwrap_none();
+            // If not, revert the swap
+            let item_a = self.swap(equip_slot_a, None);
+            let item_b = self.swap(equip_slot_b, item_a);
+            assert_eq!(self.swap(equip_slot_a, item_b), None);
         }
     }
 
@@ -195,7 +191,7 @@ impl Loadout {
         let mut suitable_slots = self
             .slots
             .iter()
-            .filter(|s| s.equip_slot.can_hold(item_kind));
+            .filter(|s| self.slot_can_hold(s.equip_slot, Some(item_kind)));
 
         let first = suitable_slots.next();
 
@@ -215,7 +211,7 @@ impl Loadout {
     ) -> impl Iterator<Item = &Item> {
         self.slots
             .iter()
-            .filter(move |s| s.equip_slot.can_hold(&item_kind))
+            .filter(move |s| self.slot_can_hold(s.equip_slot, Some(&item_kind)))
             .filter_map(|s| s.slot.as_ref())
     }
 
@@ -264,7 +260,7 @@ impl Loadout {
     pub(super) fn inv_slots_mut(&mut self) -> impl Iterator<Item = &mut InvSlot> {
         self.slots.iter_mut()
             .filter_map(|x| x.slot.as_mut().map(|item| item.slots_mut()))  // Discard loadout items that have no slots of their own
-            .flat_map(|loadout_slots| loadout_slots.iter_mut()) //Collapse iter of Vec<InvSlot> to iter of InvSlot 
+            .flat_map(|loadout_slots| loadout_slots.iter_mut()) //Collapse iter of Vec<InvSlot> to iter of InvSlot
     }
 
     /// Gets the range of loadout-provided inventory slot indexes that are
@@ -294,12 +290,17 @@ impl Loadout {
     /// If no slot is available the item is returned.
     #[must_use = "Returned item will be lost if not used"]
     pub(super) fn try_equip(&mut self, item: Item) -> Result<(), Item> {
-        if let Some(loadout_slot) = self
+        let loadout_slot = self
+            .slots
+            .iter()
+            .find(|s| s.slot.is_none() && self.slot_can_hold(s.equip_slot, Some(item.kind())))
+            .map(|s| s.equip_slot);
+        if let Some(slot) = self
             .slots
             .iter_mut()
-            .find(|s| s.slot.is_none() && s.equip_slot.can_hold(item.kind()))
+            .find(|s| Some(s.equip_slot) == loadout_slot)
         {
-            loadout_slot.slot = Some(item);
+            slot.slot = Some(item);
             Ok(())
         } else {
             Err(item)
@@ -309,6 +310,90 @@ impl Loadout {
     pub(super) fn items(&self) -> impl Iterator<Item = &Item> {
         self.slots.iter().filter_map(|x| x.slot.as_ref())
     }
+
+    /// Checks that a slot can hold a given item
+    pub(super) fn slot_can_hold(
+        &self,
+        equip_slot: EquipSlot,
+        item_kind: Option<&ItemKind>,
+    ) -> bool {
+        // Disallow equipping incompatible weapon pairs (i.e a two-handed weapon and a
+        // one-handed weapon)
+        if !(match equip_slot {
+            EquipSlot::ActiveMainhand => Loadout::is_valid_weapon_pair(
+                item_kind,
+                self.equipped(EquipSlot::ActiveOffhand).map(|x| &x.kind),
+            ),
+            EquipSlot::ActiveOffhand => Loadout::is_valid_weapon_pair(
+                self.equipped(EquipSlot::ActiveMainhand).map(|x| &x.kind),
+                item_kind,
+            ),
+            EquipSlot::InactiveMainhand => Loadout::is_valid_weapon_pair(
+                item_kind,
+                self.equipped(EquipSlot::InactiveOffhand).map(|x| &x.kind),
+            ),
+            EquipSlot::InactiveOffhand => Loadout::is_valid_weapon_pair(
+                self.equipped(EquipSlot::InactiveMainhand).map(|x| &x.kind),
+                item_kind,
+            ),
+            _ => true,
+        }) {
+            return false;
+        }
+
+        item_kind.map_or(true, |item_kind| equip_slot.can_hold(item_kind))
+    }
+
+    #[rustfmt::skip]
+    fn is_valid_weapon_pair(main_hand: Option<&ItemKind>, off_hand: Option<&ItemKind>) -> bool {
+        matches!((main_hand, off_hand),
+            (Some(ItemKind::Tool(Tool { hands: Hands::One, .. })), None) |
+            (Some(ItemKind::Tool(Tool { hands: Hands::Two, .. })), None) |
+            (Some(ItemKind::Tool(Tool { hands: Hands::One, .. })), Some(ItemKind::Tool(Tool { hands: Hands::One, .. }))) |
+            (None, None))
+    }
+
+    pub(super) fn swap_equipped_weapons(&mut self) {
+        // Checks if a given slot can hold an item right now, defaults to true if
+        // nothing is equipped in slot
+        let valid_slot = |equip_slot| {
+            self.equipped(equip_slot)
+                .map_or(true, |i| self.slot_can_hold(equip_slot, Some(i.kind())))
+        };
+
+        // If every weapon is currently in a valid slot, after this change they will
+        // still be in a valid slot. This is because active mainhand and
+        // inactive mainhand, and active offhand and inactive offhand have the same
+        // requirements on what can be equipped.
+        if valid_slot(EquipSlot::ActiveMainhand)
+            && valid_slot(EquipSlot::ActiveOffhand)
+            && valid_slot(EquipSlot::InactiveMainhand)
+            && valid_slot(EquipSlot::InactiveOffhand)
+        {
+            // Get weapons from each slot
+            let active_mainhand = self.swap(EquipSlot::ActiveMainhand, None);
+            let active_offhand = self.swap(EquipSlot::ActiveOffhand, None);
+            let inactive_mainhand = self.swap(EquipSlot::InactiveMainhand, None);
+            let inactive_offhand = self.swap(EquipSlot::InactiveOffhand, None);
+            // Equip weapons into new slots
+            assert!(
+                self.swap(EquipSlot::ActiveMainhand, inactive_mainhand)
+                    .is_none()
+            );
+            assert!(
+                self.swap(EquipSlot::ActiveOffhand, inactive_offhand)
+                    .is_none()
+            );
+            assert!(
+                self.swap(EquipSlot::InactiveMainhand, active_mainhand)
+                    .is_none()
+            );
+            assert!(
+                self.swap(EquipSlot::InactiveOffhand, active_offhand)
+                    .is_none()
+            );
+        }
+    }
 }
 
 #[cfg(test)]
diff --git a/common/src/comp/inventory/loadout_builder.rs b/common/src/comp/inventory/loadout_builder.rs
index be0c2728c7..030851e417 100644
--- a/common/src/comp/inventory/loadout_builder.rs
+++ b/common/src/comp/inventory/loadout_builder.rs
@@ -30,7 +30,7 @@ use tracing::warn;
 /// // Build a loadout with character starter defaults and a specific sword with default sword abilities
 /// let loadout = LoadoutBuilder::new()
 ///     .defaults()
-///     .active_item(Some(Item::new_from_asset_expect("common.items.weapons.sword.steel-8")))
+///     .active_mainhand(Some(Item::new_from_asset_expect("common.items.weapons.sword.steel-8")))
 ///     .build();
 /// ```
 #[derive(Clone)]
@@ -376,7 +376,7 @@ impl LoadoutBuilder {
             match config {
                 Adlet => match active_tool_kind {
                     Some(ToolKind::Bow) => LoadoutBuilder::new()
-                        .active_item(active_item)
+                        .active_mainhand(active_item)
                         .head(Some(Item::new_from_asset_expect(
                             "common.items.npc_armor.biped_small.adlet.head.adlet_bow",
                         )))
@@ -397,7 +397,7 @@ impl LoadoutBuilder {
                         )))
                         .build(),
                     Some(ToolKind::Spear) | Some(ToolKind::Staff) => LoadoutBuilder::new()
-                        .active_item(active_item)
+                        .active_mainhand(active_item)
                         .head(Some(Item::new_from_asset_expect(
                             "common.items.npc_armor.biped_small.adlet.head.adlet_spear",
                         )))
@@ -417,11 +417,11 @@ impl LoadoutBuilder {
                             "common.items.npc_armor.biped_small.adlet.tail.adlet",
                         )))
                         .build(),
-                    _ => LoadoutBuilder::new().active_item(active_item).build(),
+                    _ => LoadoutBuilder::new().active_mainhand(active_item).build(),
                 },
                 Gnarling => match active_tool_kind {
                     Some(ToolKind::Bow) => LoadoutBuilder::new()
-                        .active_item(active_item)
+                        .active_mainhand(active_item)
                         .head(Some(Item::new_from_asset_expect(
                             "common.items.npc_armor.biped_small.gnarling.head.gnarling",
                         )))
@@ -442,7 +442,7 @@ impl LoadoutBuilder {
                         )))
                         .build(),
                     Some(ToolKind::Staff) => LoadoutBuilder::new()
-                        .active_item(active_item)
+                        .active_mainhand(active_item)
                         .head(Some(Item::new_from_asset_expect(
                             "common.items.npc_armor.biped_small.gnarling.head.gnarling",
                         )))
@@ -463,7 +463,7 @@ impl LoadoutBuilder {
                         )))
                         .build(),
                     Some(ToolKind::Spear) => LoadoutBuilder::new()
-                        .active_item(active_item)
+                        .active_mainhand(active_item)
                         .head(Some(Item::new_from_asset_expect(
                             "common.items.npc_armor.biped_small.gnarling.head.gnarling",
                         )))
@@ -480,10 +480,10 @@ impl LoadoutBuilder {
                             "common.items.npc_armor.biped_small.gnarling.pants.gnarling",
                         )))
                         .build(),
-                    _ => LoadoutBuilder::new().active_item(active_item).build(),
+                    _ => LoadoutBuilder::new().active_mainhand(active_item).build(),
                 },
                 Sahagin => LoadoutBuilder::new()
-                    .active_item(active_item)
+                    .active_mainhand(active_item)
                     .head(Some(Item::new_from_asset_expect(
                         "common.items.npc_armor.biped_small.sahagin.head.sahagin",
                     )))
@@ -504,7 +504,7 @@ impl LoadoutBuilder {
                     )))
                     .build(),
                 Haniwa => LoadoutBuilder::new()
-                    .active_item(active_item)
+                    .active_mainhand(active_item)
                     .head(Some(Item::new_from_asset_expect(
                         "common.items.npc_armor.biped_small.haniwa.head.haniwa",
                     )))
@@ -522,7 +522,7 @@ impl LoadoutBuilder {
                     )))
                     .build(),
                 Myrmidon => LoadoutBuilder::new()
-                    .active_item(active_item)
+                    .active_mainhand(active_item)
                     .head(Some(Item::new_from_asset_expect(
                         "common.items.npc_armor.biped_small.myrmidon.head.myrmidon",
                     )))
@@ -543,7 +543,7 @@ impl LoadoutBuilder {
                     )))
                     .build(),
                 Husk => LoadoutBuilder::new()
-                    .active_item(active_item)
+                    .active_mainhand(active_item)
                     .head(Some(Item::new_from_asset_expect(
                         "common.items.npc_armor.biped_small.husk.head.husk",
                     )))
@@ -561,7 +561,7 @@ impl LoadoutBuilder {
                     )))
                     .build(),
                 Guard => LoadoutBuilder::new()
-                    .active_item(active_item)
+                    .active_mainhand(active_item)
                     .shoulder(Some(Item::new_from_asset_expect(
                         "common.items.armor.leather_plate.shoulder",
                     )))
@@ -703,7 +703,7 @@ impl LoadoutBuilder {
                         }
                     }
                     LoadoutBuilder::new()
-                        .active_item(active_item)
+                        .active_mainhand(active_item)
                         .shoulder(Some(Item::new_from_asset_expect(
                             "common.items.armor.twigsflowers.shoulder",
                         )))
@@ -742,7 +742,7 @@ impl LoadoutBuilder {
                         .build()
                 },
                 Outcast => LoadoutBuilder::new()
-                    .active_item(active_item)
+                    .active_mainhand(active_item)
                     .shoulder(Some(Item::new_from_asset_expect(
                         "common.items.armor.cloth_purple.shoulder",
                     )))
@@ -767,7 +767,7 @@ impl LoadoutBuilder {
                     })
                     .build(),
                 Highwayman => LoadoutBuilder::new()
-                    .active_item(active_item)
+                    .active_mainhand(active_item)
                     .shoulder(Some(Item::new_from_asset_expect(
                         "common.items.armor.swift.shoulder",
                     )))
@@ -795,7 +795,7 @@ impl LoadoutBuilder {
                     )))
                     .build(),
                 Bandit => LoadoutBuilder::new()
-                    .active_item(active_item)
+                    .active_mainhand(active_item)
                     .shoulder(Some(Item::new_from_asset_expect(
                         "common.items.armor.assassin.shoulder",
                     )))
@@ -823,7 +823,7 @@ impl LoadoutBuilder {
                     )))
                     .build(),
                 CultistNovice => LoadoutBuilder::new()
-                    .active_item(active_item)
+                    .active_mainhand(active_item)
                     .shoulder(Some(Item::new_from_asset_expect(
                         "common.items.armor.steel.shoulder",
                     )))
@@ -854,7 +854,7 @@ impl LoadoutBuilder {
                     )))
                     .build(),
                 CultistAcolyte => LoadoutBuilder::new()
-                    .active_item(active_item)
+                    .active_mainhand(active_item)
                     .shoulder(Some(Item::new_from_asset_expect(
                         "common.items.armor.cultist.shoulder",
                     )))
@@ -885,7 +885,7 @@ impl LoadoutBuilder {
                     )))
                     .build(),
                 Warlord => LoadoutBuilder::new()
-                    .active_item(active_item)
+                    .active_mainhand(active_item)
                     .shoulder(Some(Item::new_from_asset_expect(
                         "common.items.armor.warlord.shoulder",
                     )))
@@ -916,7 +916,7 @@ impl LoadoutBuilder {
                     )))
                     .build(),
                 Warlock => LoadoutBuilder::new()
-                    .active_item(active_item)
+                    .active_mainhand(active_item)
                     .shoulder(Some(Item::new_from_asset_expect(
                         "common.items.armor.warlock.shoulder",
                     )))
@@ -947,7 +947,7 @@ impl LoadoutBuilder {
                     )))
                     .build(),
                 Villager => LoadoutBuilder::new()
-                    .active_item(active_item)
+                    .active_mainhand(active_item)
                     .chest(Some(Item::new_from_asset_expect(
                         match rand::thread_rng().gen_range(0..10) {
                             0 => "common.items.armor.misc.chest.worker_green_0",
@@ -981,36 +981,46 @@ impl LoadoutBuilder {
             match body {
                 Body::BipedLarge(b) => match b.species {
                     biped_large::Species::Mindflayer => LoadoutBuilder::new()
-                        .active_item(active_item)
+                        .active_mainhand(active_item)
                         .chest(Some(Item::new_from_asset_expect(
                             "common.items.npc_armor.biped_large.mindflayer",
                         )))
                         .build(),
-                    _ => LoadoutBuilder::new().active_item(active_item).build(),
+                    _ => LoadoutBuilder::new().active_mainhand(active_item).build(),
                 },
                 Body::Golem(g) => match g.species {
                     golem::Species::ClayGolem => LoadoutBuilder::new()
-                        .active_item(active_item)
+                        .active_mainhand(active_item)
                         .chest(Some(Item::new_from_asset_expect(
                             "common.items.npc_armor.golem.claygolem",
                         )))
                         .build(),
-                    _ => LoadoutBuilder::new().active_item(active_item).build(),
+                    _ => LoadoutBuilder::new().active_mainhand(active_item).build(),
                 },
-                _ => LoadoutBuilder::new().active_item(active_item).build(),
+                _ => LoadoutBuilder::new().active_mainhand(active_item).build(),
             }
         };
 
         Self(loadout)
     }
 
-    pub fn active_item(mut self, item: Option<Item>) -> Self {
-        self.0.swap(EquipSlot::Mainhand, item);
+    pub fn active_mainhand(mut self, item: Option<Item>) -> Self {
+        self.0.swap(EquipSlot::ActiveMainhand, item);
         self
     }
 
-    pub fn second_item(mut self, item: Option<Item>) -> Self {
-        self.0.swap(EquipSlot::Offhand, item);
+    pub fn active_offhand(mut self, item: Option<Item>) -> Self {
+        self.0.swap(EquipSlot::ActiveOffhand, item);
+        self
+    }
+
+    pub fn inactive_mainhand(mut self, item: Option<Item>) -> Self {
+        self.0.swap(EquipSlot::InactiveMainhand, item);
+        self
+    }
+
+    pub fn inactive_offhand(mut self, item: Option<Item>) -> Self {
+        self.0.swap(EquipSlot::InactiveOffhand, item);
         self
     }
 
diff --git a/common/src/comp/inventory/mod.rs b/common/src/comp/inventory/mod.rs
index 8b9108e016..ff0bfb11f4 100644
--- a/common/src/comp/inventory/mod.rs
+++ b/common/src/comp/inventory/mod.rs
@@ -552,15 +552,7 @@ impl Inventory {
     pub fn equip(&mut self, inv_slot: InvSlotId) -> Vec<Item> {
         self.get(inv_slot)
             .and_then(|item| self.loadout.get_slot_to_equip_into(item.kind()))
-            .map(|equip_slot| {
-                // Special case when equipping into main hand - swap with offhand first
-                if equip_slot == EquipSlot::Mainhand {
-                    self.loadout
-                        .swap_slots(EquipSlot::Mainhand, EquipSlot::Offhand);
-                }
-
-                self.swap_inventory_loadout(inv_slot, equip_slot)
-            })
+            .map(|equip_slot| self.swap_inventory_loadout(inv_slot, equip_slot))
             .unwrap_or_else(Vec::new)
     }
 
@@ -585,7 +577,7 @@ impl Inventory {
             + i32::try_from(slots_from_inv).expect("Inventory item with more than i32::MAX slots")
             - i32::try_from(self.populated_slots())
                 .expect("Inventory item with more than i32::MAX used slots")
-            + inv_slot_for_equipped // If there is no item already in the equip slot we gain 1 slot  
+            + inv_slot_for_equipped // If there is no item already in the equip slot we gain 1 slot
     }
 
     /// Handles picking up an item, unloading any items inside the item being
@@ -648,7 +640,7 @@ impl Inventory {
                 .expect("Equipped item with more than i32::MAX slots")
             - i32::try_from(self.populated_slots())
                 .expect("Inventory item with more than i32::MAX used slots")
-            - inv_slot_for_unequipped // If there is an item being unequipped we lose 1 slot 
+            - inv_slot_for_unequipped // If there is an item being unequipped we lose 1 slot
     }
 
     /// Swaps items from two slots, regardless of if either is inventory or
@@ -692,7 +684,7 @@ impl Inventory {
             - i32::try_from(self.populated_slots())
             .expect("inventory with more than i32::MAX used slots")
             - inv_slot_for_equipped // +1 inventory slot required if an item was unequipped
-            + inv_slot_for_inv_item // -1 inventory slot required if an item was equipped        
+            + inv_slot_for_inv_item // -1 inventory slot required if an item was equipped
     }
 
     /// Swap item in an inventory slot with one in a loadout slot.
@@ -730,6 +722,36 @@ impl Inventory {
             })
             .unwrap_or_default();
 
+        // If 2 1h weapons are equipped, and mainhand weapon removed, move offhand into
+        // mainhand
+        match equip_slot {
+            EquipSlot::ActiveMainhand => {
+                if self.loadout.equipped(EquipSlot::ActiveMainhand).is_none()
+                    && self.loadout.equipped(EquipSlot::ActiveOffhand).is_some()
+                {
+                    let offhand = self.loadout.swap(EquipSlot::ActiveOffhand, None);
+                    assert!(
+                        self.loadout
+                            .swap(EquipSlot::ActiveMainhand, offhand)
+                            .is_none()
+                    );
+                }
+            },
+            EquipSlot::InactiveMainhand => {
+                if self.loadout.equipped(EquipSlot::InactiveMainhand).is_none()
+                    && self.loadout.equipped(EquipSlot::InactiveOffhand).is_some()
+                {
+                    let offhand = self.loadout.swap(EquipSlot::InactiveOffhand, None);
+                    assert!(
+                        self.loadout
+                            .swap(EquipSlot::InactiveMainhand, offhand)
+                            .is_none()
+                    );
+                }
+            },
+            _ => {},
+        }
+
         // Attempt to put any items unloaded from the unequipped item into empty
         // inventory slots and return any that don't fit to the caller where they
         // will be dropped on the ground
@@ -743,12 +765,12 @@ impl Inventory {
     /// account whether there will be free space in the inventory for the
     /// loadout item once any slots that were provided by it have been
     /// removed.
+    #[allow(clippy::blocks_in_if_conditions)]
     pub fn can_swap(&self, inv_slot_id: InvSlotId, equip_slot: EquipSlot) -> bool {
         // Check if loadout slot can hold item
-        if !self
-            .get(inv_slot_id)
-            .map_or(true, |item| equip_slot.can_hold(&item.kind()))
-        {
+        if !self.get(inv_slot_id).map_or(true, |item| {
+            self.loadout.slot_can_hold(equip_slot, Some(&item.kind()))
+        }) {
             trace!("can_swap = false, equip slot can't hold item");
             return false;
         }
@@ -777,6 +799,8 @@ impl Inventory {
     pub fn equipped_items_of_kind(&self, item_kind: ItemKind) -> impl Iterator<Item = &Item> {
         self.loadout.equipped_items_of_kind(item_kind)
     }
+
+    pub fn swap_equipped_weapons(&mut self) { self.loadout.swap_equipped_weapons() }
 }
 
 impl Component for Inventory {
diff --git a/common/src/comp/inventory/slot.rs b/common/src/comp/inventory/slot.rs
index 4a5ad3b97a..48f2789264 100644
--- a/common/src/comp/inventory/slot.rs
+++ b/common/src/comp/inventory/slot.rs
@@ -3,7 +3,7 @@ use std::{cmp::Ordering, convert::TryFrom};
 
 use crate::comp::{
     inventory::{
-        item::{armor, armor::ArmorKind, ItemKind},
+        item::{armor, armor::ArmorKind, tool, ItemKind},
         loadout::LoadoutSlotId,
     },
     item,
@@ -81,8 +81,10 @@ impl From<InvSlotId> for SlotId {
 #[derive(Clone, Copy, PartialEq, Eq, Debug, Hash, Serialize, Deserialize)]
 pub enum EquipSlot {
     Armor(ArmorSlot),
-    Mainhand,
-    Offhand,
+    ActiveMainhand,
+    ActiveOffhand,
+    InactiveMainhand,
+    InactiveOffhand,
     Lantern,
     Glider,
 }
@@ -120,8 +122,10 @@ impl EquipSlot {
     pub fn can_hold(self, item_kind: &item::ItemKind) -> bool {
         match (self, item_kind) {
             (Self::Armor(slot), ItemKind::Armor(armor::Armor { kind, .. })) => slot.can_hold(kind),
-            (Self::Mainhand, ItemKind::Tool(_)) => true,
-            (Self::Offhand, ItemKind::Tool(_)) => true,
+            (Self::ActiveMainhand, ItemKind::Tool(_)) => true,
+            (Self::ActiveOffhand, ItemKind::Tool(tool)) => matches!(tool.hands, tool::Hands::One),
+            (Self::InactiveMainhand, ItemKind::Tool(_)) => true,
+            (Self::InactiveOffhand, ItemKind::Tool(tool)) => matches!(tool.hands, tool::Hands::One),
             (Self::Lantern, ItemKind::Lantern(_)) => true,
             (Self::Glider, ItemKind::Glider(_)) => true,
             _ => false,
diff --git a/common/src/comp/inventory/test.rs b/common/src/comp/inventory/test.rs
index 5b6ec62919..fae0a8174c 100644
--- a/common/src/comp/inventory/test.rs
+++ b/common/src/comp/inventory/test.rs
@@ -231,23 +231,29 @@ fn unequip_items_both_hands() {
 
     let sword = Item::new_from_asset_expect("common.items.weapons.sword.steel-8");
 
-    inv.replace_loadout_item(EquipSlot::Mainhand, Some(sword.duplicate(ability_map, msm)));
-    inv.replace_loadout_item(EquipSlot::Offhand, Some(sword.duplicate(ability_map, msm)));
+    inv.replace_loadout_item(
+        EquipSlot::ActiveMainhand,
+        Some(sword.duplicate(ability_map, msm)),
+    );
+    inv.replace_loadout_item(
+        EquipSlot::InactiveMainhand,
+        Some(sword.duplicate(ability_map, msm)),
+    );
 
     // Fill all inventory slots except one
     fill_inv_slots(&mut inv, 17);
 
-    let result = inv.unequip(EquipSlot::Mainhand);
+    let result = inv.unequip(EquipSlot::ActiveMainhand);
     // We have space in the inventory, so this should have unequipped
-    assert_eq!(None, inv.equipped(EquipSlot::Mainhand));
+    assert_eq!(None, inv.equipped(EquipSlot::ActiveMainhand));
     assert_eq!(18, inv.populated_slots());
     assert_eq!(true, result.is_ok());
 
-    let result = inv.unequip(EquipSlot::Offhand).unwrap_err();
+    let result = inv.unequip(EquipSlot::InactiveMainhand).unwrap_err();
     assert_eq!(SlotError::InventoryFull, result);
 
     // There is no more space in the inventory, so this should still be equipped
-    assert_eq!(&sword, inv.equipped(EquipSlot::Offhand).unwrap());
+    assert_eq!(&sword, inv.equipped(EquipSlot::InactiveMainhand).unwrap());
 
     // Verify inventory
     assert_eq!(inv.slots[17], Some(sword));
diff --git a/common/src/lib.rs b/common/src/lib.rs
index b561bf0720..132fee892a 100644
--- a/common/src/lib.rs
+++ b/common/src/lib.rs
@@ -14,6 +14,7 @@
     option_expect_none,
     option_unwrap_none,
     option_zip,
+    or_patterns,
     trait_alias,
     type_alias_impl_trait
 )]
diff --git a/common/src/states/utils.rs b/common/src/states/utils.rs
index 1547bc9838..c7eff5efe7 100644
--- a/common/src/states/utils.rs
+++ b/common/src/states/utils.rs
@@ -440,14 +440,34 @@ pub fn handle_wield(data: &JoinData, update: &mut StateUpdate) {
 
 /// If a tool is equipped, goes into Equipping state, otherwise goes to Idle
 pub fn attempt_wield(data: &JoinData, update: &mut StateUpdate) {
-    if let Some((item, ItemKind::Tool(tool))) = data
-        .inventory
-        .equipped(EquipSlot::Mainhand)
-        .map(|i| (i, i.kind()))
-    {
+    // Closure to get equip time provided an equip slot if a tool is equipped in
+    // equip slot
+    let equip_time = |equip_slot| {
+        data.inventory
+            .equipped(equip_slot)
+            .and_then(|item| match item.kind() {
+                ItemKind::Tool(tool) => Some((item, tool)),
+                _ => None,
+            })
+            .map(|(item, tool)| tool.equip_time(data.msm, item.components()))
+    };
+
+    // Calculates time required to equip weapons, if weapon in mainhand and offhand,
+    // uses maximum duration
+    let mainhand_equip_time = equip_time(EquipSlot::ActiveMainhand);
+    let offhand_equip_time = equip_time(EquipSlot::ActiveOffhand);
+    let equip_time = match (mainhand_equip_time, offhand_equip_time) {
+        (Some(a), Some(b)) => Some(a.max(b)),
+        (Some(a), None) | (None, Some(a)) => Some(a),
+        (None, None) => None,
+    };
+
+    // Moves entity into equipping state if there is some equip time, else moves
+    // intantly into wield
+    if let Some(equip_time) = equip_time {
         update.character = CharacterState::Equipping(equipping::Data {
             static_data: equipping::StaticData {
-                buildup_duration: tool.equip_time(data.msm, item.components()),
+                buildup_duration: equip_time,
             },
             timer: Duration::default(),
         });
@@ -504,7 +524,15 @@ pub fn handle_climb(data: &JoinData, update: &mut StateUpdate) -> bool {
 
 /// Checks that player can Swap Weapons and updates `Loadout` if so
 pub fn attempt_swap_equipped_weapons(data: &JoinData, update: &mut StateUpdate) {
-    if data.inventory.equipped(EquipSlot::Offhand).is_some() {
+    if data
+        .inventory
+        .equipped(EquipSlot::InactiveMainhand)
+        .is_some()
+        || data
+            .inventory
+            .equipped(EquipSlot::InactiveOffhand)
+            .is_some()
+    {
         update.swap_equipped_weapons = true;
     }
 }
@@ -556,14 +584,14 @@ fn handle_ability(data: &JoinData, update: &mut StateUpdate, input: InputKind) {
     let no_main_hand = hands.0.is_none();
     // skill_index used to select ability for the AbilityKey::Skill2 input
     let (equip_slot, skill_index) = if no_main_hand {
-        (Some(EquipSlot::Offhand), 1)
+        (Some(EquipSlot::ActiveOffhand), 1)
     } else if always_main_hand {
-        (Some(EquipSlot::Mainhand), 0)
+        (Some(EquipSlot::ActiveMainhand), 0)
     } else {
         match hands {
-            (Some(Hands::Two), _) => (Some(EquipSlot::Mainhand), 1),
-            (_, Some(Hands::One)) => (Some(EquipSlot::Offhand), 0),
-            (Some(Hands::One), _) => (Some(EquipSlot::Mainhand), 1),
+            (Some(Hands::Two), _) => (Some(EquipSlot::ActiveMainhand), 1),
+            (_, Some(Hands::One)) => (Some(EquipSlot::ActiveOffhand), 0),
+            (Some(Hands::One), _) => (Some(EquipSlot::ActiveMainhand), 1),
             (_, _) => (None, 0),
         }
     };
@@ -596,7 +624,11 @@ fn handle_ability(data: &JoinData, update: &mut StateUpdate, input: InputKind) {
         {
             update.character = CharacterState::from((
                 &ability,
-                AbilityInfo::from_input(data, matches!(equip_slot, EquipSlot::Offhand), input),
+                AbilityInfo::from_input(
+                    data,
+                    matches!(equip_slot, EquipSlot::ActiveOffhand),
+                    input,
+                ),
             ));
         }
     }
@@ -640,7 +672,8 @@ pub fn handle_block_input(data: &JoinData, update: &mut StateUpdate) {
         |equip_slot| matches!(unwrap_tool_data(data, equip_slot), Some(tool) if tool.can_block());
     let hands = get_hands(data);
     if input_is_pressed(data, InputKind::Block)
-        && (can_block(EquipSlot::Mainhand) || (hands.0.is_none() && can_block(EquipSlot::Offhand)))
+        && (can_block(EquipSlot::ActiveMainhand)
+            || (hands.0.is_none() && can_block(EquipSlot::ActiveOffhand)))
     {
         let ability = CharacterAbility::default_block();
         if ability.requirements_paid(data, update) {
@@ -696,15 +729,18 @@ pub fn get_hands(data: &JoinData) -> (Option<Hands>, Option<Hands>) {
             None
         }
     };
-    (hand(EquipSlot::Mainhand), hand(EquipSlot::Offhand))
+    (
+        hand(EquipSlot::ActiveMainhand),
+        hand(EquipSlot::ActiveOffhand),
+    )
 }
 
 pub fn get_crit_data(data: &JoinData, ai: AbilityInfo) -> (f32, f32) {
     const DEFAULT_CRIT_DATA: (f32, f32) = (0.5, 1.3);
     use HandInfo::*;
     let slot = match ai.hand {
-        Some(TwoHanded) | Some(MainHand) => EquipSlot::Mainhand,
-        Some(OffHand) => EquipSlot::Offhand,
+        Some(TwoHanded) | Some(MainHand) => EquipSlot::ActiveMainhand,
+        Some(OffHand) => EquipSlot::ActiveOffhand,
         None => return DEFAULT_CRIT_DATA,
     };
     if let Some(item) = data.inventory.equipped(slot) {
@@ -792,9 +828,9 @@ pub struct AbilityInfo {
 impl AbilityInfo {
     pub fn from_input(data: &JoinData, from_offhand: bool, input: InputKind) -> Self {
         let tool_data = if from_offhand {
-            unwrap_tool_data(data, EquipSlot::Offhand)
+            unwrap_tool_data(data, EquipSlot::ActiveOffhand)
         } else {
-            unwrap_tool_data(data, EquipSlot::Mainhand)
+            unwrap_tool_data(data, EquipSlot::ActiveMainhand)
         };
         let (tool, hand) = (
             tool_data.map(|t| t.kind),
diff --git a/common/src/states/wielding.rs b/common/src/states/wielding.rs
index 4798b44834..48673483bd 100644
--- a/common/src/states/wielding.rs
+++ b/common/src/states/wielding.rs
@@ -60,9 +60,12 @@ impl CharacterBehavior for Data {
     fn manipulate_loadout(&self, data: &JoinData, inv_action: InventoryAction) -> StateUpdate {
         let mut update = StateUpdate::from(data);
         match inv_action {
-            InventoryAction::Drop(EquipSlot::Mainhand)
-            | InventoryAction::Swap(EquipSlot::Mainhand, _)
-            | InventoryAction::Swap(_, Slot::Equip(EquipSlot::Mainhand)) => {
+            InventoryAction::Drop(EquipSlot::ActiveMainhand | EquipSlot::ActiveOffhand)
+            | InventoryAction::Swap(EquipSlot::ActiveMainhand | EquipSlot::ActiveOffhand, _)
+            | InventoryAction::Swap(
+                _,
+                Slot::Equip(EquipSlot::ActiveMainhand | EquipSlot::ActiveOffhand),
+            ) => {
                 update.character = CharacterState::Idle;
             },
             _ => (),
diff --git a/common/systems/src/character_behavior.rs b/common/systems/src/character_behavior.rs
index a97350888b..20590f825f 100644
--- a/common/systems/src/character_behavior.rs
+++ b/common/systems/src/character_behavior.rs
@@ -5,14 +5,9 @@ use specs::{
 
 use common::{
     comp::{
-        self,
-        inventory::{
-            item::MaterialStatManifest,
-            slot::{EquipSlot, Slot},
-        },
-        Beam, Body, CharacterState, Combo, Controller, Density, Energy, Health, Inventory, Mass,
-        Melee, Mounting, Ori, PhysicsState, Poise, PoiseState, Pos, SkillSet, StateUpdate, Stats,
-        Vel,
+        self, inventory::item::MaterialStatManifest, Beam, Body, CharacterState, Combo, Controller,
+        Density, Energy, Health, Inventory, Mass, Melee, Mounting, Ori, PhysicsState, Poise,
+        PoiseState, Pos, SkillSet, StateUpdate, Stats, Vel,
     },
     event::{EventBus, LocalEvent, ServerEvent},
     outcome::Outcome,
@@ -48,16 +43,7 @@ fn incorporate_update(join: &mut JoinStruct, mut state_update: StateUpdate) {
     if state_update.swap_equipped_weapons {
         let mut inventory = join.inventory.get_mut_unchecked();
         let inventory = &mut *inventory;
-        assert!(
-            inventory
-                .swap(
-                    Slot::Equip(EquipSlot::Mainhand),
-                    Slot::Equip(EquipSlot::Offhand),
-                )
-                .first()
-                .is_none(),
-            "Swapping main and offhand never results in leftover items",
-        );
+        inventory.swap_equipped_weapons();
     }
 }
 
diff --git a/server/src/character_creator.rs b/server/src/character_creator.rs
index 39df6e851c..fd9b896b7e 100644
--- a/server/src/character_creator.rs
+++ b/server/src/character_creator.rs
@@ -38,7 +38,7 @@ pub fn create_character(
 
     let loadout = LoadoutBuilder::new()
         .defaults()
-        .active_item(Some(Item::new_from_asset_expect(&tool_id)))
+        .active_mainhand(Some(Item::new_from_asset_expect(&tool_id)))
         .build();
 
     let mut inventory = Inventory::new_with_loadout(loadout);
diff --git a/server/src/events/interaction.rs b/server/src/events/interaction.rs
index d671b1e341..e8c4a5e0e9 100644
--- a/server/src/events/interaction.rs
+++ b/server/src/events/interaction.rs
@@ -254,15 +254,15 @@ pub fn handle_possess(server: &mut Server, possessor_uid: Uid, possesse_uid: Uid
             assert!(
                 inventory
                     .swap(
-                        Slot::Equip(EquipSlot::Mainhand),
-                        Slot::Equip(EquipSlot::Offhand),
+                        Slot::Equip(EquipSlot::ActiveMainhand),
+                        Slot::Equip(EquipSlot::InactiveMainhand),
                     )
                     .first()
                     .is_none(),
-                "Swapping main and offhand never results in leftover items",
+                "Swapping active and inactive mainhands never results in leftover items",
             );
 
-            inventory.replace_loadout_item(EquipSlot::Mainhand, Some(debug_item));
+            inventory.replace_loadout_item(EquipSlot::ActiveMainhand, Some(debug_item));
         }
 
         // Remove will of the entity
diff --git a/server/src/migrations/V37__equip_slots.sql b/server/src/migrations/V37__equip_slots.sql
new file mode 100644
index 0000000000..b9c3c0fc6a
--- /dev/null
+++ b/server/src/migrations/V37__equip_slots.sql
@@ -0,0 +1,8 @@
+-- Sets active_item to active_mainhand and second_item to inactive_mainhand.
+--
+-- second_item becomes inactive_mainhand because active_offhand is enforced to be 1h
+-- and second_item was not necessarily guaranteed to be 1h.
+UPDATE item
+SET position = 'active_mainhand' WHERE position = 'active_item';
+UPDATE item
+SET position = 'inactive_mainhand' WHERE position = 'second_item';
\ No newline at end of file
diff --git a/server/src/sys/agent.rs b/server/src/sys/agent.rs
index 089a396fdd..9b70eec433 100644
--- a/server/src/sys/agent.rs
+++ b/server/src/sys/agent.rs
@@ -1553,7 +1553,7 @@ impl<'a> AgentData<'a> {
 
         let tactic = self
             .inventory
-            .equipped(EquipSlot::Mainhand)
+            .equipped(EquipSlot::ActiveMainhand)
             .as_ref()
             .map(|item| {
                 if let Some(ability_spec) = item.ability_spec() {
diff --git a/voxygen/src/audio/sfx/event_mapper/combat/mod.rs b/voxygen/src/audio/sfx/event_mapper/combat/mod.rs
index c63f378b1d..58ec98bf46 100644
--- a/voxygen/src/audio/sfx/event_mapper/combat/mod.rs
+++ b/voxygen/src/audio/sfx/event_mapper/combat/mod.rs
@@ -145,7 +145,7 @@ impl CombatEventMapper {
         previous_state: &PreviousEntityState,
         inventory: &Inventory,
     ) -> SfxEvent {
-        if let Some(item) = inventory.equipped(EquipSlot::Mainhand) {
+        if let Some(item) = inventory.equipped(EquipSlot::ActiveMainhand) {
             if let ItemKind::Tool(data) = item.kind() {
                 if character_state.is_attack() {
                     return SfxEvent::Attack(
diff --git a/voxygen/src/audio/sfx/event_mapper/combat/tests.rs b/voxygen/src/audio/sfx/event_mapper/combat/tests.rs
index 19856b36ab..bf50ae96f6 100644
--- a/voxygen/src/audio/sfx/event_mapper/combat/tests.rs
+++ b/voxygen/src/audio/sfx/event_mapper/combat/tests.rs
@@ -13,7 +13,7 @@ use std::time::{Duration, Instant};
 #[test]
 fn maps_wield_while_equipping() {
     let loadout = LoadoutBuilder::new()
-        .active_item(Some(Item::new_from_asset_expect(
+        .active_mainhand(Some(Item::new_from_asset_expect(
             "common.items.weapons.axe.starter_axe",
         )))
         .build();
@@ -40,7 +40,7 @@ fn maps_wield_while_equipping() {
 #[test]
 fn maps_unwield() {
     let loadout = LoadoutBuilder::new()
-        .active_item(Some(Item::new_from_asset_expect(
+        .active_mainhand(Some(Item::new_from_asset_expect(
             "common.items.weapons.bow.starter",
         )))
         .build();
@@ -62,7 +62,7 @@ fn maps_unwield() {
 #[test]
 fn maps_basic_melee() {
     let loadout = LoadoutBuilder::new()
-        .active_item(Some(Item::new_from_asset_expect(
+        .active_mainhand(Some(Item::new_from_asset_expect(
             "common.items.weapons.axe.starter_axe",
         )))
         .build();
@@ -104,7 +104,7 @@ fn maps_basic_melee() {
 #[test]
 fn matches_ability_stage() {
     let loadout = LoadoutBuilder::new()
-        .active_item(Some(Item::new_from_asset_expect(
+        .active_mainhand(Some(Item::new_from_asset_expect(
             "common.items.weapons.sword.starter",
         )))
         .build();
@@ -163,7 +163,7 @@ fn matches_ability_stage() {
 #[test]
 fn ignores_different_ability_stage() {
     let loadout = LoadoutBuilder::new()
-        .active_item(Some(Item::new_from_asset_expect(
+        .active_mainhand(Some(Item::new_from_asset_expect(
             "common.items.weapons.axe.starter_axe",
         )))
         .build();
diff --git a/voxygen/src/hud/bag.rs b/voxygen/src/hud/bag.rs
index c9baa92f9b..7a1027da61 100644
--- a/voxygen/src/hud/bag.rs
+++ b/voxygen/src/hud/bag.rs
@@ -13,6 +13,8 @@ use crate::{
         ImageFrame, ItemTooltip, ItemTooltipManager, ItemTooltipable, Tooltip, TooltipManager,
         Tooltipable,
     },
+    window::GameInput,
+    GlobalState,
 };
 use client::Client;
 use common::{
@@ -448,8 +450,11 @@ widget_ids! {
         back_slot,
         tabard_slot,
         glider_slot,
-        mainhand_slot,
-        offhand_slot,
+        active_mainhand_slot,
+        active_offhand_slot,
+        inactive_mainhand_slot,
+        inactive_offhand_slot,
+        swap_equipped_weapons_btn,
         bag1_slot,
         bag2_slot,
         bag3_slot,
@@ -463,6 +468,7 @@ widget_ids! {
 #[derive(WidgetCommon)]
 pub struct Bag<'a> {
     client: &'a Client,
+    global_state: &'a GlobalState,
     imgs: &'a Imgs,
     item_imgs: &'a ItemImgs,
     fonts: &'a Fonts,
@@ -487,6 +493,7 @@ impl<'a> Bag<'a> {
     #[allow(clippy::too_many_arguments)] // TODO: Pending review in #587
     pub fn new(
         client: &'a Client,
+        global_state: &'a GlobalState,
         imgs: &'a Imgs,
         item_imgs: &'a ItemImgs,
         fonts: &'a Fonts,
@@ -506,6 +513,7 @@ impl<'a> Bag<'a> {
     ) -> Self {
         Self {
             client,
+            global_state,
             imgs,
             item_imgs,
             fonts,
@@ -543,6 +551,7 @@ pub enum Event {
     BagExpand,
     Close,
     SortInventory,
+    SwapEquippedWeapons,
 }
 
 impl<'a> Widget for Bag<'a> {
@@ -567,6 +576,7 @@ impl<'a> Widget for Bag<'a> {
     fn update(self, args: widget::UpdateArgs<Self>) -> Self::Event {
         let widget::UpdateArgs { state, ui, .. } = args;
         let i18n = &self.localized_strings;
+        let key_layout = &self.global_state.window.key_layout;
 
         let mut event = None;
         let bag_tooltip = Tooltip::new({
@@ -690,7 +700,7 @@ impl<'a> Widget for Bag<'a> {
         if inventory.slots().count() > 45 || self.show.bag_inv {
             let expand_btn_top = if self.show.bag_inv { 53.0 } else { 460.0 };
             if expand_btn
-                .top_left_with_margins_on(state.bg_ids.bg_frame, expand_btn_top, 211.5)
+                .top_right_with_margins_on(state.bg_ids.bg_frame, expand_btn_top, 37.0)
                 .with_tooltip(self.tooltip_manager, &txt, "", &bag_tooltip, TEXT_COLOR)
                 .set(state.ids.bag_expand_btn, ui)
                 .was_clicked()
@@ -1152,19 +1162,19 @@ impl<'a> Widget for Bag<'a> {
                 )
                 .set(state.ids.tabard_slot, ui)
             }
-            // Mainhand/Left-Slot
+            // Active Mainhand/Left-Slot
             let mainhand_item = inventory
-                .equipped(EquipSlot::Mainhand)
+                .equipped(EquipSlot::ActiveMainhand)
                 .map(|item| item.to_owned());
 
             let slot = slot_maker
-                .fabricate(EquipSlot::Mainhand, [85.0; 2])
+                .fabricate(EquipSlot::ActiveMainhand, [85.0; 2])
                 .bottom_right_with_margins_on(state.ids.back_slot, -95.0, 0.0)
                 .with_icon(self.imgs.mainhand_bg, Vec2::new(75.0, 75.0), Some(UI_MAIN))
                 .filled_slot(filled_slot);
             if let Some(item) = mainhand_item {
                 slot.with_item_tooltip(self.item_tooltip_manager, &item, &None, &item_tooltip)
-                    .set(state.ids.mainhand_slot, ui)
+                    .set(state.ids.active_mainhand_slot, ui)
             } else {
                 slot.with_tooltip(
                     self.tooltip_manager,
@@ -1173,20 +1183,21 @@ impl<'a> Widget for Bag<'a> {
                     &tooltip,
                     color::WHITE,
                 )
-                .set(state.ids.mainhand_slot, ui)
+                .set(state.ids.active_mainhand_slot, ui)
             }
-            // Offhand/Right-Slot
+
+            // Active Offhand/Right-Slot
             let offhand_item = inventory
-                .equipped(EquipSlot::Offhand)
+                .equipped(EquipSlot::ActiveOffhand)
                 .map(|item| item.to_owned());
             let slot = slot_maker
-                .fabricate(EquipSlot::Offhand, [85.0; 2])
+                .fabricate(EquipSlot::ActiveOffhand, [85.0; 2])
                 .bottom_left_with_margins_on(state.ids.feet_slot, -95.0, 0.0)
                 .with_icon(self.imgs.offhand_bg, Vec2::new(75.0, 75.0), Some(UI_MAIN))
                 .filled_slot(filled_slot);
             if let Some(item) = offhand_item {
                 slot.with_item_tooltip(self.item_tooltip_manager, &item, &None, &item_tooltip)
-                    .set(state.ids.offhand_slot, ui)
+                    .set(state.ids.active_offhand_slot, ui)
             } else {
                 slot.with_tooltip(
                     self.tooltip_manager,
@@ -1195,7 +1206,83 @@ impl<'a> Widget for Bag<'a> {
                     &tooltip,
                     color::WHITE,
                 )
-                .set(state.ids.offhand_slot, ui)
+                .set(state.ids.active_offhand_slot, ui)
+            }
+            // Inactive Mainhand/Left-Slot
+            let mainhand_item = inventory
+                .equipped(EquipSlot::InactiveMainhand)
+                .map(|item| item.to_owned());
+
+            let slot = slot_maker
+                .fabricate(EquipSlot::InactiveMainhand, [40.0; 2])
+                .bottom_right_with_margins_on(state.ids.active_mainhand_slot, 3.0, -47.0)
+                .with_icon(self.imgs.mainhand_bg, Vec2::new(35.0, 35.0), Some(UI_MAIN))
+                .filled_slot(filled_slot);
+            if let Some(item) = mainhand_item {
+                slot.with_item_tooltip(self.item_tooltip_manager, &item, &None, &item_tooltip)
+                    .set(state.ids.inactive_mainhand_slot, ui)
+            } else {
+                slot.with_tooltip(
+                    self.tooltip_manager,
+                    i18n.get("hud.bag.inactive_mainhand"),
+                    "",
+                    &tooltip,
+                    color::WHITE,
+                )
+                .set(state.ids.inactive_mainhand_slot, ui)
+            }
+
+            // Inctive Offhand/Right-Slot
+            let offhand_item = inventory
+                .equipped(EquipSlot::InactiveOffhand)
+                .map(|item| item.to_owned());
+            let slot = slot_maker
+                .fabricate(EquipSlot::InactiveOffhand, [40.0; 2])
+                .bottom_left_with_margins_on(state.ids.active_offhand_slot, 3.0, -47.0)
+                .with_icon(self.imgs.offhand_bg, Vec2::new(35.0, 35.0), Some(UI_MAIN))
+                .filled_slot(filled_slot);
+            if let Some(item) = offhand_item {
+                slot.with_item_tooltip(self.item_tooltip_manager, &item, &None, &item_tooltip)
+                    .set(state.ids.inactive_offhand_slot, ui)
+            } else {
+                slot.with_tooltip(
+                    self.tooltip_manager,
+                    i18n.get("hud.bag.inactive_offhand"),
+                    "",
+                    &tooltip,
+                    color::WHITE,
+                )
+                .set(state.ids.inactive_offhand_slot, ui)
+            }
+
+            if Button::image(self.imgs.swap_equipped_weapons_btn)
+                .hover_image(self.imgs.swap_equipped_weapons_btn_hover)
+                .press_image(self.imgs.swap_equipped_weapons_btn_press)
+                .w_h(32.0, 40.0)
+                .bottom_left_with_margins_on(state.bg_ids.bg_frame, 0.0, 23.3)
+                .align_middle_y_of(state.ids.active_mainhand_slot)
+                .with_tooltip(
+                    self.tooltip_manager,
+                    i18n.get("hud.bag.swap_equipped_weapons_title"),
+                    if let Some(key) = self
+                        .global_state
+                        .settings
+                        .controls
+                        .get_binding(GameInput::SwapLoadout)
+                    {
+                        i18n.get("hud.bag.swap_equipped_weapons_desc")
+                            .replace("{key}", key.display_string(key_layout).as_str())
+                    } else {
+                        "".to_string()
+                    }
+                    .as_str(),
+                    &tooltip,
+                    color::WHITE,
+                )
+                .set(state.ids.swap_equipped_weapons_btn, ui)
+                .was_clicked()
+            {
+                event = Some(Event::SwapEquippedWeapons);
             }
         }
 
diff --git a/voxygen/src/hud/hotbar.rs b/voxygen/src/hud/hotbar.rs
index 56107833de..7bfe10b8d4 100644
--- a/voxygen/src/hud/hotbar.rs
+++ b/voxygen/src/hud/hotbar.rs
@@ -88,9 +88,12 @@ impl State {
                 _ => None,
             };
 
-        let equip_slot = match (hands(EquipSlot::Mainhand), hands(EquipSlot::Offhand)) {
-            (Some(_), _) => Some(EquipSlot::Mainhand),
-            (_, Some(_)) => Some(EquipSlot::Offhand),
+        let equip_slot = match (
+            hands(EquipSlot::ActiveMainhand),
+            hands(EquipSlot::ActiveOffhand),
+        ) {
+            (Some(_), _) => Some(EquipSlot::ActiveMainhand),
+            (_, Some(_)) => Some(EquipSlot::ActiveOffhand),
             _ => None,
         };
 
@@ -140,14 +143,14 @@ impl State {
                 _ => None,
             };
 
-            let active_tool_hands = hands(EquipSlot::Mainhand);
-            let second_tool_hands = hands(EquipSlot::Offhand);
+            let active_tool_hands = hands(EquipSlot::ActiveMainhand);
+            let second_tool_hands = hands(EquipSlot::ActiveOffhand);
 
             let (equip_slot, skill_index) = match (active_tool_hands, second_tool_hands) {
-                (Some(Hands::Two), _) => (Some(EquipSlot::Mainhand), 1),
-                (Some(_), Some(Hands::One)) => (Some(EquipSlot::Offhand), 0),
-                (Some(Hands::One), _) => (Some(EquipSlot::Mainhand), 1),
-                (None, Some(_)) => (Some(EquipSlot::Offhand), 1),
+                (Some(Hands::Two), _) => (Some(EquipSlot::ActiveMainhand), 1),
+                (Some(_), Some(Hands::One)) => (Some(EquipSlot::ActiveOffhand), 0),
+                (Some(Hands::One), _) => (Some(EquipSlot::ActiveMainhand), 1),
+                (None, Some(_)) => (Some(EquipSlot::ActiveOffhand), 1),
                 (_, _) => (None, 0),
             };
 
diff --git a/voxygen/src/hud/img_ids.rs b/voxygen/src/hud/img_ids.rs
index ae25ebf567..712754a2b3 100644
--- a/voxygen/src/hud/img_ids.rs
+++ b/voxygen/src/hud/img_ids.rs
@@ -421,6 +421,9 @@ image_ids! {
         inv_sort_btn: "voxygen.element.ui.bag.buttons.inv_sort",
         inv_sort_btn_hover: "voxygen.element.ui.bag.buttons.inv_sort_hover",
         inv_sort_btn_press: "voxygen.element.ui.bag.buttons.inv_sort_press",
+        swap_equipped_weapons_btn: "voxygen.element.ui.bag.buttons.swap_equipped_weapons",
+        swap_equipped_weapons_btn_hover: "voxygen.element.ui.bag.buttons.swap_equipped_weapons_hover",
+        swap_equipped_weapons_btn_press: "voxygen.element.ui.bag.buttons.swap_equipped_weapons_press",
         coin_ico: "voxygen.element.items.coin",
         cheese_ico: "voxygen.element.items.item_cheese",
         inv_bg_armor: "voxygen.element.ui.bag.inv_bg_0",
diff --git a/voxygen/src/hud/mod.rs b/voxygen/src/hud/mod.rs
index de3a920bc7..3bc56d24ee 100644
--- a/voxygen/src/hud/mod.rs
+++ b/voxygen/src/hud/mod.rs
@@ -373,6 +373,7 @@ pub enum Event {
         slot: comp::slot::Slot,
         bypass_dialog: bool,
     },
+    SwapEquippedWeapons,
     SwapSlots {
         slot_a: comp::slot::Slot,
         slot_b: comp::slot::Slot,
@@ -2448,6 +2449,7 @@ impl Hud {
             ) {
                 match Bag::new(
                     client,
+                    global_state,
                     &self.imgs,
                     &self.item_imgs,
                     &self.fonts,
@@ -2479,6 +2481,9 @@ impl Hud {
                         };
                     },
                     Some(bag::Event::SortInventory) => self.events.push(Event::SortInventory),
+                    Some(bag::Event::SwapEquippedWeapons) => {
+                        self.events.push(Event::SwapEquippedWeapons)
+                    },
                     None => {},
                 }
             }
diff --git a/voxygen/src/hud/skillbar.rs b/voxygen/src/hud/skillbar.rs
index a458a8aef8..f48eb11aa9 100644
--- a/voxygen/src/hud/skillbar.rs
+++ b/voxygen/src/hud/skillbar.rs
@@ -536,7 +536,7 @@ impl<'a> Widget for Skillbar<'a> {
                         .map(|item| (item.name(), item.description())),
                     hotbar::SlotContents::Ability3 => content_source
                         .1
-                        .equipped(EquipSlot::Mainhand)
+                        .equipped(EquipSlot::ActiveMainhand)
                         .map(|i| i.kind())
                         .and_then(|kind| match kind {
                             ItemKind::Tool(Tool { kind, .. }) => ability_description(kind),
@@ -552,14 +552,14 @@ impl<'a> Widget for Skillbar<'a> {
                             _ => None,
                         };
 
-                        let active_tool_hands = hands(EquipSlot::Mainhand);
-                        let second_tool_hands = hands(EquipSlot::Offhand);
+                        let active_tool_hands = hands(EquipSlot::ActiveMainhand);
+                        let second_tool_hands = hands(EquipSlot::ActiveOffhand);
 
                         let equip_slot = match (active_tool_hands, second_tool_hands) {
-                            (Some(Hands::Two), _) => Some(EquipSlot::Mainhand),
-                            (Some(_), Some(Hands::One)) => Some(EquipSlot::Offhand),
-                            (Some(Hands::One), _) => Some(EquipSlot::Mainhand),
-                            (None, Some(_)) => Some(EquipSlot::Offhand),
+                            (Some(Hands::Two), _) => Some(EquipSlot::ActiveMainhand),
+                            (Some(_), Some(Hands::One)) => Some(EquipSlot::ActiveOffhand),
+                            (Some(Hands::One), _) => Some(EquipSlot::ActiveMainhand),
+                            (None, Some(_)) => Some(EquipSlot::ActiveOffhand),
                             (_, _) => None,
                         };
 
@@ -657,8 +657,8 @@ impl<'a> Widget for Skillbar<'a> {
             .right_from(state.ids.slot5, slot_offset)
             .set(state.ids.m1_slot_bg, ui);
 
-        let active_tool = get_item_and_tool(self.inventory, EquipSlot::Mainhand);
-        let second_tool = get_item_and_tool(self.inventory, EquipSlot::Offhand);
+        let active_tool = get_item_and_tool(self.inventory, EquipSlot::ActiveMainhand);
+        let second_tool = get_item_and_tool(self.inventory, EquipSlot::ActiveOffhand);
 
         let tool = match (
             active_tool.map(|(_, x)| x.hands),
@@ -700,8 +700,8 @@ impl<'a> Widget for Skillbar<'a> {
             }
         }
 
-        let active_tool = get_item_and_tool(self.inventory, EquipSlot::Mainhand);
-        let second_tool = get_item_and_tool(self.inventory, EquipSlot::Offhand);
+        let active_tool = get_item_and_tool(self.inventory, EquipSlot::ActiveMainhand);
+        let second_tool = get_item_and_tool(self.inventory, EquipSlot::ActiveOffhand);
 
         let tool = match (
             active_tool.map(|(_, x)| x.hands),
diff --git a/voxygen/src/hud/slots.rs b/voxygen/src/hud/slots.rs
index d5b587f5ec..d77bebb153 100644
--- a/voxygen/src/hud/slots.rs
+++ b/voxygen/src/hud/slots.rs
@@ -143,12 +143,12 @@ impl<'a> SlotKey<HotbarSource<'a>, HotbarImageSource<'a>> for HotbarSlot {
                     _ => None,
                 };
 
-                let active_tool_hands = hands(EquipSlot::Mainhand);
-                let second_tool_hands = hands(EquipSlot::Offhand);
+                let active_tool_hands = hands(EquipSlot::ActiveMainhand);
+                let second_tool_hands = hands(EquipSlot::ActiveOffhand);
 
                 let equip_slot = match (active_tool_hands, second_tool_hands) {
-                    (Some(_), _) => Some(EquipSlot::Mainhand),
-                    (None, Some(_)) => Some(EquipSlot::Offhand),
+                    (Some(_), _) => Some(EquipSlot::ActiveMainhand),
+                    (None, Some(_)) => Some(EquipSlot::ActiveOffhand),
                     (_, _) => None,
                 };
 
@@ -189,14 +189,14 @@ impl<'a> SlotKey<HotbarSource<'a>, HotbarImageSource<'a>> for HotbarSlot {
                     _ => None,
                 };
 
-                let active_tool_hands = hands(EquipSlot::Mainhand);
-                let second_tool_hands = hands(EquipSlot::Offhand);
+                let active_tool_hands = hands(EquipSlot::ActiveMainhand);
+                let second_tool_hands = hands(EquipSlot::ActiveOffhand);
 
                 let (equip_slot, skill_index) = match (active_tool_hands, second_tool_hands) {
-                    (Some(Hands::Two), _) => (Some(EquipSlot::Mainhand), 1),
-                    (Some(_), Some(Hands::One)) => (Some(EquipSlot::Offhand), 0),
-                    (Some(Hands::One), _) => (Some(EquipSlot::Mainhand), 1),
-                    (None, Some(_)) => (Some(EquipSlot::Offhand), 1),
+                    (Some(Hands::Two), _) => (Some(EquipSlot::ActiveMainhand), 1),
+                    (Some(_), Some(Hands::One)) => (Some(EquipSlot::ActiveOffhand), 0),
+                    (Some(Hands::One), _) => (Some(EquipSlot::ActiveMainhand), 1),
+                    (None, Some(_)) => (Some(EquipSlot::ActiveOffhand), 1),
                     (_, _) => (None, 0),
                 };
 
diff --git a/voxygen/src/menu/char_selection/ui/mod.rs b/voxygen/src/menu/char_selection/ui/mod.rs
index df028ea997..502320c95d 100644
--- a/voxygen/src/menu/char_selection/ui/mod.rs
+++ b/voxygen/src/menu/char_selection/ui/mod.rs
@@ -182,7 +182,7 @@ impl Mode {
 
         let loadout = LoadoutBuilder::new()
             .defaults()
-            .active_item(Some(Item::new_from_asset_expect(tool)))
+            .active_mainhand(Some(Item::new_from_asset_expect(tool)))
             .build();
 
         let inventory = Box::new(Inventory::new_with_loadout(loadout));
@@ -1328,7 +1328,7 @@ impl Controls {
                 {
                     *tool = value;
                     inventory.replace_loadout_item(
-                        EquipSlot::Mainhand,
+                        EquipSlot::ActiveMainhand,
                         Some(Item::new_from_asset_expect(*tool)),
                     );
                 }
diff --git a/voxygen/src/scene/figure/cache.rs b/voxygen/src/scene/figure/cache.rs
index 8ac24de101..c148d5a9dd 100644
--- a/voxygen/src/scene/figure/cache.rs
+++ b/voxygen/src/scene/figure/cache.rs
@@ -221,10 +221,10 @@ impl CharacterCacheKey {
                 };
                 Some(CharacterToolKey {
                     active: inventory
-                        .equipped(EquipSlot::Mainhand)
+                        .equipped(EquipSlot::ActiveMainhand)
                         .map(tool_key_from_item),
                     second: inventory
-                        .equipped(EquipSlot::Offhand)
+                        .equipped(EquipSlot::ActiveOffhand)
                         .map(tool_key_from_item),
                 })
             } else {
diff --git a/voxygen/src/scene/figure/mod.rs b/voxygen/src/scene/figure/mod.rs
index be968bb815..b4b16a1778 100644
--- a/voxygen/src/scene/figure/mod.rs
+++ b/voxygen/src/scene/figure/mod.rs
@@ -740,9 +740,9 @@ impl FigureMgr {
             };
 
             let (active_tool_kind, active_tool_hand, active_tool_spec) =
-                tool_info(EquipSlot::Mainhand);
+                tool_info(EquipSlot::ActiveMainhand);
             let (second_tool_kind, second_tool_hand, second_tool_spec) =
-                tool_info(EquipSlot::Offhand);
+                tool_info(EquipSlot::ActiveOffhand);
 
             let hands = (active_tool_hand, second_tool_hand);
 
diff --git a/voxygen/src/scene/simple.rs b/voxygen/src/scene/simple.rs
index 4901a5972c..2b0b0135a9 100644
--- a/voxygen/src/scene/simple.rs
+++ b/voxygen/src/scene/simple.rs
@@ -307,7 +307,7 @@ impl Scene {
             .clean(&mut self.col_lights, scene_data.tick);
 
         let active_item_kind = inventory
-            .and_then(|inv| inv.equipped(EquipSlot::Mainhand))
+            .and_then(|inv| inv.equipped(EquipSlot::ActiveMainhand))
             .map(|i| i.kind());
 
         let (active_tool_kind, active_tool_hand) =
@@ -318,7 +318,7 @@ impl Scene {
             };
 
         let second_item_kind = inventory
-            .and_then(|inv| inv.equipped(EquipSlot::Offhand))
+            .and_then(|inv| inv.equipped(EquipSlot::ActiveOffhand))
             .map(|i| i.kind());
 
         let (second_tool_kind, second_tool_hand) =
diff --git a/voxygen/src/session/mod.rs b/voxygen/src/session/mod.rs
index 87fc07c724..86a33f669a 100644
--- a/voxygen/src/session/mod.rs
+++ b/voxygen/src/session/mod.rs
@@ -329,7 +329,7 @@ impl PlayState for SessionState {
                 .borrow()
                 .inventories()
                 .get(player_entity)
-                .and_then(|inv| inv.equipped(EquipSlot::Mainhand))
+                .and_then(|inv| inv.equipped(EquipSlot::ActiveMainhand))
                 .and_then(|item| item.tool())
                 .map_or(false, |tool| tool.kind == ToolKind::Pick)
                 && self.client.borrow().is_wielding() == Some(true);
@@ -1086,6 +1086,9 @@ impl PlayState for SessionState {
                             self.client.borrow_mut().use_slot(slot);
                         }
                     },
+                    HudEvent::SwapEquippedWeapons => {
+                        self.client.borrow_mut().swap_loadout();
+                    },
                     HudEvent::SwapSlots {
                         slot_a,
                         slot_b,