diff --git a/CHANGELOG.md b/CHANGELOG.md
index 09cab0b53989626127684da09a02515bd6855f3e..bde91d87d7504c3c76d81607f6e93db8a1c64f97 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -65,6 +65,8 @@ Coq development, but not every API-breaking change is listed.  Changes marked
   lemmas is through `iInv`, and that does not change.)
 * Added a construction `bi_rtc` to create reflexive transitive closures of
   PROP-level binary relations.
+* Add new introduction pattern `-# pat` that moves a hypothesis from the
+  intuitionistic context to the spatial context.
 
 ## Iris 3.2.0 (released 2019-08-29)
 
diff --git a/docs/proof_mode.md b/docs/proof_mode.md
index 202403f3ef62a159f87ef57e1795f9bae3056858..f87591b57ab330f777954027a7878176a04149d2 100644
--- a/docs/proof_mode.md
+++ b/docs/proof_mode.md
@@ -227,7 +227,16 @@ _introduction patterns_:
 - `[]` : false elimination.
 - `%` : move the hypothesis to the pure Coq context (anonymously).
 - `->` and `<-` : rewrite using a pure Coq equality
-- `# ipat` : move the hypothesis into the intuitionistic context.
+- `# ipat` : move the hypothesis into the intuitionistic context. The tactic
+  will fail if the hypothesis is not intuitionistic. On success, the tactic will
+  strip any number of intuitionistic and persistence modalities. If the
+  hypothesis is already in the intuitionistic context, the tactic will still
+  strip intuitionistic and persistence modalities (it is a no-op if the
+  hypothesis does not contain such modalities).
+- `-# ipat` : move the hypothesis from the intuitionistic context into the
+  spatial context. If the hypothesis is already in the spatial context, the
+  tactic is a no-op. If the hypothesis is not affine, an `<affine>` modality is
+  added to the hypothesis.
 - `> ipat` : eliminate a modality (if the goal permits).
 
 Apart from this, there are the following introduction patterns that can only
diff --git a/tests/proofmode.ref b/tests/proofmode.ref
index 93b90699c55e66edf9d87d38478d714bf882be8d..be894c20a79851c59c2fd6be4b9de8ebab79c358 100644
--- a/tests/proofmode.ref
+++ b/tests/proofmode.ref
@@ -54,6 +54,29 @@
   --------------------------------------â–¡
   <affine> (P ∗ Q)
   
+"test_iDestruct_spatial"
+     : string
+1 subgoal
+  
+  PROP : sbi
+  Q : PROP
+  ============================
+  "HQ" : <affine> Q
+  --------------------------------------∗
+  Q
+  
+"test_iDestruct_spatial_affine"
+     : string
+1 subgoal
+  
+  PROP : sbi
+  Q : PROP
+  Affine0 : Affine Q
+  ============================
+  "HQ" : Q
+  --------------------------------------∗
+  Q
+  
 The command has indeed failed with message:
 Ltac call to "done" failed.
 No applicable tactic.
