diff --git a/.changeset/nine-mirrors-float.md b/.changeset/nine-mirrors-float.md
new file mode 100644
index 00000000..952d2057
--- /dev/null
+++ b/.changeset/nine-mirrors-float.md
@@ -0,0 +1,5 @@
+---
+'eslint-plugin-primer-react': major
+---
+
+Add `a11y-no-title-usage` that warns against using `title` in some components
diff --git a/docs/rules/a11y-no-title-usage.md b/docs/rules/a11y-no-title-usage.md
new file mode 100644
index 00000000..0339b07c
--- /dev/null
+++ b/docs/rules/a11y-no-title-usage.md
@@ -0,0 +1,40 @@
+## Rule Details
+
+This rule aims to prevent the use of the `title` attribute with some components from `@primer/react`. The `title` attribute is not keyboard accessible, which results in accessibility issues. Instead, we should utilize alternatives that are accessible.
+
+👎 Examples of **incorrect** code for this rule
+
+```jsx
+import {RelativeTime} from '@primer/react'
+
+const App = () =>
+```
+
+👍 Examples of **correct** code for this rule:
+
+```jsx
+import {RelativeTime} from '@primer/react'
+
+const App = () =>
+```
+
+The noTitle attribute is true by default, so it can be omitted.
+
+## With alternative tooltip
+
+If you want to still utilize a tooltip in a similar way to how the `title` attribute works, you can use the [Primer `Tooltip`](https://primer.style/components/tooltip/react/beta). If you use the `Tooltip` component, you must use it with an interactive element, such as with a button or a link.
+
+```jsx
+import {RelativeTime, Tooltip} from '@primer/react'
+
+const App = () => {
+ const date = new Date('2020-01-01T00:00:00Z')
+ return (
+
+
+
+
+
+ )
+}
+```
diff --git a/src/configs/recommended.js b/src/configs/recommended.js
index 2b247df4..e29c3c00 100644
--- a/src/configs/recommended.js
+++ b/src/configs/recommended.js
@@ -15,6 +15,7 @@ module.exports = {
'primer-react/a11y-tooltip-interactive-trigger': 'error',
'primer-react/new-color-css-vars': 'error',
'primer-react/a11y-explicit-heading': 'error',
+ 'primer-react/a11y-no-title-usage': 'error',
'primer-react/no-deprecated-props': 'warn',
'primer-react/a11y-remove-disable-tooltip': 'error',
'primer-react/a11y-use-accessible-tooltip': 'error',
diff --git a/src/index.js b/src/index.js
index 2acf7c88..9ba22767 100644
--- a/src/index.js
+++ b/src/index.js
@@ -10,6 +10,7 @@ module.exports = {
'a11y-link-in-text-block': require('./rules/a11y-link-in-text-block'),
'a11y-remove-disable-tooltip': require('./rules/a11y-remove-disable-tooltip'),
'a11y-use-accessible-tooltip': require('./rules/a11y-use-accessible-tooltip'),
+ 'a11y-no-title-usage': require('./rules/a11y-no-title-usage'),
'use-deprecated-from-deprecated': require('./rules/use-deprecated-from-deprecated'),
'no-wildcard-imports': require('./rules/no-wildcard-imports'),
'no-unnecessary-components': require('./rules/no-unnecessary-components'),
diff --git a/src/rules/__tests__/a11y-no-title-usage.test.js b/src/rules/__tests__/a11y-no-title-usage.test.js
new file mode 100644
index 00000000..04b418bc
--- /dev/null
+++ b/src/rules/__tests__/a11y-no-title-usage.test.js
@@ -0,0 +1,27 @@
+const rule = require('../a11y-no-title-usage')
+const {RuleTester} = require('eslint')
+
+const ruleTester = new RuleTester({
+ parserOptions: {
+ ecmaVersion: 'latest',
+ sourceType: 'module',
+ ecmaFeatures: {
+ jsx: true,
+ },
+ },
+})
+
+ruleTester.run('a11y-no-title-usage', rule, {
+ valid: [
+ ``,
+ ``,
+ ``,
+ ],
+ invalid: [
+ {
+ code: ``,
+ output: ``,
+ errors: [{messageId: 'noTitleOnRelativeTime'}],
+ },
+ ],
+})
diff --git a/src/rules/a11y-no-title-usage.js b/src/rules/a11y-no-title-usage.js
new file mode 100644
index 00000000..53ef25f8
--- /dev/null
+++ b/src/rules/a11y-no-title-usage.js
@@ -0,0 +1,37 @@
+const url = require('../url')
+const {getJSXOpeningElementAttribute} = require('../utils/get-jsx-opening-element-attribute')
+
+module.exports = {
+ meta: {
+ type: 'error',
+ docs: {
+ description: 'Disallow usage of title attribute on some components',
+ recommended: true,
+ url: url(module),
+ },
+ messages: {
+ noTitleOnRelativeTime: 'Avoid using the title attribute on RelativeTime.',
+ },
+ fixable: 'code',
+ },
+
+ create(context) {
+ return {
+ JSXOpeningElement(jsxNode) {
+ const title = getJSXOpeningElementAttribute(jsxNode, 'noTitle')
+
+ if (title && title.value && title.value.expression && title.value.expression.value !== true) {
+ context.report({
+ node: title,
+ messageId: 'noTitleOnRelativeTime',
+ fix(fixer) {
+ const start = title.range[0] - 1
+ const end = title.range[1]
+ return fixer.removeRange([start, end])
+ },
+ })
+ }
+ },
+ }
+ },
+}