From 2e175c24008324a281da46f02e28107b7772346c Mon Sep 17 00:00:00 2001 From: Tamerlan Gudabayev Date: Mon, 27 Apr 2026 14:20:30 +0200 Subject: [PATCH 01/13] Change `href-or-on-click` rule to require `href` when `onClick` is present Instead of warning when both `href` and `onClick` are supplied, the rule now warns when `onClick` is used without `href` on link-like EUI components, encouraging proper link semantics so that Cmd+Click / "Open in new tab" works. --- .../src/rules/href_or_on_click.test.ts | 39 ++++++++++++++++--- .../src/rules/href_or_on_click.ts | 12 +++--- 2 files changed, 40 insertions(+), 11 deletions(-) diff --git a/packages/eslint-plugin/src/rules/href_or_on_click.test.ts b/packages/eslint-plugin/src/rules/href_or_on_click.test.ts index a24700b091ce..1f43b3c6564f 100644 --- a/packages/eslint-plugin/src/rules/href_or_on_click.test.ts +++ b/packages/eslint-plugin/src/rules/href_or_on_click.test.ts @@ -54,7 +54,7 @@ ruleTester.run('href-or-on-click', HrefOnClick, { { code: dedent` module.export = () => ( - + ) `, languageOptions, @@ -62,7 +62,23 @@ ruleTester.run('href-or-on-click', HrefOnClick, { { code: dedent` module.export = () => ( - executeAction()} /> + + ) + `, + languageOptions, + }, + { + code: dedent` + module.export = () => ( + + ) + `, + languageOptions, + }, + { + code: dedent` + module.export = () => ( + ) `, languageOptions, @@ -73,7 +89,20 @@ ruleTester.run('href-or-on-click', HrefOnClick, { { code: dedent` module.export = () => ( - + + ) + `, + languageOptions, + errors: [ + { + messageId: 'hrefOrOnClick', + }, + ], + }, + { + code: dedent` + module.export = () => ( + ) `, languageOptions, @@ -86,7 +115,7 @@ ruleTester.run('href-or-on-click', HrefOnClick, { { code: dedent` module.export = () => ( - + ) `, languageOptions, @@ -99,7 +128,7 @@ ruleTester.run('href-or-on-click', HrefOnClick, { { code: dedent` module.export = () => ( - + ) `, languageOptions, diff --git a/packages/eslint-plugin/src/rules/href_or_on_click.ts b/packages/eslint-plugin/src/rules/href_or_on_click.ts index 0fb76c1ae748..5514688e1029 100644 --- a/packages/eslint-plugin/src/rules/href_or_on_click.ts +++ b/packages/eslint-plugin/src/rules/href_or_on_click.ts @@ -22,7 +22,7 @@ export const HrefOnClick = ESLintUtils.RuleCreator.withoutDocs({ return; } - // Check if the node has both `href` and `onClick` attributes + // Check if the node has `href` and `onClick` attributes const hasHref = node.attributes.some( (attr) => attr.type === 'JSXAttribute' && attr.name.name === 'href' ); @@ -30,8 +30,8 @@ export const HrefOnClick = ESLintUtils.RuleCreator.withoutDocs({ (attr) => attr.type === 'JSXAttribute' && attr.name.name === 'onClick' ); - // Report an issue if both attributes are present - if (hasHref && hasOnClick) { + // Report an issue if `onClick` is present without `href` + if (hasOnClick && !hasHref) { context.report({ node, messageId: 'hrefOrOnClick', @@ -44,15 +44,15 @@ export const HrefOnClick = ESLintUtils.RuleCreator.withoutDocs({ }; }, meta: { - type: 'problem', + type: 'suggestion', docs: { description: - 'Discourage supplying both `href` and `onClick` to certain EUI components.', + 'Recommend including `href` alongside `onClick` on link-like EUI components so that Cmd+Click (open in new tab) works.', }, schema: [], messages: { hrefOrOnClick: - '<{{name}}> supplied with both `href` and `onClick`; is this intentional? (Valid use cases include programmatic navigation via `onClick` while preserving "Open in new tab" style functionality via `href`.)', + '<{{name}}> has `onClick` but no `href`. Consider adding an `href` so that the component renders as a link and supports Cmd+Click / "Open in new tab".', }, }, defaultOptions: [], From c8ee8ac66d290736fbc1a79f433159da5089b913 Mon Sep 17 00:00:00 2001 From: Tamerlan Gudabayev Date: Mon, 27 Apr 2026 14:44:36 +0200 Subject: [PATCH 02/13] Bail out of href-or-on-click rule when spread attributes are present When props are spread (e.g. ``), we can't statically know if `href` is provided, so skip reporting to avoid false positives. Uses the existing `hasSpread` utility. --- packages/eslint-plugin/src/rules/href_or_on_click.test.ts | 8 ++++++++ packages/eslint-plugin/src/rules/href_or_on_click.ts | 8 ++++++++ 2 files changed, 16 insertions(+) diff --git a/packages/eslint-plugin/src/rules/href_or_on_click.test.ts b/packages/eslint-plugin/src/rules/href_or_on_click.test.ts index 1f43b3c6564f..61d7879a6ead 100644 --- a/packages/eslint-plugin/src/rules/href_or_on_click.test.ts +++ b/packages/eslint-plugin/src/rules/href_or_on_click.test.ts @@ -83,6 +83,14 @@ ruleTester.run('href-or-on-click', HrefOnClick, { `, languageOptions, }, + { + code: dedent` + module.export = () => ( + + ) + `, + languageOptions, + }, ], invalid: [ diff --git a/packages/eslint-plugin/src/rules/href_or_on_click.ts b/packages/eslint-plugin/src/rules/href_or_on_click.ts index 5514688e1029..28aa58d58a2e 100644 --- a/packages/eslint-plugin/src/rules/href_or_on_click.ts +++ b/packages/eslint-plugin/src/rules/href_or_on_click.ts @@ -8,6 +8,8 @@ import { TSESTree, ESLintUtils } from '@typescript-eslint/utils'; +import { hasSpread } from '../utils/has_spread'; + const componentNames = ['EuiButton', 'EuiButtonEmpty', 'EuiLink', 'EuiBadge']; export const HrefOnClick = ESLintUtils.RuleCreator.withoutDocs({ @@ -22,6 +24,12 @@ export const HrefOnClick = ESLintUtils.RuleCreator.withoutDocs({ return; } + // Bail out if props are spread — we can't statically determine + // whether `href` is provided via the spread + if (hasSpread(node.attributes)) { + return; + } + // Check if the node has `href` and `onClick` attributes const hasHref = node.attributes.some( (attr) => attr.type === 'JSXAttribute' && attr.name.name === 'href' From f013d196116c62924e94deaff32edf791842df74 Mon Sep 17 00:00:00 2001 From: Tamerlan Gudabayev Date: Mon, 27 Apr 2026 14:46:48 +0200 Subject: [PATCH 03/13] Use OS-agnostic wording in href-or-on-click rule message Replace "Cmd+Click" with "Ctrl/Cmd+Click" and "Open in new tab" with "Open link in new tab" so the message is accurate on all platforms. --- packages/eslint-plugin/src/rules/href_or_on_click.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/eslint-plugin/src/rules/href_or_on_click.ts b/packages/eslint-plugin/src/rules/href_or_on_click.ts index 28aa58d58a2e..1aafad1ae648 100644 --- a/packages/eslint-plugin/src/rules/href_or_on_click.ts +++ b/packages/eslint-plugin/src/rules/href_or_on_click.ts @@ -55,12 +55,12 @@ export const HrefOnClick = ESLintUtils.RuleCreator.withoutDocs({ type: 'suggestion', docs: { description: - 'Recommend including `href` alongside `onClick` on link-like EUI components so that Cmd+Click (open in new tab) works.', + 'Recommend including `href` alongside `onClick` on link-like EUI components so that Ctrl/Cmd+Click (open link in new tab) works.', }, schema: [], messages: { hrefOrOnClick: - '<{{name}}> has `onClick` but no `href`. Consider adding an `href` so that the component renders as a link and supports Cmd+Click / "Open in new tab".', + '<{{name}}> has `onClick` but no `href`. Consider adding an `href` so that the component renders as a link and supports Ctrl/Cmd+Click / "Open link in new tab".', }, }, defaultOptions: [], From 17de41654ace54004d21479149f1f0e07084223d Mon Sep 17 00:00:00 2001 From: Tamerlan Gudabayev Date: Mon, 27 Apr 2026 15:50:16 +0200 Subject: [PATCH 04/13] Add `require-href-for-link` rule for EuiLink and revert `href-or-on-click` Revert `href-or-on-click` to its original behavior (warn when both `href` and `onClick` are present). Extract the new logic into a separate `require-href-for-link` rule that only targets EuiLink and warns when `onClick` is used without `href`. --- packages/eslint-plugin/src/index.ts | 5 +- .../src/rules/href_or_on_click.test.ts | 47 ++--------- .../src/rules/href_or_on_click.ts | 20 ++--- .../src/rules/require_href_for_link.test.ts | 83 +++++++++++++++++++ .../src/rules/require_href_for_link.ts | 66 +++++++++++++++ 5 files changed, 164 insertions(+), 57 deletions(-) create mode 100644 packages/eslint-plugin/src/rules/require_href_for_link.test.ts create mode 100644 packages/eslint-plugin/src/rules/require_href_for_link.ts diff --git a/packages/eslint-plugin/src/index.ts b/packages/eslint-plugin/src/index.ts index 849f9951008b..26e4af1e89e3 100644 --- a/packages/eslint-plugin/src/index.ts +++ b/packages/eslint-plugin/src/index.ts @@ -11,6 +11,7 @@ import { AccessibleInteractiveElements } from './rules/a11y/accessible_interacti import { CallOutAnnounceOnMount } from './rules/a11y/callout_announce_on_mount'; import { ConsistentIsInvalidProps } from './rules/a11y/consistent_is_invalid_props'; import { HrefOnClick } from './rules/href_or_on_click'; +import { RequireHrefForLink } from './rules/require_href_for_link'; import { NoCssColor } from './rules/no_css_color'; import { NoRestrictedEuiImports } from './rules/no_restricted_eui_imports'; import { NoStaticZIndex } from './rules/no_static_z_index'; @@ -41,7 +42,8 @@ const config = { 'sr-output-disabled-tooltip': ScreenReaderOutputDisabledTooltip, 'tooltip-focusable-anchor': TooltipFocusableAnchor, 'badge-accessibility-rules': EuiBadgeAccessibilityRules, - 'icon-accessibility-rules': EuiIconAccessibilityRules + 'icon-accessibility-rules': EuiIconAccessibilityRules, + 'require-href-for-link': RequireHrefForLink, }, configs: { recommended: { @@ -63,6 +65,7 @@ const config = { '@elastic/eui/tooltip-focusable-anchor': 'warn', '@elastic/eui/badge-accessibility-rules': 'warn', '@elastic/eui/icon-accessibility-rules': 'warn', + '@elastic/eui/require-href-for-link': 'warn', }, }, }, diff --git a/packages/eslint-plugin/src/rules/href_or_on_click.test.ts b/packages/eslint-plugin/src/rules/href_or_on_click.test.ts index 61d7879a6ead..a24700b091ce 100644 --- a/packages/eslint-plugin/src/rules/href_or_on_click.test.ts +++ b/packages/eslint-plugin/src/rules/href_or_on_click.test.ts @@ -54,7 +54,7 @@ ruleTester.run('href-or-on-click', HrefOnClick, { { code: dedent` module.export = () => ( - + ) `, languageOptions, @@ -62,31 +62,7 @@ ruleTester.run('href-or-on-click', HrefOnClick, { { code: dedent` module.export = () => ( - - ) - `, - languageOptions, - }, - { - code: dedent` - module.export = () => ( - - ) - `, - languageOptions, - }, - { - code: dedent` - module.export = () => ( - - ) - `, - languageOptions, - }, - { - code: dedent` - module.export = () => ( - + executeAction()} /> ) `, languageOptions, @@ -97,20 +73,7 @@ ruleTester.run('href-or-on-click', HrefOnClick, { { code: dedent` module.export = () => ( - - ) - `, - languageOptions, - errors: [ - { - messageId: 'hrefOrOnClick', - }, - ], - }, - { - code: dedent` - module.export = () => ( - + ) `, languageOptions, @@ -123,7 +86,7 @@ ruleTester.run('href-or-on-click', HrefOnClick, { { code: dedent` module.export = () => ( - + ) `, languageOptions, @@ -136,7 +99,7 @@ ruleTester.run('href-or-on-click', HrefOnClick, { { code: dedent` module.export = () => ( - + ) `, languageOptions, diff --git a/packages/eslint-plugin/src/rules/href_or_on_click.ts b/packages/eslint-plugin/src/rules/href_or_on_click.ts index 1aafad1ae648..0fb76c1ae748 100644 --- a/packages/eslint-plugin/src/rules/href_or_on_click.ts +++ b/packages/eslint-plugin/src/rules/href_or_on_click.ts @@ -8,8 +8,6 @@ import { TSESTree, ESLintUtils } from '@typescript-eslint/utils'; -import { hasSpread } from '../utils/has_spread'; - const componentNames = ['EuiButton', 'EuiButtonEmpty', 'EuiLink', 'EuiBadge']; export const HrefOnClick = ESLintUtils.RuleCreator.withoutDocs({ @@ -24,13 +22,7 @@ export const HrefOnClick = ESLintUtils.RuleCreator.withoutDocs({ return; } - // Bail out if props are spread — we can't statically determine - // whether `href` is provided via the spread - if (hasSpread(node.attributes)) { - return; - } - - // Check if the node has `href` and `onClick` attributes + // Check if the node has both `href` and `onClick` attributes const hasHref = node.attributes.some( (attr) => attr.type === 'JSXAttribute' && attr.name.name === 'href' ); @@ -38,8 +30,8 @@ export const HrefOnClick = ESLintUtils.RuleCreator.withoutDocs({ (attr) => attr.type === 'JSXAttribute' && attr.name.name === 'onClick' ); - // Report an issue if `onClick` is present without `href` - if (hasOnClick && !hasHref) { + // Report an issue if both attributes are present + if (hasHref && hasOnClick) { context.report({ node, messageId: 'hrefOrOnClick', @@ -52,15 +44,15 @@ export const HrefOnClick = ESLintUtils.RuleCreator.withoutDocs({ }; }, meta: { - type: 'suggestion', + type: 'problem', docs: { description: - 'Recommend including `href` alongside `onClick` on link-like EUI components so that Ctrl/Cmd+Click (open link in new tab) works.', + 'Discourage supplying both `href` and `onClick` to certain EUI components.', }, schema: [], messages: { hrefOrOnClick: - '<{{name}}> has `onClick` but no `href`. Consider adding an `href` so that the component renders as a link and supports Ctrl/Cmd+Click / "Open link in new tab".', + '<{{name}}> supplied with both `href` and `onClick`; is this intentional? (Valid use cases include programmatic navigation via `onClick` while preserving "Open in new tab" style functionality via `href`.)', }, }, defaultOptions: [], diff --git a/packages/eslint-plugin/src/rules/require_href_for_link.test.ts b/packages/eslint-plugin/src/rules/require_href_for_link.test.ts new file mode 100644 index 000000000000..2fb6b8ad342c --- /dev/null +++ b/packages/eslint-plugin/src/rules/require_href_for_link.test.ts @@ -0,0 +1,83 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import dedent from 'dedent'; +import { RuleTester } from '@typescript-eslint/rule-tester'; + +import { RequireHrefForLink } from './require_href_for_link'; + +const languageOptions = { + parserOptions: { + ecmaFeatures: { + jsx: true, + }, + }, +}; + +const ruleTester = new RuleTester(); + +ruleTester.run('require-href-for-link', RequireHrefForLink, { + valid: [ + { + code: dedent` + module.export = () => ( + + ) + `, + languageOptions, + }, + { + code: dedent` + module.export = () => ( + + ) + `, + languageOptions, + }, + { + code: dedent` + module.export = () => ( + + ) + `, + languageOptions, + }, + { + code: dedent` + module.export = () => ( + + ) + `, + languageOptions, + }, + { + code: dedent` + module.export = () => ( + + ) + `, + languageOptions, + }, + ], + + invalid: [ + { + code: dedent` + module.export = () => ( + + ) + `, + languageOptions, + errors: [ + { + messageId: 'requireHrefForLink', + }, + ], + }, + ], +}); diff --git a/packages/eslint-plugin/src/rules/require_href_for_link.ts b/packages/eslint-plugin/src/rules/require_href_for_link.ts new file mode 100644 index 000000000000..a9f8fcf8d738 --- /dev/null +++ b/packages/eslint-plugin/src/rules/require_href_for_link.ts @@ -0,0 +1,66 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { TSESTree, ESLintUtils } from '@typescript-eslint/utils'; + +import { hasSpread } from '../utils/has_spread'; + +const componentNames = ['EuiLink']; + +export const RequireHrefForLink = ESLintUtils.RuleCreator.withoutDocs({ + create(context) { + return { + JSXOpeningElement(node: TSESTree.JSXOpeningElement): void { + if ( + node.name.type !== 'JSXIdentifier' || + !componentNames.includes(node.name.name) + ) { + return; + } + + // Bail out if props are spread — we can't statically determine + // whether `href` is provided via the spread + if (hasSpread(node.attributes)) { + return; + } + + // Check if the node has `href` and `onClick` attributes + const hasHref = node.attributes.some( + (attr) => attr.type === 'JSXAttribute' && attr.name.name === 'href' + ); + const hasOnClick = node.attributes.some( + (attr) => attr.type === 'JSXAttribute' && attr.name.name === 'onClick' + ); + + // Report an issue if `onClick` is present without `href` + if (hasOnClick && !hasHref) { + context.report({ + node, + messageId: 'requireHrefForLink', + data: { + name: node.name.name, + }, + }); + } + }, + }; + }, + meta: { + type: 'suggestion', + docs: { + description: + 'Recommend including `href` alongside `onClick` on EuiLink so that Ctrl/Cmd+Click (open link in new tab) works.', + }, + schema: [], + messages: { + requireHrefForLink: + '<{{name}}> has `onClick` but no `href`. Consider adding an `href` so that the component renders as a link and supports Ctrl/Cmd+Click / "Open link in new tab".', + }, + }, + defaultOptions: [], +}); From edf36c34f2342fb2f8166cdffd50131df6cb03bb Mon Sep 17 00:00:00 2001 From: Tamerlan Gudabayev Date: Mon, 27 Apr 2026 15:55:50 +0200 Subject: [PATCH 05/13] refactor: remove eui link from href_or_on_click rule --- packages/eslint-plugin/src/rules/href_or_on_click.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/eslint-plugin/src/rules/href_or_on_click.ts b/packages/eslint-plugin/src/rules/href_or_on_click.ts index 0fb76c1ae748..a39ca14f02bc 100644 --- a/packages/eslint-plugin/src/rules/href_or_on_click.ts +++ b/packages/eslint-plugin/src/rules/href_or_on_click.ts @@ -8,7 +8,7 @@ import { TSESTree, ESLintUtils } from '@typescript-eslint/utils'; -const componentNames = ['EuiButton', 'EuiButtonEmpty', 'EuiLink', 'EuiBadge']; +const componentNames = ['EuiButton', 'EuiButtonEmpty', 'EuiBadge']; export const HrefOnClick = ESLintUtils.RuleCreator.withoutDocs({ create(context) { From 40fd5f351166ed136dbe83f60ef0c88a0d1fa3d4 Mon Sep 17 00:00:00 2001 From: Tamerlan Gudabayev Date: Mon, 27 Apr 2026 15:57:25 +0200 Subject: [PATCH 06/13] remove eui link test --- .../src/rules/href_or_on_click.test.ts | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/packages/eslint-plugin/src/rules/href_or_on_click.test.ts b/packages/eslint-plugin/src/rules/href_or_on_click.test.ts index a24700b091ce..34c348ac27bb 100644 --- a/packages/eslint-plugin/src/rules/href_or_on_click.test.ts +++ b/packages/eslint-plugin/src/rules/href_or_on_click.test.ts @@ -96,18 +96,5 @@ ruleTester.run('href-or-on-click', HrefOnClick, { }, ], }, - { - code: dedent` - module.export = () => ( - - ) - `, - languageOptions, - errors: [ - { - messageId: 'hrefOrOnClick', - }, - ], - }, ], }); From 5dc6e085d5e59ca33db2ba55f017c6defcf4beaf Mon Sep 17 00:00:00 2001 From: Tamerlan Gudabayev Date: Mon, 27 Apr 2026 15:59:20 +0200 Subject: [PATCH 07/13] Simplify require-href-for-link to compare against EuiLink directly --- packages/eslint-plugin/src/rules/require_href_for_link.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/eslint-plugin/src/rules/require_href_for_link.ts b/packages/eslint-plugin/src/rules/require_href_for_link.ts index a9f8fcf8d738..dff51bbba3eb 100644 --- a/packages/eslint-plugin/src/rules/require_href_for_link.ts +++ b/packages/eslint-plugin/src/rules/require_href_for_link.ts @@ -10,15 +10,13 @@ import { TSESTree, ESLintUtils } from '@typescript-eslint/utils'; import { hasSpread } from '../utils/has_spread'; -const componentNames = ['EuiLink']; - export const RequireHrefForLink = ESLintUtils.RuleCreator.withoutDocs({ create(context) { return { JSXOpeningElement(node: TSESTree.JSXOpeningElement): void { if ( node.name.type !== 'JSXIdentifier' || - !componentNames.includes(node.name.name) + node.name.name !== 'EuiLink' ) { return; } From 20768c6b06632b1ea26a072c1cc7f30bd20e6186 Mon Sep 17 00:00:00 2001 From: Tamerlan Gudabayev Date: Tue, 28 Apr 2026 23:34:55 +0200 Subject: [PATCH 08/13] Add README docs and CHANGELOG entry for require-href-for-link rule --- packages/eslint-plugin/README.md | 4 ++++ packages/eslint-plugin/changelogs/CHANGELOG_2026.md | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/packages/eslint-plugin/README.md b/packages/eslint-plugin/README.md index ca2a6659bad0..7b9688f82ce2 100644 --- a/packages/eslint-plugin/README.md +++ b/packages/eslint-plugin/README.md @@ -175,6 +175,10 @@ Ensure the `EuiBadge` includes appropriate accessibility attributes. - `iconOnClickAriaLabel` is only valid when `iconOnClick` is present. The rule autofixes by removing `iconOnClickAriaLabel`. - `onClickAriaLabel` is only valid when `onClick` is present. The rule autofixes by removing `onClickAriaLabel`. +### `@elastic/eui/require-href-for-link` + +Ensure `EuiLink` components that have an `onClick` handler also include an `href` prop. Without `href`, the component does not render as a true link, which means users cannot Ctrl/Cmd+Click to open in a new tab or use other standard link interactions. The rule bails out when spread attributes are present, since `href` may be provided via the spread. + ### `@elastic/eui/icon-accessibility-rules` Ensure the `EuiIcon` includes appropriate accessibility attributes. diff --git a/packages/eslint-plugin/changelogs/CHANGELOG_2026.md b/packages/eslint-plugin/changelogs/CHANGELOG_2026.md index 6d596f9193ca..5744fdfd350e 100644 --- a/packages/eslint-plugin/changelogs/CHANGELOG_2026.md +++ b/packages/eslint-plugin/changelogs/CHANGELOG_2026.md @@ -1,3 +1,7 @@ +## [`v2.12.0`](https://github.com/elastic/eui/releases/v2.12.0) + +- Added new `require-href-for-link` rule. ([#9615](https://github.com/elastic/eui/pull/9615)) + ## [`v2.11.1`](https://github.com/elastic/eui/releases/v2.11.1) **Bug fixes** From 824f8bda4ef902e29ff441f8b93c6da8a9778fe1 Mon Sep 17 00:00:00 2001 From: Tamerlan Gudabayev Date: Tue, 28 Apr 2026 23:45:54 +0200 Subject: [PATCH 09/13] fix changelog --- packages/eslint-plugin/changelogs/CHANGELOG_2026.md | 4 ---- packages/eslint-plugin/changelogs/upcomings/9615.md | 3 +++ 2 files changed, 3 insertions(+), 4 deletions(-) create mode 100644 packages/eslint-plugin/changelogs/upcomings/9615.md diff --git a/packages/eslint-plugin/changelogs/CHANGELOG_2026.md b/packages/eslint-plugin/changelogs/CHANGELOG_2026.md index 5744fdfd350e..6d596f9193ca 100644 --- a/packages/eslint-plugin/changelogs/CHANGELOG_2026.md +++ b/packages/eslint-plugin/changelogs/CHANGELOG_2026.md @@ -1,7 +1,3 @@ -## [`v2.12.0`](https://github.com/elastic/eui/releases/v2.12.0) - -- Added new `require-href-for-link` rule. ([#9615](https://github.com/elastic/eui/pull/9615)) - ## [`v2.11.1`](https://github.com/elastic/eui/releases/v2.11.1) **Bug fixes** diff --git a/packages/eslint-plugin/changelogs/upcomings/9615.md b/packages/eslint-plugin/changelogs/upcomings/9615.md new file mode 100644 index 000000000000..ec2895d180ed --- /dev/null +++ b/packages/eslint-plugin/changelogs/upcomings/9615.md @@ -0,0 +1,3 @@ +## [`v2.12.0`](https://github.com/elastic/eui/releases/v2.12.0) + +- Added new `require-href-for-link` rule. ([#9615](https://github.com/elastic/eui/pull/9615)) From b3c2f24735b93b2082f64992ff845c2eda8aa45a Mon Sep 17 00:00:00 2001 From: Tamerlan Gudabayev Date: Wed, 29 Apr 2026 00:03:14 +0200 Subject: [PATCH 10/13] fix typo --- packages/eslint-plugin/changelogs/{upcomings => upcoming}/9615.md | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename packages/eslint-plugin/changelogs/{upcomings => upcoming}/9615.md (100%) diff --git a/packages/eslint-plugin/changelogs/upcomings/9615.md b/packages/eslint-plugin/changelogs/upcoming/9615.md similarity index 100% rename from packages/eslint-plugin/changelogs/upcomings/9615.md rename to packages/eslint-plugin/changelogs/upcoming/9615.md From 2221acea0a523cd5dd63763878823d22f202ed28 Mon Sep 17 00:00:00 2001 From: Tamerlan Gudabayev <37669316+TamerlanG@users.noreply.github.com> Date: Wed, 6 May 2026 14:30:21 +0200 Subject: [PATCH 11/13] Update packages/eslint-plugin/README.md Co-authored-by: Weronika Olejniczak <32842468+weronikaolejniczak@users.noreply.github.com> --- packages/eslint-plugin/README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/packages/eslint-plugin/README.md b/packages/eslint-plugin/README.md index 7b9688f82ce2..38bb1da30b04 100644 --- a/packages/eslint-plugin/README.md +++ b/packages/eslint-plugin/README.md @@ -13,8 +13,14 @@ This package contains an eslint plugin that enforces some default rules for usin `` should either be a button or a link, for a11y purposes. When given an `href` the button behaves as a link, otherwise an `onClick` handler is expected and it will behave as a button. +### `@elastic/eui/href-or-on-click` + +`` should either be a button or a link, for a11y purposes. When given an `href` the button behaves as a link, otherwise an `onClick` handler is expected and it will behave as a button. + In some cases it makes sense to disable this rule locally, such as when cmd + click should open the link in a new tab, but a standard click should use the `history.pushState()` API to change the URL without triggering a full page load. +**Exception**: `EuiLink` has to be provided with both `onClick` and `href` so that it renders as an anchor and support Ctrl/Cmd+Click to open in a new tab, and other standard link interactions. See `@elastic/eui/require-href-for-link`. + ### `@elastic/eui/no-restricted-eui-imports` At times, we deprecate features that may need more highlighting and/or that are not possible to annotate with JSDoc `@deprecated`, e.g. JSON token imports: `@elastic/eui/dist/eui_theme_*.json` (for context: https://github.com/elastic/kibana/issues/199715#json-tokens). From 003f149eec28393ea1ec4b2797db7644981a60be Mon Sep 17 00:00:00 2001 From: Tamerlan Gudabayev <37669316+TamerlanG@users.noreply.github.com> Date: Wed, 6 May 2026 14:30:35 +0200 Subject: [PATCH 12/13] Update packages/eslint-plugin/changelogs/upcoming/9615.md Co-authored-by: Weronika Olejniczak <32842468+weronikaolejniczak@users.noreply.github.com> --- packages/eslint-plugin/changelogs/upcoming/9615.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/eslint-plugin/changelogs/upcoming/9615.md b/packages/eslint-plugin/changelogs/upcoming/9615.md index ec2895d180ed..14e97bf6e3b0 100644 --- a/packages/eslint-plugin/changelogs/upcoming/9615.md +++ b/packages/eslint-plugin/changelogs/upcoming/9615.md @@ -1,3 +1 @@ -## [`v2.12.0`](https://github.com/elastic/eui/releases/v2.12.0) - -- Added new `require-href-for-link` rule. ([#9615](https://github.com/elastic/eui/pull/9615)) +- Added new `require-href-for-link` rule From 62518bbd146701bb37a3292feee830798b2ebdc1 Mon Sep 17 00:00:00 2001 From: Tamerlan Gudabayev <37669316+TamerlanG@users.noreply.github.com> Date: Wed, 6 May 2026 14:30:50 +0200 Subject: [PATCH 13/13] Update packages/eslint-plugin/src/rules/require_href_for_link.ts Co-authored-by: Weronika Olejniczak <32842468+weronikaolejniczak@users.noreply.github.com> --- packages/eslint-plugin/src/rules/require_href_for_link.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/eslint-plugin/src/rules/require_href_for_link.ts b/packages/eslint-plugin/src/rules/require_href_for_link.ts index dff51bbba3eb..abaa5570a9b5 100644 --- a/packages/eslint-plugin/src/rules/require_href_for_link.ts +++ b/packages/eslint-plugin/src/rules/require_href_for_link.ts @@ -57,7 +57,7 @@ export const RequireHrefForLink = ESLintUtils.RuleCreator.withoutDocs({ schema: [], messages: { requireHrefForLink: - '<{{name}}> has `onClick` but no `href`. Consider adding an `href` so that the component renders as a link and supports Ctrl/Cmd+Click / "Open link in new tab".', + '<{{name}}> has `onClick` but no `href`. Consider adding `href` so that the component renders as a link and supports Ctrl/Cmd+Click / "Open link in new tab".', }, }, defaultOptions: [],