diff --git a/tests/proofmode.v b/tests/proofmode.v
index b41edc481b33abee85bdff9663bdd11f1b8e4021..fc1e7df41d68ad0bb4bdb3f38cf5f4ca9d082344 100644
--- a/tests/proofmode.v
+++ b/tests/proofmode.v
@@ -90,6 +90,21 @@ Lemma test_iDestruct_intuitionistic_affine_bi `{!BiAffine PROP} P Q `{!Persisten
   Q ∗ (Q -∗ P) -∗ P ∗ Q.
 Proof. iIntros "[HQ HQP]". iDestruct ("HQP" with "HQ") as "#HP". by iFrame. Qed.
 
+Check "test_iDestruct_spatial".
+Lemma test_iDestruct_spatial Q : □ Q -∗ Q.
+Proof. iIntros "#HQ". iDestruct "HQ" as "-#HQ". Show. done. Qed.
+
+Check "test_iDestruct_spatial_affine".
+Lemma test_iDestruct_spatial_affine Q `{!Affine Q} : □ Q -∗ Q.
+Proof.
+  iIntros "#-#HQ".
+  (* Since [Q] is affine, it should not add an <affine> modality *)
+  Show. done.
+Qed.
+
+Lemma test_iDestruct_spatial_noop Q : Q -∗ Q.
+Proof. iIntros "-#HQ". done. Qed.
+
 Lemma test_iIntros_pure (ψ φ : Prop) P : ψ → (⌜ φ ⌝ → P → ⌜ φ ∧ ψ ⌝ ∧ P)%I.
 Proof. iIntros (??) "H". auto. Qed.
 
diff --git a/theories/proofmode/coq_tactics.v b/theories/proofmode/coq_tactics.v
index 50d3c69880b399ca0a05a319564bac9bcd051ef5..ef0c8d8c242a023923fd5e23a7d3fa064234cca8 100644
--- a/theories/proofmode/coq_tactics.v
+++ b/theories/proofmode/coq_tactics.v
@@ -182,6 +182,22 @@ Proof.
         absorbingly_sep_l wand_elim_r HQ.
 Qed.
 
+Lemma tac_spatial Δ i p P P' Q :
+  envs_lookup i Δ = Some (p, P) →
+  (if p then FromAffinely P' P else TCEq P' P) →
+  match envs_replace i p false (Esnoc Enil i P') Δ with
+  | None => False
+  | Some Δ' => envs_entails Δ' Q
+  end →
+  envs_entails Δ Q.
+Proof.
+  intros ? HP. destruct (envs_replace _ _ _ _ _) as [Δ'|] eqn:Hrep; last done.
+  rewrite envs_entails_eq=> <-. rewrite envs_replace_singleton_sound //; simpl.
+  destruct p; simpl; last destruct HP.
+  - by rewrite intuitionistically_affinely (from_affinely P' P) wand_elim_r.
+  - by rewrite wand_elim_r.
+Qed.
+
 (** * Implication and wand *)
 Lemma tac_impl_intro Δ i P P' Q R :
   FromImpl R P Q →
diff --git a/theories/proofmode/intro_patterns.v b/theories/proofmode/intro_patterns.v
index 1857119891e963c5349d74606f8d332a6b92bbc8..0227092dbdb2e55d7643f39d806b5cd3c9f869d2 100644
--- a/theories/proofmode/intro_patterns.v
+++ b/theories/proofmode/intro_patterns.v
@@ -10,6 +10,7 @@ Inductive intro_pat :=
   | IList : list (list intro_pat) → intro_pat
   | IPure : intro_pat
   | IIntuitionistic : intro_pat → intro_pat
+  | ISpatial : intro_pat → intro_pat
   | IModalElim : intro_pat → intro_pat
   | IRewrite : direction → intro_pat
   | IPureIntro : intro_pat
@@ -29,6 +30,7 @@ Inductive stack_item :=
   | StBar : stack_item
   | StAmp : stack_item
   | StIntuitionistic : stack_item
+  | StSpatial : stack_item
   | StModalElim : stack_item.
 Notation stack := (list stack_item).
 
@@ -63,6 +65,7 @@ Fixpoint close_conj_list (k : stack)
      Some (StPat (big_conj ps) :: k)
   | StPat pat :: k => guard (cur = None); close_conj_list k (Some pat) ps
   | StIntuitionistic :: k => p ← cur; close_conj_list k (Some (IIntuitionistic p)) ps
+  | StSpatial :: k => p ← cur; close_conj_list k (Some (ISpatial p)) ps
   | StModalElim :: k => p ← cur; close_conj_list k (Some (IModalElim p)) ps
   | StAmp :: k => p ← cur; close_conj_list k None (p :: ps)
   | _ => None
@@ -83,6 +86,7 @@ Fixpoint parse_go (ts : list token) (k : stack) : option stack :=
   | TParenR :: ts => close_conj_list k None [] ≫= parse_go ts
   | TPure :: ts => parse_go ts (StPat IPure :: k)
   | TIntuitionistic :: ts => parse_go ts (StIntuitionistic :: k)
+  | TMinus :: TIntuitionistic :: ts => parse_go ts (StSpatial :: k)
   | TModal :: ts => parse_go ts (StModalElim :: k)
   | TArrow d :: ts => parse_go ts (StPat (IRewrite d) :: k)
   | TPureIntro :: ts => parse_go ts (StPat IPureIntro :: k)
@@ -113,6 +117,7 @@ Fixpoint close (k : stack) (ps : list intro_pat) : option (list intro_pat) :=
   | [] => Some ps
   | StPat pat :: k => close k (pat :: ps)
   | StIntuitionistic :: k => ''(p,ps) ← maybe2 (::) ps; close k (IIntuitionistic p :: ps)
+  | StSpatial :: k => ''(p,ps) ← maybe2 (::) ps; close k (ISpatial p :: ps)
   | StModalElim :: k => ''(p,ps) ← maybe2 (::) ps; close k (IModalElim p :: ps)
   | _ => None
   end.
diff --git a/theories/proofmode/ltac_tactics.v b/theories/proofmode/ltac_tactics.v
index bf120ccd7c8c9e5cf1804eb03b4cffd6daf57050..493d2ebbbbda70e1602ff4b408f23912b3cdf4da 100644
--- a/theories/proofmode/ltac_tactics.v
+++ b/theories/proofmode/ltac_tactics.v
@@ -301,6 +301,14 @@ Local Tactic Notation "iIntuitionistic" constr(H) :=
      fail "iIntuitionistic:" P "not affine and the goal not absorbing"
     |pm_reduce].
 
+Local Tactic Notation "iSpatial" constr(H) :=
+  eapply tac_spatial with H _ _ _;
+    [pm_reflexivity ||
+     let H := pretty_ident H in
+     fail "iSpatial:" H "not found"
+    |pm_reduce; iSolveTC
+    |pm_reduce].
+
 Tactic Notation "iPure" constr(H) "as" simple_intropattern(pat) :=
   eapply tac_pure with H _ _ _; (* (i:=H1) *)
     [pm_reflexivity ||
@@ -1381,6 +1389,7 @@ Local Ltac iDestructHypGo Hz pat :=
   | IRewrite Right => iPure Hz as ->
   | IRewrite Left => iPure Hz as <-
   | IIntuitionistic ?pat => iIntuitionistic Hz; iDestructHypGo Hz pat
+  | ISpatial ?pat => iSpatial Hz; iDestructHypGo Hz pat
   | IModalElim ?pat => iModCore Hz; iDestructHypGo Hz pat
   | _ => fail "iDestruct:" pat "invalid"
   end.