diff --git a/docs/reference/slate/change.md b/docs/reference/slate/change.md
index f9e91d6ec..e82e0d7bf 100644
--- a/docs/reference/slate/change.md
+++ b/docs/reference/slate/change.md
@@ -192,6 +192,14 @@ Split the [`Inline`](./inline.md) node in the current selection by `depth` level
Remove a [`mark`](./mark.md) from the characters in the current selection. For convenience, you can pass a `type` string or `properties` object to implicitly create a [`Mark`](./mark.md) of that type.
+### `replaceMark`
+
+`replaceMark(oldMark: Mark, newMark: Mark) => Change`
+`replaceMark(oldProperties: Object, newProperties: Object) => Change`
+`replaceMark(oldType: String, newType: String) => Change`
+
+Replace a [`mark`](./mark.md) in the characters in the current selection. For convenience, you can pass a `type` string or `properties` object to implicitly create a [`Mark`](./mark.md) of that type.
+
### `toggleMark`
`toggleMark(mark: Mark) => Change`
diff --git a/packages/slate/src/changes/at-current-range.js b/packages/slate/src/changes/at-current-range.js
index bee3fc7f8..f898a46a3 100644
--- a/packages/slate/src/changes/at-current-range.js
+++ b/packages/slate/src/changes/at-current-range.js
@@ -264,6 +264,19 @@ Changes.removeMark = (change, mark) => {
}
}
+/**
+ * Replace an `oldMark` with a `newMark` in the characters in the current selection.
+ *
+ * @param {Change} change
+ * @param {Mark} oldMark
+ * @param {Mark} newMark
+ */
+
+Changes.replaceMark = (change, oldMark, newMark) => {
+ change.removeMark(oldMark)
+ change.addMark(newMark)
+}
+
/**
* Add or remove a `mark` from the characters in the current selection,
* depending on whether it's already there.
diff --git a/packages/slate/test/changes/at-current-range/replace-mark/across-blocks.js b/packages/slate/test/changes/at-current-range/replace-mark/across-blocks.js
new file mode 100644
index 000000000..3252075c4
--- /dev/null
+++ b/packages/slate/test/changes/at-current-range/replace-mark/across-blocks.js
@@ -0,0 +1,37 @@
+/** @jsx h */
+
+import h from '../../../helpers/h'
+
+export default function(change) {
+ change.replaceMark('italic', 'bold')
+}
+
+export const input = (
+
+
+
+ wo
+ rd
+
+
+ an
+ other
+
+
+
+)
+
+export const output = (
+
+
+
+ wo
+ rd
+
+
+ an
+ other
+
+
+
+)
diff --git a/packages/slate/test/changes/at-current-range/replace-mark/across-inlines.js b/packages/slate/test/changes/at-current-range/replace-mark/across-inlines.js
new file mode 100644
index 000000000..8ea3f0dd7
--- /dev/null
+++ b/packages/slate/test/changes/at-current-range/replace-mark/across-inlines.js
@@ -0,0 +1,49 @@
+/** @jsx h */
+
+import h from '../../../helpers/h'
+
+export default function(change) {
+ change.replaceMark('italic', 'bold')
+}
+
+export const input = (
+
+
+
+
+ wo
+ rd
+
+
+
+
+
+
+ an
+ other
+
+
+
+
+)
+
+export const output = (
+
+
+
+
+ wo
+ rd
+
+
+
+
+
+
+ an
+ other
+
+
+
+
+)
diff --git a/packages/slate/test/changes/at-current-range/replace-mark/collapsed-selection.js b/packages/slate/test/changes/at-current-range/replace-mark/collapsed-selection.js
new file mode 100644
index 000000000..e34a739aa
--- /dev/null
+++ b/packages/slate/test/changes/at-current-range/replace-mark/collapsed-selection.js
@@ -0,0 +1,29 @@
+/** @jsx h */
+
+import h from '../../../helpers/h'
+
+export default function(change) {
+ change.replaceMark('italic', 'bold').insertText('a')
+}
+
+export const input = (
+
+
+
+
+ word
+
+
+
+)
+
+export const output = (
+
+
+
+ a
+ word
+
+
+
+)
diff --git a/packages/slate/test/changes/at-current-range/replace-mark/existing-marks.js b/packages/slate/test/changes/at-current-range/replace-mark/existing-marks.js
new file mode 100644
index 000000000..867641f3e
--- /dev/null
+++ b/packages/slate/test/changes/at-current-range/replace-mark/existing-marks.js
@@ -0,0 +1,33 @@
+/** @jsx h */
+
+import h from '../../../helpers/h'
+
+export default function(change) {
+ change.replaceMark('italic', 'bold')
+}
+
+export const input = (
+
+
+
+
+
+ word
+
+
+
+
+)
+
+export const output = (
+
+
+
+
+ wo
+
+ rd
+
+
+
+)
diff --git a/packages/slate/test/changes/at-current-range/replace-mark/first-character.js b/packages/slate/test/changes/at-current-range/replace-mark/first-character.js
new file mode 100644
index 000000000..c005a36fd
--- /dev/null
+++ b/packages/slate/test/changes/at-current-range/replace-mark/first-character.js
@@ -0,0 +1,31 @@
+/** @jsx h */
+
+import h from '../../../helpers/h'
+
+export default function(change) {
+ change.replaceMark('italic', 'bold')
+}
+
+export const input = (
+
+
+
+
+ w
+ ord
+
+
+
+)
+
+export const output = (
+
+
+
+
+ w
+ ord
+
+
+
+)
diff --git a/packages/slate/test/changes/at-current-range/replace-mark/last-character.js b/packages/slate/test/changes/at-current-range/replace-mark/last-character.js
new file mode 100644
index 000000000..884f0f3bf
--- /dev/null
+++ b/packages/slate/test/changes/at-current-range/replace-mark/last-character.js
@@ -0,0 +1,31 @@
+/** @jsx h */
+
+import h from '../../../helpers/h'
+
+export default function(change) {
+ change.replaceMark('italic', 'bold')
+}
+
+export const input = (
+
+
+
+ wor
+ d
+
+
+
+
+)
+
+export const output = (
+
+
+
+ wor
+ d
+
+
+
+
+)
diff --git a/packages/slate/test/changes/at-current-range/replace-mark/middle-character.js b/packages/slate/test/changes/at-current-range/replace-mark/middle-character.js
new file mode 100644
index 000000000..eb191a9c4
--- /dev/null
+++ b/packages/slate/test/changes/at-current-range/replace-mark/middle-character.js
@@ -0,0 +1,31 @@
+/** @jsx h */
+
+import h from '../../../helpers/h'
+
+export default function(change) {
+ change.replaceMark('italic', 'bold')
+}
+
+export const input = (
+
+
+
+ w
+ o
+ rd
+
+
+
+)
+
+export const output = (
+
+
+
+ w
+ o
+ rd
+
+
+
+)
diff --git a/packages/slate/test/changes/at-current-range/replace-mark/whole-word.js b/packages/slate/test/changes/at-current-range/replace-mark/whole-word.js
new file mode 100644
index 000000000..41bbda069
--- /dev/null
+++ b/packages/slate/test/changes/at-current-range/replace-mark/whole-word.js
@@ -0,0 +1,31 @@
+/** @jsx h */
+
+import h from '../../../helpers/h'
+
+export default function(change) {
+ change.replaceMark('italic', 'bold')
+}
+
+export const input = (
+
+
+
+
+ word
+
+
+
+
+)
+
+export const output = (
+
+
+
+
+ word
+
+
+
+
+)
diff --git a/packages/slate/test/changes/at-current-range/replace-mark/with-mark-object.js b/packages/slate/test/changes/at-current-range/replace-mark/with-mark-object.js
new file mode 100644
index 000000000..7a528c18f
--- /dev/null
+++ b/packages/slate/test/changes/at-current-range/replace-mark/with-mark-object.js
@@ -0,0 +1,39 @@
+/** @jsx h */
+
+import h from '../../../helpers/h'
+
+import { Mark } from '../../../..'
+
+export default function(change) {
+ change.replaceMark(
+ 'italic',
+ Mark.create({
+ type: 'bold',
+ data: { thing: 'value' },
+ })
+ )
+}
+
+export const input = (
+
+
+
+
+ w
+ ord
+
+
+
+)
+
+export const output = (
+
+
+
+
+ w
+ ord
+
+
+
+)
diff --git a/packages/slate/test/changes/at-current-range/replace-mark/with-plain-object.js b/packages/slate/test/changes/at-current-range/replace-mark/with-plain-object.js
new file mode 100644
index 000000000..65d5d4910
--- /dev/null
+++ b/packages/slate/test/changes/at-current-range/replace-mark/with-plain-object.js
@@ -0,0 +1,34 @@
+/** @jsx h */
+
+import h from '../../../helpers/h'
+
+export default function(change) {
+ change.replaceMark('italic', {
+ type: 'bold',
+ data: { thing: 'value' },
+ })
+}
+
+export const input = (
+
+
+
+
+ w
+ ord
+
+
+
+)
+
+export const output = (
+
+
+
+
+ w
+ ord
+
+
+
+)