diff --git a/CHANGELOG.md b/CHANGELOG.md
index bba4ed42698..768263c60ff 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,6 +1,6 @@
## [`master`](https://github.com/elastic/eui/tree/master)
-No public interface changes since `22.2.0`.
+- Improved `htmlIdGenerator` when supplying both `prefix` and `suffix` ([#3076](https://github.com/elastic/eui/pull/3076))
## [`22.2.0`](https://github.com/elastic/eui/tree/v22.2.0)
diff --git a/src-docs/src/routes.js b/src-docs/src/routes.js
index 377a0806a94..9e9b0ee66cb 100644
--- a/src-docs/src/routes.js
+++ b/src-docs/src/routes.js
@@ -122,6 +122,8 @@ import { HighlightAndMarkExample } from './views/highlight_and_mark/highlight_an
import { HorizontalRuleExample } from './views/horizontal_rule/horizontal_rule_example';
+import { HtmlIdGeneratorExample } from './views/html_id_generator/html_id_generator_example';
+
import { I18nExample } from './views/i18n/i18n_example';
import { IconExample } from './views/icon/icon_example';
@@ -414,6 +416,7 @@ const navigation = [
ErrorBoundaryExample,
FocusTrapExample,
HighlightAndMarkExample,
+ HtmlIdGeneratorExample,
InnerTextExample,
I18nExample,
IsColorDarkExample,
diff --git a/src-docs/src/views/html_id_generator/bothPrefixSuffix.js b/src-docs/src/views/html_id_generator/bothPrefixSuffix.js
new file mode 100644
index 00000000000..7c20d4a5e34
--- /dev/null
+++ b/src-docs/src/views/html_id_generator/bothPrefixSuffix.js
@@ -0,0 +1,76 @@
+import React, { Component, Fragment } from 'react';
+
+import {
+ EuiFieldText,
+ EuiFlexGroup,
+ EuiFlexItem,
+ EuiSpacer,
+ EuiCode,
+ EuiFormRow,
+} from '../../../../src/components';
+import { htmlIdGenerator } from '../../../../src/services';
+
+export class PrefixSufix extends Component {
+ constructor(props) {
+ super(props);
+
+ this.state = {
+ prefix: 'Some',
+ suffix: 'Id',
+ id1: htmlIdGenerator('Some')('Id'),
+ };
+ }
+
+ onPrefixChange = e => {
+ const prefix = e.target.value;
+ const { suffix } = this.state;
+
+ this.setState({
+ prefix,
+ id1: htmlIdGenerator(prefix)(suffix),
+ });
+ };
+
+ onSuffixChange = e => {
+ const suffix = e.target.value;
+ const { prefix } = this.state;
+
+ this.setState({
+ suffix,
+ id1: htmlIdGenerator(prefix)(suffix),
+ });
+ };
+
+ render() {
+ const { prefix, suffix, id1 } = this.state;
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {id1}
+
+ );
+ }
+}
diff --git a/src-docs/src/views/html_id_generator/htmlIdGenerator.js b/src-docs/src/views/html_id_generator/htmlIdGenerator.js
new file mode 100644
index 00000000000..d534e049226
--- /dev/null
+++ b/src-docs/src/views/html_id_generator/htmlIdGenerator.js
@@ -0,0 +1,43 @@
+import React, { Component, Fragment } from 'react';
+
+import {
+ EuiFlexGroup,
+ EuiFlexItem,
+ EuiButton,
+ EuiCode,
+} from '../../../../src/components';
+
+import { htmlIdGenerator } from '../../../../src/services';
+
+export default class extends Component {
+ constructor(props) {
+ super(props);
+
+ this.state = {
+ value: htmlIdGenerator()(),
+ };
+ }
+
+ reGenerate = () => {
+ this.setState({ value: htmlIdGenerator()() });
+ };
+
+ render() {
+ const { value } = this.state;
+ return (
+
+
+
+ {value}
+
+
+ Regenerate
+
+
+
+ );
+ }
+}
diff --git a/src-docs/src/views/html_id_generator/htmlIdGeneratorPrefix.js b/src-docs/src/views/html_id_generator/htmlIdGeneratorPrefix.js
new file mode 100644
index 00000000000..2bd726415d2
--- /dev/null
+++ b/src-docs/src/views/html_id_generator/htmlIdGeneratorPrefix.js
@@ -0,0 +1,54 @@
+import React, { Component, Fragment } from 'react';
+
+import {
+ EuiFieldText,
+ EuiFlexGroup,
+ EuiFlexItem,
+ EuiSpacer,
+ EuiCode,
+ EuiFormRow,
+} from '../../../../src/components';
+import { htmlIdGenerator } from '../../../../src/services';
+
+export class HtmlIdGeneratorPrefix extends Component {
+ constructor(props) {
+ super(props);
+
+ this.state = {
+ prefix: 'Id',
+ id1: htmlIdGenerator('Id')(),
+ };
+ }
+
+ onSearchChange = e => {
+ const prefix = e.target.value;
+ this.setState({
+ prefix,
+ id1: htmlIdGenerator(prefix)(),
+ });
+ };
+
+ render() {
+ const { prefix, id1 } = this.state;
+ return (
+
+
+
+
+
+
+
+
+
+ {id1}
+
+ );
+ }
+}
diff --git a/src-docs/src/views/html_id_generator/htmlIdGeneratorSuffix.js b/src-docs/src/views/html_id_generator/htmlIdGeneratorSuffix.js
new file mode 100644
index 00000000000..3f837874ff3
--- /dev/null
+++ b/src-docs/src/views/html_id_generator/htmlIdGeneratorSuffix.js
@@ -0,0 +1,54 @@
+import React, { Component, Fragment } from 'react';
+
+import {
+ EuiFieldText,
+ EuiFlexGroup,
+ EuiFlexItem,
+ EuiSpacer,
+ EuiCode,
+ EuiFormRow,
+} from '../../../../src/components';
+import { htmlIdGenerator } from '../../../../src/services';
+
+export class HtmlIdGeneratorSuffix extends Component {
+ constructor(props) {
+ super(props);
+
+ this.state = {
+ suffix: 'Id',
+ id1: htmlIdGenerator()('Id'),
+ };
+ }
+
+ onSuffixChange = e => {
+ const suffix = e.target.value;
+ this.setState({
+ suffix,
+ id1: htmlIdGenerator()(suffix),
+ });
+ };
+
+ render() {
+ const { suffix, id1 } = this.state;
+ return (
+
+
+
+
+
+
+
+
+
+ {id1}
+
+ );
+ }
+}
diff --git a/src-docs/src/views/html_id_generator/html_id_generator_example.js b/src-docs/src/views/html_id_generator/html_id_generator_example.js
new file mode 100644
index 00000000000..9affbf5a201
--- /dev/null
+++ b/src-docs/src/views/html_id_generator/html_id_generator_example.js
@@ -0,0 +1,120 @@
+import React from 'react';
+
+import { renderToHtml } from '../../services';
+
+import { GuideSectionTypes } from '../../components';
+import { EuiCode } from '../../../../src/components';
+
+import IdGenerator from './htmlIdGenerator';
+import { HtmlIdGeneratorPrefix } from './htmlIdGeneratorPrefix';
+import { HtmlIdGeneratorSuffix } from './htmlIdGeneratorSuffix';
+import { PrefixSufix } from './bothPrefixSuffix';
+
+const htmlIdGeneratorSource = require('!!raw-loader!./htmlIdGenerator');
+const htmlIdGeneratorHtml = renderToHtml(IdGenerator);
+const htmlIdGeneratorSnippet = ' htmlIdGenerator()()';
+
+const htmlIdGeneratorPrefixSource = require('!!raw-loader!./htmlIdGeneratorPrefix');
+const htmlIdGeneratorPrefixHtml = renderToHtml(HtmlIdGeneratorPrefix);
+const htmlIdGeneratorPrefixSnippet = " htmlIdGenerator('prefix')()";
+
+const HtmlIdGeneratorSuffixSource = require('!!raw-loader!./htmlIdGeneratorSuffix');
+const HtmlIdGeneratorSuffixHtml = renderToHtml(HtmlIdGeneratorSuffix);
+const suffixSnippet = " htmlIdGenerator()('suffix')";
+
+const PrefixSufixSource = require('!!raw-loader!./bothPrefixSuffix');
+const PrefixSufixHtml = renderToHtml(PrefixSufix);
+const prefixSuffixSnippet = " htmlIdGenerator('prefix')('suffix')";
+
+export const HtmlIdGeneratorExample = {
+ title: 'Html Id Generator',
+ sections: [
+ {
+ source: [
+ {
+ type: GuideSectionTypes.JS,
+ code: htmlIdGeneratorSource,
+ },
+ {
+ type: GuideSectionTypes.HTML,
+ code: htmlIdGeneratorHtml,
+ },
+ ],
+ text: (
+
+ Use htmlIdGenerator to generate unique IDs for
+ elements with an optional prefix and/or{' '}
+ suffix. The first call to{' '}
+ htmlIdGenerator accepts the prefix as an optional
+ argument and returns a second function which accepts an optional
+ suffix and returns the generated ID.
+
+ ),
+ snippet: htmlIdGeneratorSnippet,
+ demo: ,
+ },
+ {
+ title: 'ID generator with prefix',
+ source: [
+ {
+ type: GuideSectionTypes.JS,
+ code: htmlIdGeneratorPrefixSource,
+ },
+ {
+ type: GuideSectionTypes.HTML,
+ code: htmlIdGeneratorPrefixHtml,
+ },
+ ],
+ text: (
+
+ Provide a prefix to the generator to get an ID that
+ starts with the specified prefix.
+
+ ),
+ snippet: htmlIdGeneratorPrefixSnippet,
+ demo: ,
+ },
+ {
+ title: 'ID generator with suffix',
+ source: [
+ {
+ type: GuideSectionTypes.JS,
+ code: HtmlIdGeneratorSuffixSource,
+ },
+ {
+ type: GuideSectionTypes.HTML,
+ code: HtmlIdGeneratorSuffixHtml,
+ },
+ ],
+ text: (
+
+ Provide a suffix to the generator to get an ID that
+ starts with the specified suffix.
+
+ ),
+ snippet: suffixSnippet,
+ demo: ,
+ },
+ {
+ title: 'ID generator with both prefix and suffix',
+ source: [
+ {
+ type: GuideSectionTypes.JS,
+ code: PrefixSufixSource,
+ },
+ {
+ type: GuideSectionTypes.HTML,
+ code: PrefixSufixHtml,
+ },
+ ],
+ text: (
+
+ The HtmlIdGenerator is capable of generating an ID
+ with both a specified prefix and suffix.
+
+ ),
+ snippet: prefixSuffixSnippet,
+ demo: ,
+ },
+ ],
+};
diff --git a/src/services/accessibility/html_id_generator.ts b/src/services/accessibility/html_id_generator.ts
index 21a8d80ac3b..2ff468933b5 100644
--- a/src/services/accessibility/html_id_generator.ts
+++ b/src/services/accessibility/html_id_generator.ts
@@ -7,7 +7,11 @@ import uuid from 'uuid';
* specify it, it generates a random id prefix. If you specify a custom prefix
* it should begin with an letter to be HTML4 compliant.
*/
-export function htmlIdGenerator(idPrefix?: string) {
- const prefix = idPrefix || `i${uuid.v1()}`;
- return (suffix?: string) => `${prefix}_${suffix || uuid.v1()}`;
+export function htmlIdGenerator(idPrefix: string = '') {
+ const staticUuid = uuid.v1();
+ return (idSuffix: string = '') => {
+ const prefix = `${idPrefix}${idPrefix !== '' ? '_' : 'i'}`;
+ const suffix = idSuffix ? `_${idSuffix}` : '';
+ return `${prefix}${suffix ? staticUuid : uuid.v1()}${suffix}`;
+ };
}