diff --git a/README.md b/README.md
index e69ece0f..86a2267a 100644
--- a/README.md
+++ b/README.md
@@ -75,6 +75,7 @@ clear to read and to maintain.
- [`toBeChecked`](#tobechecked)
- [`toBePartiallyChecked`](#tobepartiallychecked)
- [`toHaveDescription`](#tohavedescription)
+ - [`toHaveErrorMessage`](#tohaveerrormessage)
- [Deprecated matchers](#deprecated-matchers)
- [`toBeInTheDOM`](#tobeinthedom)
- [Inspiration](#inspiration)
@@ -1042,6 +1043,58 @@ expect(deleteButton).not.toHaveDescription()
expect(deleteButton).toHaveDescription('') // Missing or empty description always becomes a blank string
```
+### `toHaveErrorMessage`
+
+```typescript
+toHaveErrorMessage(text: string | RegExp)
+```
+
+This allows you to check whether the given element has an
+[ARIA error message](https://www.w3.org/TR/wai-aria/#aria-errormessage) or not.
+
+Use the `aria-errormessage` attribute to reference another element that contains
+custom error message text. Multiple ids is **NOT** allowed. Authors MUST use
+`aria-invalid` in conjunction with `aria-errormessage`. Leran more from
+[`aria-errormessage` spec](https://www.w3.org/TR/wai-aria/#aria-errormessage).
+
+Whitespace is normalized.
+
+When a `string` argument is passed through, it will perform a whole
+case-sensitive match to the error message text.
+
+To perform a case-insensitive match, you can use a `RegExp` with the `/i`
+modifier.
+
+To perform a partial match, you can pass a `RegExp` or use
+`expect.stringContaining("partial string")`.
+
+#### Examples
+
+```html
+
+
+
+ Invalid time: the time must be between 9:00 AM and 5:00 PM"
+
+```
+
+```javascript
+const timeInput = getByLabel('startTime')
+
+expect(timeInput).toHaveErrorMessage(
+ 'Invalid time: the time must be between 9:00 AM and 5:00 PM',
+)
+expect(timeInput).toHaveErrorMessage(/invalid time/i) // to partially match
+expect(timeInput).toHaveErrorMessage(expect.stringContaining('Invalid time')) // to partially match
+expect(timeInput).not.toHaveErrorMessage('Pikachu!')
+```
+
## Deprecated matchers
### `toBeInTheDOM`
diff --git a/src/__tests__/to-have-errormessage.js b/src/__tests__/to-have-errormessage.js
new file mode 100644
index 00000000..68817653
--- /dev/null
+++ b/src/__tests__/to-have-errormessage.js
@@ -0,0 +1,206 @@
+import {render} from './helpers/test-utils'
+
+// eslint-disable-next-line max-lines-per-function
+describe('.toHaveErrorMessage', () => {
+ test('resolves for object with correct aria-errormessage reference', () => {
+ const {queryByTestId} = render(`
+
+
+ Invalid time: the time must be between 9:00 AM and 5:00 PM
+ `)
+
+ const timeInput = queryByTestId('startTime')
+
+ expect(timeInput).toHaveErrorMessage(
+ 'Invalid time: the time must be between 9:00 AM and 5:00 PM',
+ )
+ expect(timeInput).toHaveErrorMessage(/invalid time/i) // to partially match
+ expect(timeInput).toHaveErrorMessage(
+ expect.stringContaining('Invalid time'),
+ ) // to partially match
+ expect(timeInput).not.toHaveErrorMessage('Pikachu!')
+ })
+
+ test('works correctly on implicit invalid element', () => {
+ const {queryByTestId} = render(`
+
+
+ Invalid time: the time must be between 9:00 AM and 5:00 PM
+ `)
+
+ const timeInput = queryByTestId('startTime')
+
+ expect(timeInput).toHaveErrorMessage(
+ 'Invalid time: the time must be between 9:00 AM and 5:00 PM',
+ )
+ expect(timeInput).toHaveErrorMessage(/invalid time/i) // to partially match
+ expect(timeInput).toHaveErrorMessage(
+ expect.stringContaining('Invalid time'),
+ ) // to partially match
+ expect(timeInput).not.toHaveErrorMessage('Pikachu!')
+ })
+
+ test('rejects for valid object', () => {
+ const {queryByTestId} = render(`
+