The Elastic UI Framework is a collection of React UI components for quickly building user interfaces at Elastic.
Check out our full documentation site which contains many examples of components in the EUI framework aesthetic, and how to use them in your products. Our FAQ below covers common usage questions — for other general questions regarding EUI, check out the Discussions tab.
Frequently Asked Questions
What is the Elastic UI Framework?
The Elastic UI Framework (EUI) is a design library in use at Elastic to build React applications that need to share our branding and aesthetics. It distributes typed UI React components and static assets for use in building web layouts. Alongside the React components, we ship theme & style utilities that can be used independently on their own.
The primary goal of this library is to provide reusable UI components that can be used throughout Elastic's web products. As React components, they remove CSS from the process of building UIs. As a single source of truth, the framework allows our designers to make changes to our aesthetic directly in the code. And unit test coverage for the UI components allows us to deliver a stable "API for user interfaces".
Many of Elastic's products are open source and rely upon this library to function. The Elastic UI Framework began as a folder of code in Kibana and we decided it could be used beyond that codebase. It exists as an independent library so that patterns can be shared across teams and design standards can be scaled across our organization. Since most of our products are open source, we treat this one similarly as far as public publishing and conversation even if its usage tends to focus more inward towards Elastic itself.
What is the versioning, release, and upgrade strategy?
We use semver for versioning and use that to denote breaking changes in EUI upgrades. Traditionally we consider API changes in our prop names or existing component functionality to be a reason for a breaking change, but do not track the renaming of CSS selectors, mixins or other style changes under this same rigor.
Traditionally releases are made weekly against whatever is in the main branch and you can upgrade from NPM as you see fit.
Bug reports and feature requests are most welcome, but our roadmap and prioritization are driven primarily by internal Elastic usage.
Please note that in order to keep our backlog manageable and focused on tasks we intend to complete, feature requests & tech debt issues that are inactive for a year will be auto-closed (bugs will remain open if determined to be reproducible and valid).
This activity counter can be soft reset by commenting on the issue directly, but please do so mindfully. We would ask that you proactively let the EUI team know why this request matters to you or how it impacts you or your users, in order to help us prioritize accordingly.
The EUI team, like everyone else, has a finite amount of time and resources, and it is not humanly possible for us to implement every task or feature requested of us. If you want to contribute a fix, see Can I contribute to EUI?.
Our wiki docs contain instructions and guidelines on multiple areas of EUI usage and development that are too detailed for an initial README. For more information, see:
yarn add @elastic/eui @elastic/eui-theme-borealis @elastic/datemath @emotion/react @emotion/css moment
Requirements and dependencies
EUI also has moment and @elastic/datemath as dependencies itself. These are already loaded in most Elastic repos, but make sure to install them if you are starting from scratch.
What's available
EUI publishes React UI components, JavaScript helpers called services, and utilities for writing Jest tests. Please refer to the Elastic UI Framework website for comprehensive info on what's available.
You can import React components from the top-level EUI module.
js
import {
EuiButton,
EuiCallOut,
EuiPanel,
} from '@elastic/eui';
Test
Test utilities are published from the lib/test directory.
js
import { findTestSubject } from '@elastic/eui/lib/test'; // Enzyme
import { findByTestSubject, render, screen } from '@elastic/eui/lib/test/rtl'; // React Testing Library
Custom styles
EUI uses a CSS-in-JS approach for styling, specifically Emotion library. We recommend using the css function (read more here) from @emotion/react, which automatically sets the generated className and combines all passed styles into a single ruleset. To enable css concatenation, you'll need the Babel preset.
You can find more information regarding Emotion usage with EUI here.
Theming
EUI leverages Emotion for its theming as well. As such, we require an <EuiProvider> wrapper around your application in order for various theme-related UI & UX (such as dark/light mode switching) to work as expected.
"Module build failed" or "Module parse failed: Unexpected token" error
If you get an error when importing a React component, you might need to configure Webpack's resolve.mainFields to ['webpack', 'browser', 'main'] to import the components from lib instead of src. See the Webpack docs for more info.
Using the `test-env` build
EUI provides a separate babel-transformed and partially mocked commonjs build for testing environments in consuming projects. The output is identical to that of lib/, but has transformed async functions and dynamic import statements, and also applies some useful mocks. This build mainly targets Kibana's Jest environment, but may be helpful for testing environments in other projects.
Mapping to the `test-env` directory
In Kibana's Jest configuration, the moduleNameMapper option is used to resolve standard EUI import statements with test-env aliases.
This eliminates the need to polyfill or transform the EUI build for an environment that otherwise has no need for such processing.
Mocked component files
Besides babel transforms, the test environment build consumes mocked component files of the type src/**/[name].testenv.*. During the build, files of the type src/**/[name].* will be replaced by those with the testenv namespace. The purpose of this mocking is to further mitigate the impacts of time- and import-dependent rendering, and simplify environment output such as test snapshots. Information on creating mock component files can be found with testing documentation.
Using the `optimize` build (Beta)
The optimize output directory is an opt-in intermediate step as we work towards dedicated, formal build output for development and production environments.
When compiling with webpack, use the resolve.alias configuration to target the desired directory:
Designated "Beta" as included babel plugins may change and the output directory is likely to be renamed.
Notable differences
Absence of propTypes
Significant bundle size decrease
Likely not suitable for development environments
Runtime transforms
Slight bundle size decrease
Requires @babel/runtime be a consumer dependency
Contributing To Eui README (`wiki/contributing-to-eui/README.md`)
Contributing to EUI
🙌 Thanks for your interest in contributing to EUI! 🙌
We welcome and encourage contributions.
However, because EUI has a large footprint in Elastic products, we must maintain a high standard of quality and due diligence for contributions. The guidance below outlines our expectations. While we'd hate to turn away any contribution, PRs that deviate from this guidance will most likely be rejected.
Who can contribute
Elastic employees — EUI is built primarily for Elastic products; maintainers prioritize internal roadmap work first. If your need isn't currently a priority, feel free to contribute the solution yourself.
Community — Outside contributions are welcome and merged on a best-effort basis.
What to contribute
External contributors may only open PRs for issues labelled help wanted. We add that label when a task fits our roadmap and it has been prepared with clear instructions.
If you are having a problem with EUI, file an issue.
If you want to contribute a fix for an issue that affects you, comment on that issue with a proposed solution. We can discuss it there. If we agree, we will add the help wanted label.
What not to contribute
Unless agreed upon previously:
Large or core roadmap work — Often handled internally so architecture, accessibility, and downstream coordination stay aligned.
Style or design changes — These require alignment with design teams at Elastic.
High-impact changes - Even small code changes can have a large impact on Kibana, requiring significant testing and review.
How to contribute
We do not assign issues to community members. Comment on the help wanted issue before you start so others know you are working on it.
Review timing — We aim to respond within about a week.
Concurrency — Please limit open PRs so review stays focused.
Due diligence — Follow the PR template (summary, impact, screenshots, checklists, QA notes).
Reviewers — Code ownership is defined in CODEOWNERS; the right teams will be auto-requested based on the paths you touch.
Draft PRs — We don’t review drafts by default. When you want feedback or help, comment and ping @eui-team (same idea as the note at the top of the PR template).
Commit messages
We do not enforce a commit naming convention but it's customary to use conventional commits.
PRs typically follow the pattern: [ComponentName] Short description (#PR), e.g. [EuiBanner] Adjust additional content spacing. When merging, we squash all commits and the main commit message is the PR title. The (#PR) suffix is appended automatically.
PR labels
A few labels carry behavior beyond categorization:
skip-changelog — bypasses the CI changelog check for all packages. See Changelogs for when this is acceptable.
skip-changelog- — bypasses the check for a single package only (e.g. skip-changelog-eslint-plugin-eui).
breaking change — required for PRs introducing breaking changes; triggers an automated comment with extra due-diligence steps for the Kibana upgrade.
Verifying your change
From packages/eui (unless noted):
yarn lint — ESLint + type checking
yarn test-unit — Jest unit tests (add --watch while iterating)
yarn test-cypress — Cypress component tests
A changelog entry in packages/eui/changelogs/upcoming/.md for any user-facing change — see the changelog guide
Stale PRs
Inactive PRs (~3 months) may be closed by GitHub’s stale workflow.
If we owe review, ping us to get things moving again.
If you owed changes and the PR auto-closed, we may push to your branch or close it and continue in a new PR based on your work.
Contributing To Eui Designing README (`wiki/contributing-to-eui/designing/README.md`)
Design System Synchronization Guidelines
When submitting code changes that affect the visual design, behavior, or properties of EUI components or tokens, it's critical to ensure the Elastic UI Borealis Figma library remains synchronized with the codebase. This maintains design-development consistency across the entire Elastic ecosystem.
When Figma Updates Are Required
Code changes that impact design and require Figma library updates include:
We use a number of patterns and conventions throughout our components for naming, props, refs, and icons that should be followed whenever possible.
Contributing To Eui Documenting README (`wiki/contributing-to-eui/documenting/README.md`)
Documentation guidelines
Always remember to update the documentation site in the packages/website folder and add a changelog in the same PR that contains functional changes. We do this in tandem to prevent our examples from going out of sync with the actual components. In this sense, treat documentation no differently than how you would treat tests.
The complexity of the component should determine how many examples you need to create, and how complex they should be. In general, your examples should demonstrate:
The most common use-cases for the component.
How the component handles edge cases, e.g. overflowing content, text-based vs. element-based content.
The various states of the component, e.g. disabled, selected, empty of content, error state.
Refer to Writing documentation for information on the process of documenting components.
Formatting & writing style
Here are our formatting guidelines for writing documentation:
Use sentence case, always, for page and section titles. Example: This component does something
Component names:
When referencing the component name, wrap it in ** tags. Example: EuiComponent**
When referencing the component name, always include the Eui prefix unless you are referencing the generic term. Example: EuiFlyout vs flyout
Code syntax:
Wrap references to prop names and elements in code blocks. Example: `propName`
Contributing To Eui Testing README (`wiki/contributing-to-eui/testing/README.md`)
Testing EUI components
All components should have, at minimum, unit tests for each prop.
Cypress component tests — optional, used for complex components or where real-browser DOM and interactions are needed.
Storybook — the component development environment and the source of stories used by visual regression testing.
Visual regression tests — Playwright + jest-image-snapshot against Storybook stories, run on every pull request.
EUI test helpers — Playwright validation tests for @elastic/eui-test-helpers, run on every pull request with flake detection when a component changes.
Code coverage
Code coverage is a tool intended to help devs quickly find lines/branches that still need to be covered and determine test cases to write. EUI does not enforce a code coverage threshold in CI; reports are generated on demand for local inspection.
Jest coverage reports
Jest code coverage reports can be generated by running yarn test-unit with the --coverage flag. Generated HTML reports are output to the reports/ folder.
To view the reports, you can either drag the reports/jest-coverage/index.html file into a browser window, or in a terminal, run open reports/jest-coverage/index.html from the EUI project root.
Cypress coverage reports
Cypress has been configured to automatically output Cypress code coverage to the reports/cypress-coverage folder. If you would prefer to turn this off locally, you can add the --env coverage=false flag when running your your cypress commands.
To view the reports, you can either drag the reports/cypress-coverage/index.html file into a browser window, or in a terminal, run open reports/cypress-coverage/index.html from the EUI project root.
Combined Jest & Cypress coverage reports
If you're working on a component that has both Cypress and Jest tests, we have a command that allows you to combine the results of both coverage reports.
Setup: On the component(s) you're looking for final coverage #s for,
Ensure you have already run yarn test-unit --coverage to generate a Jest report
Ensure you have already run yarn test-cypress to generate a Cypress report
Run yarn combine-test-coverage
This should automatically open a browser window with reports/combined-coverage/index.html
Packages Website Docs Components Index (`packages/website/docs/components/index.mdx`)
sidebar_position: 1
Overview
EUI offers a wide variety of components. This section is organized topically with individual pages containing live example, usage recommendations, guidelines, and props.
The adjacent patterns section provides additional guidance for handling use cases that cross multiple components.
Packages Website Docs Dataviz Index (`packages/website/docs/dataviz/index.mdx`)
sidebar_position: 1
import figmaDatavizConsistency from '!url-loader!./assets/dataviz_consistency.svg'; import figmaDatavizHierarchyOfInformation from '!url-loader!./assets/dataviz_hierarchy-of-information.svg'; import figmaDatavizSemantic from '!url-loader!./assets/dataviz_semantic.svg'; import figmaDatavizTitlesAndLabels from '!url-loader!./assets/dataviz_titles-and-labels.svg'; import figmaDatavizMultiPurposeCharts from '!url-loader!./assets/dataviz_multi-purpose-charts.svg'; import figmaDatavizTimeAxis from '!url-loader!./assets/dataviz_time-axis.svg';
Overview
import VersionBadge from './version_badge';
Elastic Charts (github repository, storybook) is library of charts, a set of independent components that share a series of common rules in terms of style and behavior. Moving past the concept of “library”, we consider this list of charts as a “system”, and with that we mean a series of elements that clearly belong to the same design environment, that are consistent with EUI and that are working in a much more cohesive and connected way, depending on each other instead of living on their own.
Please consider that the work described in this page is a long term goal that we are trying to achieve and therefore this material has to be considered a work-in-progress and might not reflect precisely the current state of the library.
Consistency
It might sound simple, even trivial from some points of view, but the most important thing is that all charts should look like they belong to the same system. Charts should behave (link to interaction page) and look similar, there should be an overall shared aesthetic that is consistent among them and with EUI. If charts, such as any other component, look different from the rest of elements they will be perceived as detached from the product and the whole user experience would be affected, this is why elements within charts follow the same set of rules of positioning, style and interaction.
Style
We consider the concept of style as not purely related to aesthetics. While we obviously know that appearance plays a crucial role in fruition of components, we prefer to approach this topic from the perspective of semantic and hierarchy of information. We focus on how we display information in order to convey meaning and data in a consistent way across the whole set of charts.
Hierarchy of information
Labels on top of charts are a good example of this concept. Up until 8.11, charts such as pie charts and treemaps have been displaying information in a slightly confusing and conflicting way (left). Reading direction would suggest that the first information we display is the most important, while font-weight in communicating the opposite, giving the values a stronger visual role. The planned design update (right) reinforces these roles being redundant in both position and weight across these two elements.
Semantic
When representing data, everything should have a meaning and every detail should be carefully considered in order to properly convey information. A good illustration of this approach is how axis labels are positioned next to gridlines consistently across charts. Labels that describe passage of time (such as timestamps, dates or hours and minutes) are always positioned right next to the gridlines, suggesting that a specific portion of time starts from that specific point and proceeds from that. Categorical labels (such as the name of a Country, or a specific data series), instead, are always centered on the element in order to directly associate the label with the referred element.
Titles and labels
Titles and subtitles play a crucial role in describing what the user is supposed to see, they can guide and help our customers in understanding charts and their importance, both visually and semantically, is often underestimated. When too subtle they might disappear from the component failing in delivering information, when too strong they might visually interfere with the overall aesthetic balance being too prominent.
Few typologies of charts (e.g. Metric and Bullet) include, by design, their own title and subtitle while in other cases (e.g. line charts, barchart) that is not required. Both these cases should be consistent and should follow the same rules in order to have a cohesive visual appearance when placed next to each other.
Dependencies among charts
In a system, differently from a library, charts should not live by themselves. The behavior that has been designed for bullet charts is a proof of that.
We are aware that our users might create very small charts and that our charts might be displayed on different monitors in a very wide variety of resolutions. In order to be sure that readability of data is always ensured, we designed a responsive behavior that not only takes in consideration how elements would be displayed within the chart (e.g. see truncation of titles) but also how that whole chart could switch to a simpler and more readable model in case of need.
Multi-purpose charts
In data visualization you can often obtain the same representation with different approaches, or relying on different typologies of charts and the possibility to do that would be a good metric to define the flexibility of a system of charts. The following examples show how we can obtain the same exact representation coming from different starting points, especially when we don’t have a specific solution for a problem.
A good example would be a representation of the health status of a monitored environment, which we represent here with a heatmap and a barchart getting pretty similar results.
Time Axis
Most of the charts created within the Kibana environment are time-based and therefore we need an advanced way of displaying time so that we can accommodate most of the needs while at the same time providing all information about the context. Our axes are based on a two-levels organization that would provide information about time in a different degree of detail.
A first set of gridlines display the detailed set of time buckets while a higher one would help users understand the context, adding information about years, months or any measure upon that.
Timestamps are also automatically transformed into common ways of writing time such as hours and minutes instead of milliseconds.
Packages Website Docs Patterns Index (`packages/website/docs/patterns/index.mdx`)
sidebar_position: 1
Overview
Patterns are preferred solutions to specific user needs, ensuring consistency in design, behavior, and interaction.
What are patterns?
Patterns offer practical solutions that can be applied repeatedly to address frequent design issues, ensuring alignment with our principles and guidelines. They serve as building blocks for creating consistent and cohesive user interfaces.
How do patterns differ from guidelines?
Guidelines are best practices for EUI components that ensure consistency and quality. They answer "how to use it correctly," providing usage instructions, do's and don'ts, accessibility guidelines, and best practices.
Patterns are reusable solutions combining components to solve design problems. They answer "how to solve this problem," showing component combinations, layout, user flow, interaction, and code examples.
Contributing
The content available here reflects collective insights and best practices. If you see opportunities for improvement or have new ideas, we encourage contributions. Your expertise helps ensure our design system remains relevant and user-focused.
Start by creating a discussion with the EUI team here.
Additional patterns at elastic
We capture patterns specific to EUI here. For additional patterns and design guidelines specific to Kibana and Solutions, be sure to look for additional content on https://docs.elastic.dev/ (Elastic internal link).
Packages Website Docs Components Tables Index (`packages/website/docs/components/tables/index.mdx`)
Tables
Tables can get complicated very fast. EUI provides both opinionated and non-opinionated ways to build tables.
Opinionated high-level components:
These high-level components remove the need to worry about constructing individual building blocks together. You simply arrange your data in the format it asks for.
EuiBasicTable handles mobile row selection, row actions, row expansion, and mobile UX out of the box with relatively simple-to-use APIs. It is best used with asynchronous data, or static datasets that do not need pagination/sorting.
EuiInMemoryTable has all the features that EuiBasicTable has, and additionally handles pagination, sorting, and searching the passed data out-of-the-box with relatively minimal APIs. It is best used with smaller synchronous datasets.
Please note that if you go this route, you must han`dle your own data management as well as table accessibility and mobile UX.
Packages Website Docs Patterns Error Messages Index (`packages/website/docs/patterns/error-messages/index.mdx`)
Error messages
What is an error?
An error happens when the system couldn’t do something it was trying to do.
There are lots of reasons why errors happen, but they’re not always obvious to the user. It could be because instructions were not clear, data is missing, or there are bugs in the application.
We should do everything we can to help users to avoid errors. When an error occurs, we must tell the user. You should tell them exactly what went wrong, and any actions they can take to fix it.
Missing or badly written error messages can be frustrating, time consuming and difficult to recover from. They can also make the user believe the task is more complex than it is, and can give the impression that our product is low quality.
Primary errors are as bad as it gets. They block the user from completing the current task, and we can’t even show them the page they expected. For example, when a user does not have permission to view a page, error code 403. A primary error should redirect the user to an error page.
Secondary errors happen when there is an error which blocks the current task, but we can still show them the page they expected. For example, an API or cluster that is not responding, error code 504. A secondary error should reload the page without the user losing any information, and display an error banner.
Validation errors happen when the user enters information in a different format than expected, blocking the current task, error code 400. Validation errors should reload the page without the user losing any information, display an error summary, and highlight any fields which caused errors.
Unrelated errors are warnings. They don’t impact the user right now, but they’re useful to know. For example, there may be an error in another part of the application, but it’s not blocking the current task.
When an error happens, there is usually an error code provided by the client (browser) or the server, which can help us to explain to the user what went wrong.
It can be tempting to just tell the user, ‘hey, error 504’. But, most users won’t know what that means. Rather than just quoting the error code, we should translate what it means to the user in plain language.
Packages Website Docs Patterns Nested Drag And Drop Index (`packages/website/docs/patterns/nested-drag-and-drop/index.mdx`)
Nested drag and drop
mdx-code-block
import { EuiLink } from '@elastic/eui';
EUI exposes EuiDraggable and EuiDroppable components (see documentation), which are wrappers around @hello-pangea/dnd. While excellent for simple lists, @hello-pangea/dnd is heavy and has limitations such as not being able to move items between different levels of nesting.
For complex use cases like this, we recommend Pragmatic drag and drop by Atlassian. It is a performance-focused, flexible library that handles these scenarios efficiently.
The example below demonstrates a simplified version of Pragmatic's Tree pattern, adapted to use EUI components and design tokens with enhanced keyboard navigation.
:::accessibility Accessibility
This example uses nested EuiAccordion components with roving tabindex to improve usability, similarly as in Tree view pattern.
For complex trees, the recommended accessibility pattern is not to mimic mouse dragging with keys but provide a "Move" action. In this case, we use EuiPopover and EuiContextMenu composition inside the extraAction prop. :::
:::warning Package versions
This pattern may work differently depending on which version of @atlaskit/pragmatic-drag-and-drop you use. Please check the versions used in our package.json. :::
Packages Website Docs Patterns Severity Index (`packages/website/docs/patterns/severity/index.mdx`)
title: Health and Severity
sidebar_position: 6
mdx-code-block
import { css } from '@emotion/react';
import { EuiButton, EuiButtonIcon, EuiIcon, EuiTable, EuiTableBody, EuiTableHeader, EuiTableHeaderCell, EuiTableRow, EuiTableRowCell } from '@elastic/eui';
Health describes the status of an element (eg. node, environment, host) that would be typically associated with positive or negative values, while in severity indicates an increasing level of risk.
Levels
These levels, accordingly applied, serve both purposes of communicating Health statuses and Severity concerns among different contexts. They are willingly generic and descriptive only at a high level so that they can be applied and adapted to different contexts, while at the same time providing consistency across the product.
LevelNameDescription0UnknownCreated for unknown or unassigned values that can’t be associated with a specific level. It does not imply any positive or negative meaning.
text
<EuiTableRow>
<EuiTableRowCell>1</EuiTableRowCell>
<EuiTableRowCell>Good / Success</EuiTableRowCell>
<EuiTableRowCell>Created for a direct association with positive meanings, used for healthy elements and positive outcomes.</EuiTableRowCell>
</EuiTableRow>
<EuiTableRow>
<EuiTableRowCell>2</EuiTableRowCell>
<EuiTableRowCell>Regular / Neutral</EuiTableRowCell>
<EuiTableRowCell>Used for usual, acceptable states that need to be recognised but don't need to stand out.</EuiTableRowCell>
</EuiTableRow>
<EuiTableRow>
<EuiTableRowCell>3</EuiTableRowCell>
<EuiTableRowCell>Warning</EuiTableRowCell>
<EuiTableRowCell>The starting level of concern, used for elements that would need attention.</EuiTableRowCell>
</EuiTableRow>
<EuiTableRow>
<EuiTableRowCell>4</EuiTableRowCell>
<EuiTableRowCell>Risk</EuiTableRowCell>
<EuiTableRowCell>Middle level of risk, used for potential escalations and increasing level of severity.</EuiTableRowCell>
</EuiTableRow>
<EuiTableRow>
<EuiTableRowCell>5</EuiTableRowCell>
<EuiTableRowCell>Danger</EuiTableRowCell>
<EuiTableRowCell>Highest level of danger, used for elements that need immediate support and attention.</EuiTableRowCell>
</EuiTableRow>
Use cases
The following mapping is not exhaustive of all use cases but it serves as an example of how the Health & Severity levels might be applied. Different contexts and environments might need dedicated terminology, therefore these levels can be used with alternative, customised variations in terms of words and naming, serving the dedicated purpose while aligning the use of colors across the product.
LevelUse case A (Alerting/Priority)Use case B (Health/Status)Use case C (Severity/Logs)Unknown-Unknown / No dataOther
This association between levels and colors has been created to be recognised and immediately understood, avoiding conflicts with other EUI elements and tints.
mdx-code-block
import { SeverityColorAssociationTable } from './color_association_table.tsx';
Tokens
import { SeverityColorsTable } from '../../getting-started/theming/tokens/colors/severity_colors_table.tsx'; import { DataVisBackgroundColor } from '../../getting-started/theming/tokens/colors/data_vis_colors_table.tsx'; import { DataVisColorsPreview } from '../../getting-started/theming/tokens/colors/data_vis_colors_preview.tsx'
Text Tokens
import { TextColorPreview } from '../../getting-started/theming/tokens/colors/text_color_preview.tsx'; import { SeverityTextColorsTable } from '../../getting-started/theming/tokens/colors/severity_text_colors_table.tsx'; import { Example } from '@site/src/components';
Background Tokens
import { BackgroundColorPreview } from '../../getting-started/theming/tokens/colors/background_color_preview.tsx'; import { SeverityBackgroundColorsTable } from '../../getting-started/theming/tokens/colors/severity_background_colors_table.tsx';
Edge cases
There might be cases in which these levels are not sufficient and don't fully fullfill the needs, in that case please reach out to EUI team.
Packages Website Docs Utilities Accessibility Index (`packages/website/docs/utilities/accessibility/index.mdx`)
Using EuiScreenReaderOnly hides the wrapped element from the page, but keeps it accessible for screen readers to provide more context. It should be used primarily to mask text and requires the child to be a single React element for cloning.
:::warning WebAIM recommendation for screen reader-only content "In most cases, if content (particularly content that provides functionality or interactivity) is important enough to provide to screen reader users, it should probably be made available to all users." Learn more about invisible content :::
Using a screen reader, verify that there is a second paragraph.
```tsx
import React from 'react';
import { EuiText, EuiScreenReaderOnly } from '@elastic/eui';
export default () => (
This is the first paragraph. It is visible to all.
This is the second paragraph. It is hidden for sighted users but visible to screen readers.
This is the third paragraph. It is visible to all.
);
text
</Demo>
### Showing on focus
If the wrapped element is **focusable**, you must use the `showOnFocus` prop to visibly show the element to all users when focused.
*Tab through the following example with your keyboard to verify the element is visible on focus.*
<Demo>
```tsx
import React from 'react';
import { EuiText, EuiScreenReaderOnly, EuiLink, EuiToolTip, EuiButtonIcon } from '@elastic/eui';
export default () => (
<EuiText>
<p>
This link is visible to all on focus:{' '}
<EuiScreenReaderOnly showOnFocus>
<EuiLink href="./utilities/accessibility">Link text</EuiLink>
</EuiScreenReaderOnly>
</p>
<p>
This tooltip + button is visible on focus within:{' '}
<EuiScreenReaderOnly showOnFocus>
<span>
<EuiToolTip content="Information">
<EuiButtonIcon iconType="info" aria-label="Information" />
</EuiToolTip>
</span>
</EuiScreenReaderOnly>
</p>
</EuiText>
);
Screen reader live region
Using EuiScreenReaderLive to announce dynamic content, such as status changes based on user interaction.
The configurable role and aria-live props default to status and polite respectively for non-intrusive but timely update announcements. When not using the default values, be sure to follow ARIA guidelines for role to aria-live mapping.
Also consider other live region guidelines, such as that live regions must be present on initial page load, and should not be in a conditional JSX wrapper.
import ScreenReaderLiveRegion from './screen_reader_live_region';
```tsx
import { useState, useCallback } from 'react';
import { EuiButton, EuiCode, EuiScreenReaderLive, EuiSpacer, EuiText, } from '@elastic/eui';
export default () => { const [screenReaderText, setScreenReaderText] = useState( 'You have no notifications.' );
</Demo>
### Auto-focusing the live region on text change
The `focusRegionOnTextChange` prop will automatically focus the `EuiScreenReaderLive` region (causing screen readers to read out the text content) whenever `children` changes.
This is primarily useful for announcing navigation or page changes, when programmatically resetting focus location back to a certain part of the page (where the `EuiScreenReaderLive` is placed) is desired.
*Using a screen reader, click the following navigation links and notice that when the new page is announced, focus is also set to the top of the body content.*
<Demo previewPadding="s">
/* Detailed source-code truncated for AI context efficiency. */
text
</Demo>
import { EuiBetaBadge } from '@elastic/eui';
## Live announcer region <EuiBetaBadge label="Beta" color="accent" />
:::warning `EuiLiveAnnouncer` is a new, experimental alternative to `EuiScreenReaderLive` for screen reader live region announcements.
Its API and behavior may change based on feedback and testing.
:::
Both `EuiLiveAnnouncer` and `EuiScreenReaderLive` are designed for the **same use cases**: to announce dynamic content, such as nodifications or status changes based on user interaction.
The main drawback for live regions is that they must be present on initial page load before reading content updates. `EuiLiveAnnouncer` addresses this
by updating the content after the initial render to ensure an announcement is made when mounting a component.
Same as for `EuiScreenReaderLive`, `EuiLiveAnnouncer` supports configurable `role` and `aria-live` props which default to `status` and `polite` respectively for non-intrusive but timely update announcements.
If you're not using the default values, be sure to follow [ARIA guidelines](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/ARIA_Live_Regions) for `role` to `aria-live` mapping.
### Main difference
- **`EuiLiveAnnouncer`** will announce the message **when the component mounts** (e.g., when it first appears in the DOM), as well as when the message changes.
- **`EuiScreenReaderLive`** will only announce when the message changes, and will not announce the initial message on mount.
This means `EuiLiveAnnouncer` is especially useful for scenarios where you want to ensure an announcement is made immediately when a component is mounted, e.g. showing a notification or alert based on user interaction.
An additional difference is that `EuiLiveAnnouncer` can clear its message after a configurable timeout via `clearAfterMs`, which prevents stale
announcements and duplicated DOM content for screen reader users. Screen readers will continue reading the message even if it was already cleared.
<Demo>
```tsx
import { useState } from 'react';
import { EuiButton, EuiCodeBlock, EuiFlexGroup, EuiFlexItem, EuiLiveAnnouncer, EuiSpacer } from '@elastic/eui';
export default () => {
const [announcement, setAnnouncement] = useState('No notifications.');
const [isShown, setIsShown] = useState(false);
return (
<EuiFlexGroup direction="column" gutterSize="m" css={{ maxWidth: 300 }}>
<EuiFlexItem>
<EuiButton onClick={() => setIsShown((shown) => !shown)}>
Toggle announcement
</EuiButton>
</EuiFlexItem>
<EuiFlexItem>
{isShown && (
<>
<EuiButton onClick={() => setAnnouncement(`You have ${Math.floor(Math.random() * 1000)} new notifications.`)}>
Update announcement
</EuiButton>
<EuiSpacer size="m" />
<EuiCodeBlock>{announcement}</EuiCodeBlock>
<EuiLiveAnnouncer>
{announcement}
</EuiLiveAnnouncer>
</>
)}
</EuiFlexItem>
</EuiFlexGroup>
);
};
Key differences between `EuiLiveAnnouncer` and `EuiScreenReaderLive`
Feature
EuiLiveAnnouncer
EuiScreenReaderLive
Auto-clear message
Yes, after configurable timeout (clearAfterMs)
No
Announce on mount
Yes
No
Announce on message change
Yes
Yes
Focus on message change
No
Yes (focusRegionOnTextChange prop)
Handles rapid updates
Yes
Yes
Output consistency
High
Medium
API stability
Beta/experimental
Stable
Skip link
The EuiSkipLink component allows users to bypass navigation, or ornamental elements, and quickly reach the main content of the page. It requires a destinationId which should match the id of your main content. If your ID does not correspond to a valid element, the skip link will fall back to focusing the <main> tag on your page, if it exists.
Tab through the following section to verify the Skip to content button is visible on focus.
```tsx
import React from 'react';
import { EuiSkipLink, EuiText } from '@elastic/eui';
export default () => { return (
The following skip links are only visible on focus:
Skips to this example container
Falls back to main container
); };
text
</Demo>
## Styles helpers
import { ClassNamePreview } from './class_name_preview';
import { FunctionPreview } from './function_preview';
import { HookPreview } from './hook_preview';
<Example>
<Example.Description>
### `.euiScreenReaderOnly` className
This utility class allows you to apply the screen reader only CSS styles directly to your component.
</Example.Description>
<Example.Preview>
<ClassNamePreview />
</Example.Preview>
<Example.Snippet>
```tsx
<p className="euiScreenReaderOnly" />
```
</Example.Snippet>
</Example>
<Example>
<Example.Description>
### `euiScreenReaderOnly()` function
This function allows you to apply the screen reader only CSS styles directly to your component.
</Example.Description>
<Example.Preview>
<FunctionPreview />
</Example.Preview>
<Example.Snippet>
```tsx
css`
${euiScreenReaderOnly()}
`
```
</Example.Snippet>
</Example>
<Example>
<Example.Description>
### `useEuiFocusRing(offset?, color?)` hook
By default, all interactive elements will inherit the `outline` property from the reset file. However, some instances require adjustment to the `offset` and `color` of this outline. This helper function allows that customization of the focus outline.
`offset: 'inset' | 'outset' | 'center' | CSSProperties['outlineOffset'];`
`color: CSSProperties['outlineColor'];`
</Example.Description>
<Example.Preview>
<HookPreview />
</Example.Preview>
<Example.Snippet>
```tsx
css`
&:focus {
${useEuiFocusRing('center', euiTheme.colors.accent)}
}
`
```
</Example.Snippet>
</Example>
## Props
import docgen from '@elastic/eui-docgen/dist/components';
---
### Packages Website Docs Utilities Color Palettes Index (`packages/website/docs/utilities/color-palettes/index.mdx`)
# Color palettes
import { ColorPaletteCopyCode, ColorPaletteFlexItem } from './color_palettes_components';
import colorPaletteComponentsSource from '!raw-loader!./color_palettes_components';
EUI provides a base set of color palettes that return an array of hexadecimal color for use in other EUI components or charts.
## Preset qualitative palettes
Qualitative palettes are best suited for communicating and comparing discrete data series. EUI recommends using the `euiPaletteColorBlind()` for qualitative and categorical data.
This palette is restricted to only 10 colors. However, you can add more groups of 10 which are alternates of the original. This is better than allowing the initial set to loop.
These colors are meant to be used as graphics and contrasted against the value of `euiColorEmptyShade` for the current theme. When placing text on top of these colors, use the `euiPaletteColorBlindBehindText()` variant. It is a brightened version of the base palette to create better contrast with text.
<Demo scope={{ ColorPaletteCopyCode, ColorPaletteFlexItem }} extraFiles={{ 'color_palettes_components.tsx': colorPaletteComponentsSource }}>
/* Detailed source-code truncated for AI context efficiency. */
text
</Demo>
## Recommended quantitative palettes
Quantitative palettes are best suited for displaying data on a continuum, as in the case of health statuses and large geographic or demographic-based data sets.
EUI provides the following common palettes for quantitative data and [charts](../../dataviz/index.mdx). Just pass in the number of steps needed and the function will interpolate between the colors.
For usages in React we recommend using the respective [palette hooks](https://github.com/elastic/eui/blob/main/packages/eui/src/services/color/eui_palettes_hooks.ts), e.g. `useEuiPaletteColorBlind`.
:::warning
The palette for status is the only palette that has proper contrast ratios. When using the other palettes, consider adding another form of the data for screen readers.
:::
<Demo scope={{ ColorPaletteCopyCode, ColorPaletteFlexItem }} extraFiles={{ 'color_palettes_components.tsx': colorPaletteComponentsSource }}>
/* Detailed source-code truncated for AI context efficiency. */
text
</Demo>
## Custom palettes
Use the `colorPalette` service to generate a custom, gradiated palette array of any length from one or more hexadecimal color codes. The third parameter `divergent`, will interpolate between the two halves of the spectrums separately. If a middle point is not provided, it will graduate to light gray.
<Demo scope={{ ColorPaletteCopyCode, ColorPaletteFlexItem }} extraFiles={{ 'color_palettes_components.tsx': colorPaletteComponentsSource }}>
/* Detailed source-code truncated for AI context efficiency. */
text
</Demo>
## Props
import docgen from '@elastic/eui-docgen/dist/services/color';
---
### Packages Website Docs Utilities Focus Trap Index (`packages/website/docs/utilities/focus-trap/index.mdx`)
---
keywords: [EuiFocusTrap]
---
# Focus trap
Use **EuiFocusTrap** to prevent keyboard-initiated focus from leaving a defined area. Temporary flows and UX escapes that occur in components such as [EuiModal](../../components/containers/modal/index.mdx) and [EuiFlyout](../../components/containers/flyout/index.mdx) are prime examples.
For components that project content in a React portal, **EuiFocusTrap** will maintain the tab order expected by users.
* Use `clickOutsideDisables` to disable the focus trap when the user clicks outside the trap.
* Use `noIsolation={false}` when pointer events on outside elements should be disallowed.
* Use `crossFrame={true}` when focus should not be allowed on other iframes on the page.
import FormExample from './form_compressed';
import formCompressedSource from '!raw-loader!./form_compressed';
<Demo scope={{ FormExample }} extraFiles={{ 'form_compressed.tsx': formCompressedSource }}>
```tsx interactive
import React, { useState } from 'react';
import {
EuiBadge,
EuiButton,
EuiFocusTrap,
EuiPanel,
EuiSpacer,
EuiText,
} from '@elastic/eui';
import FormExample from './form_compressed';
export default () => {
const [isDisabled, changeDisabled] = useState(true);
const toggleDisabled = () => changeDisabled(!isDisabled);
return (
<div>
<EuiBadge>Trap is {isDisabled ? 'disabled' : 'enabled'}</EuiBadge>
<EuiSpacer size="s" />
<EuiFocusTrap disabled={isDisabled}>
<EuiPanel>
<FormExample />
<EuiSpacer size="m" />
<EuiButton onClick={toggleDisabled}>
{`${!isDisabled ? 'Disable' : 'Enable'} Focus Trap`}
</EuiButton>
</EuiPanel>
</EuiFocusTrap>
<EuiSpacer size="l" />
<EuiText>
The button below is not focusable by keyboard as long as the focus trap
is enabled.
</EuiText>
<EuiButton onClick={() => {}}>External Focusable Element</EuiButton>
</div>
);
};
Props
import docgen from '@elastic/eui-docgen/dist/components/focus_trap';
Packages Website Docs Utilities Scroll Index (`packages/website/docs/utilities/scroll/index.mdx`)
Scroll
import { css } from '@emotion/react'; import { Example } from '@site/src/components'; import { EuiFlexGroup, EuiFlexItem, EuiPanel, EuiProvider, EuiText, logicalCSS, logicalCSSWithFallback, useEuiOverflowScroll, useEuiScrollBar } from '@elastic/eui';
import { ScrollContent } from './scroll_content'; import { HorizontalScrollClassNamePreview, HorizontalScrollHookPreview, ScrollBarClassNamePreview, ScrollBarHookPreview, VerticalScrollClassNamePreview, VerticalScrollHookPreview } from './previews';
:::warning Scrollable regions must be focusable, promoted to region and with the right aria-label
Use this utility class to style the browser's scrollbar in a minimal aesthetic. This is best used on small, inner-page contents like panels wherever you have `overflow: auto`.
Use this style function to style the browser's scrollbar in a minimal aesthetic. This is best used on small, inner-page contents like panels.
All parameters are optional and default to specific global settings.
These utilities allow for quickly applying vertical scrolling to a container. They also automatically apply the minimal scroll bar styles as well. If you do not want your content to stretch to 100% height, you will need to manually add a height (or max-height) to the container.
The WithShadows variants are primarily used in modals and flyouts and masks the edges to indicate there is more content. When using these variants, you may want to add a small amount padding to the top and bottom of your content so the mask doesn't overlay it.
### `.eui-yScroll` className
text
Quick utility class for adding vertical scrolling to a container.
Styles hook for adding vertical scrolling to a container.
To mask the top and bottom of the scrolled content, indicating visually that there is more content below, pass in true to the second parameter `mask`.
`useEuiOverflowScroll('y', true);`
Styles hook for adding horizontal scrolling to a container.
To mask the top and bottom of the scrolled content, indicating visually that there is more content below, pass in true to the second parameter `mask`.
`useEuiOverflowScroll('x', true);`
Quick utility for expanding the height of the element to its parents dimensions. Use it to stretch each nested element until the one that applies scroll.
Works on both flex and non-flex elements.
</Example.Description> <Example.Preview>
text
<EuiFlexGroup className="eui-fullHeight" gutterSize="s" responsive={false}>
<EuiFlexItem>
<EuiPanel className="eui-yScroll" color="warning" tabIndex={0}>
<EuiText size="s">
Orbiting this at a distance of roughly ninety-two million miles is
an utterly insignificant little blue green planet whose
ape-descended life forms are so amazingly primitive that they
still think digital watches are a pretty neat idea.
</EuiText>
</EuiPanel>
</EuiFlexItem>
<EuiFlexItem>
<EuiPanel className="eui-yScroll" color="warning" tabIndex={0}>
<EuiText size="s">
Orbiting this at a distance of roughly ninety-two million miles is
an utterly insignificant little blue green planet whose
ape-descended life forms are so amazingly primitive that they
still think digital watches are a pretty neat idea.
</EuiText>
</EuiPanel>
</EuiFlexItem>
</EuiFlexGroup>
<Example.Description> ### euiFullHeight() function
text
Emotion mixin for adding full height scrolling to a container or flex child.
It applies `height: 100%; overflow: hidden;` but also adds `flex: 1 1 auto;` for use within `flex` containers.
Packages Website Docs Utilities Window Events Index (`packages/website/docs/utilities/window-events/index.mdx`)
Window events
import { ModalExample } from './modal_example'; import modalExampleSource from '!raw-loader!./modal_example';
Basic example: closing a modal on escape
Use an EuiWindowEvent to safely and declaratively manage adding and auto-removing event listeners to the window. This is preferable to setting up your own window event listeners because it will remove old listeners when your component unmounts, preventing you from accidentally leaving them around forever.
This modal example registers a listener on the keydown event and listens for ESC key presses, which closes the open modal.
import React from 'react';
import {
EuiModal,
EuiModalBody,
EuiModalHeader,
EuiModalHeaderTitle,
} from '@elastic/eui';
import { ModalExample } from './modal_example';
const BasicModal = ({ onClose }) => (
<EuiModal onClose={onClose} style={{ width: '800px' }}>
<EuiModalHeader>
<EuiModalHeaderTitle>Example modal</EuiModalHeaderTitle>
</EuiModalHeader>
<EuiModalBody>
<p>
This modal closes when you press ESC, using a window event listener.
</p>
</EuiModalBody>
</EuiModal>
);
export default () => <ModalExample modal={BasicModal} />;
Avoiding event conflicts
:::warning Be careful with global listeners
Since window event listeners are global, they can conflict with other event listeners if you aren't careful. :::
The safest and best way to avoid these conflicts is to use event.stopPropagation() at the lowest, most specific level where you are responding to a DOM event. This will prevent the event from bubbling up to the window, and the EuiWindowEvent listener will never be triggered, avoiding the conflict.
import React, { useState } from 'react';
import {
EuiModal,
EuiModalBody,
EuiModalHeader,
EuiModalHeaderTitle,
EuiFieldText,
EuiSpacer,
keys,
} from '@elastic/eui';
import { ModalExample } from './modal_example';
const ConflictModal = (props) => {
const [inputValue, setInputValue] = useState('');
const updateInputValue = (e) => {
setInputValue(e.target.value);
};
const clearInputValueOnEscape = (event) => {
if (event.key === keys.ESCAPE) {
setInputValue('');
event.stopPropagation();
}
};
return (
<EuiModal onClose={props.onClose} style={{ width: '800px' }}>
<EuiModalHeader>
<EuiModalHeaderTitle>Example modal</EuiModalHeaderTitle>
</EuiModalHeader>
<EuiModalBody>
<EuiFieldText
value={inputValue}
onChange={updateInputValue}
onKeyDown={clearInputValueOnEscape}
/>
<EuiSpacer size="s" />
<p>While typing in this field, ESC will clear the field.</p>
<EuiSpacer size="l" />
<p>
Otherwise, the event bubbles up to the window and ESC closes the
modal.
</p>
</EuiModalBody>
</EuiModal>
);
};
export default () => (
<ModalExample
modal={ConflictModal}
buttonText="Open Modal with Conflicting Listener"
/>
);
Tracking mouse position
For some DOM events, you have to listen on the window. One example of this is tracking mouse position. Below, when you click the toggle switch, your mouse position is tracked. When you toggle off, tracking stops.
If you were manually attaching window listeners, you might forget to remove the listener and be silently responding to mouse events in the background for the life of your app. The EuiWindowEvent component manages that unmount/unregister process for you.
```tsx interactive
import React, { useState } from 'react';
import { EuiSwitch, EuiDescriptionList, EuiSpacer, EuiWindowEvent, } from '@elastic/eui';
</Demo>
## Props
import docgen from '@elastic/eui-docgen/dist/services/window_event';
---
### Packages Website Docs Components Containers Flyout Index (`packages/website/docs/components/containers/flyout/index.mdx`)
---
keywords: [EuiFlyout, EuiFlyoutBody, EuiFlyoutHeader, EuiFlyoutFooter, EuiFlyoutMenu]
---
# Flyout
**EuiFlyout** is a fixed position panel that pops in from the side of the window. It should be used to reveal more detailed contextual information or to provide complex forms without losing the user's current state. It is a good alternative to [modals](../modal/index.mdx) when the action is not transient.
Like modals, you control the visibility of the flyout using your own state management, but **EuiFlyout** requires an `onClose` handler for it's internal dismiss buttons.
## Component
:::accessibility Accessibility requirement
Use `aria-labelledby={headingId}` to ensure the flyout is announced to screen readers.
:::
```tsx interactive
import React, { useState } from 'react';
import {
EuiFlyout,
EuiFlyoutBody,
EuiFlyoutHeader,
EuiButton,
EuiText,
EuiTitle,
EuiCodeBlock,
useGeneratedHtmlId,
} from '@elastic/eui';
export default () => {
const [isFlyoutVisible, setIsFlyoutVisible] = useState(false);
const simpleFlyoutTitleId = useGeneratedHtmlId({
prefix: 'simpleFlyoutTitle',
});
let flyout;
const htmlCode = `<EuiFlyout ...>
<EuiFlyoutHeader hasBorder>
<EuiTitle size="m">
<h2></h2>
</EuiTitle>
</EuiFlyoutHeader>
<EuiFlyoutBody>
...
</EuiFlyoutBody>
</EuiFlyout>
`;
if (isFlyoutVisible) {
flyout = (
<EuiFlyout
ownFocus
onClose={() => setIsFlyoutVisible(false)}
aria-labelledby={simpleFlyoutTitleId}
>
<EuiFlyoutHeader hasBorder>
<EuiTitle size="m">
<h2 id={simpleFlyoutTitleId}>A typical flyout</h2>
</EuiTitle>
</EuiFlyoutHeader>
<EuiFlyoutBody>
<EuiText>
<p>
For consistency across the many flyouts, please utilize the
following code for implementing the flyout with a header.
</p>
</EuiText>
<EuiCodeBlock language="html">{htmlCode}</EuiCodeBlock>
</EuiFlyoutBody>
</EuiFlyout>
);
}
return (
<div>
<EuiButton onClick={() => setIsFlyoutVisible(true)}>
Show flyout
</EuiButton>
{flyout}
</div>
);
};
Usage
Accessing flyout elements
Pass a React ref to EuiFlyoutHeader or EuiFlyoutFooter to access its root div. On EuiFlyout, ref points to the flyout panel and scrollContainerRef points to the outer .euiFlyout__content container wrapping the header, body, and footer. EuiFlyoutBody has a separate scrollContainerRef pointing to its inner overflow container.
Normally, the body's container is what scrolls. The outer container scrolls only when using EuiFlyoutBody and the header, body, and footer exceed the viewport height (usually on short screens). To respond to every scroll, attach to both containers.
More complicated flyout
This component also comes with related child components for ease of creating headers, footers and scrolling body content. EuiFlyoutHeader and EuiFlyoutFooter are pinned to the top and bottom of the flyout, respectively, to allow for always visible navigation and actions. The EuiFlyoutBody component will then automatically overflow.
text
/* Detailed source-code truncated for AI context efficiency. */
Sizing
Flyouts come in three predefined sizes of 's' | 'm' | 'l', which define the width relative to the window size with a minimum width defined in pixels. You can otherwise supply your own fixed width in number or string format.
text
/* Detailed source-code truncated for AI context efficiency. */
Padding
All the inner flyout components inherit their padding from the wrapping EuiFlyout component. This ensures that all the horizontal edges line up no matter the paddingSize. When using the "none" size, you will need to accommodate your content with some other way of creating distance to the edges of the flyout.
text
/* Detailed source-code truncated for AI context efficiency. */
Banners
To highlight some information at the top of a flyout, you can pass an EuiCallOut to the banner prop available in EuiFlyoutBody and its layout will adjust appropriately.
text
/* Detailed source-code truncated for AI context efficiency. */
Without ownFocus
Like modals, you will usually want to obscure the page content beneath with ownFocus which wraps the flyout with an EuiOverlayMask . However, there are use-cases where flyouts present more information or controls, but need to maintain the interactions of the page content. By setting ownFocus={false}, the underlying page content will be visible and clickable.
tsx interactive
import React, { useState } from 'react';
import {
EuiFlyout,
EuiFlyoutHeader,
EuiFlyoutBody,
EuiButton,
EuiText,
EuiTitle,
useGeneratedHtmlId,
} from '@elastic/eui';
export default () => {
const [isFlyoutVisible, setIsFlyoutVisible] = useState(false);
const closeFlyout = () => setIsFlyoutVisible(false);
const toggleFlyout = () => setIsFlyoutVisible((isVisible) => !isVisible);
const withoutFocusFlyoutTitleId = useGeneratedHtmlId({
prefix: 'withoutFocusFlyoutTitle',
});
let flyout;
if (isFlyoutVisible) {
flyout = (
<EuiFlyout
ownFocus={false}
onClose={closeFlyout}
aria-labelledby={withoutFocusFlyoutTitleId}
>
<EuiFlyoutHeader hasBorder>
<EuiTitle size="s">
<h2 id={withoutFocusFlyoutTitleId}>A flyout without ownFocus</h2>
</EuiTitle>
</EuiFlyoutHeader>
<EuiFlyoutBody>
<EuiText>
<p>
The page contents is still interactive though screen reader users
will find themselves still within the bounds of the flyout.
</p>
</EuiText>
</EuiFlyoutBody>
</EuiFlyout>
);
}
return (
<div>
<EuiButton onClick={toggleFlyout}>Toggle flyout</EuiButton>
{flyout}
</div>
);
};
Understanding max-width
By default, flyouts will continue to grow with the width of the window. To stop this growth at an ideal width, set maxWidth to true, or pass your own custom size.
:::warning Note that there are some caveats to providing a maxWidth that is smaller than the minWidth. :::
text
/* Detailed source-code truncated for AI context efficiency. */
import { Example } from '@site/src/components';
Focus management
EuiFlyout used with type="overlay" manages focus for keyboard navigation and accessibility. This ensures users can navigate modal content effectively, especially those using screen readers or keyboard-only navigation.
When a EuiFlyout is used, it will automatically:
Move focus to the flyout when opened
Trap focus inside the flyout while open
Return focus to the trigger element when closed
Manual return focus control
Use focusTrapProps to customize the focus behavior:
tsx
<EuiFlyout
focusTrapProps={{
returnFocus: (triggerElement) => {
// Return true to use default behavior (focus triggerElement)
// Return false to prevent default behavior
if (customElement.current) {
customElement.current.focus();
return false;
}
return true;
},
}}
/>
:::warning Manually return focus when the trigger element is removed If the trigger element is removed from the DOM before the flyout closes, focus will be "lost" to the document body. In this case, manually return focus to an appropriate element using focusTrapProps.returnFocus. :::
Push flyout
import PushFlyout from './_flyout_push.mdx';
Resizable flyout
import ResizableFlyout from './_flyout_resizable.mdx';
Container-relative positioning
By default, flyouts use position: fixed and are positioned relative to the viewport. The optional container prop allows you to scope a flyout to a specific region of the page instead. When a container element is provided, the flyout's bounding rect is used to compute inline positioning styles that visually constrain the flyout within the container's bounds. The flyout remains in document.body with position: fixed — no DOM mutations are applied to the container (except push-mode padding).
This is useful when flyouts should not overlap application chrome such as side navigation or toolbars. For example, in Kibana the default container is the application content area so that flyouts stay within the app region.
Resize clamping and responsive breakpoints use the container's width as the reference instead of the viewport width. The container element is treated as read-only — it is not modified by the flyout except for push-mode padding (paddingInlineEnd or paddingInlineStart) when a push flyout is open.
Child flyout inheritance
Child flyouts automatically inherit the container from the parent flyout's context. There is no need to pass the prop explicitly to child flyouts. To use the document body as the reference container for a child (overriding an inherited container), pass container={null}.
mdx-code-block
import { EuiBetaBadge } from '@elastic/eui';
Flyout menu {#flyout-menu}
import FlyoutMenu from './_flyout_menu.mdx';
Guidelines
import SessionManagement from './_session_management.mdx';
Props
import docgen from '@elastic/eui-docgen/dist/components/flyout'; import docgenFlyoutMenuTypes from '@elastic/eui-docgen/dist/components/flyout/flyout_menu/types.docgen.json';
Packages Website Docs Components Containers Modal Index (`packages/website/docs/components/containers/modal/index.mdx`)
A modal is best for focusing the user's attention on a small amount of content and prompting a decision, such as a confirmation. Use it to temporarily interrupt the current task and block interaction with the content below.
Each EuiModal needs a specific set of nested child components. You can omit some if needed, but do not change their order.
:::tip Modal vs flyout
If your modal content is complex or takes a long time to complete, consider using an EuiFlyout instead. :::
Components
:::accessibility Accessibility requirement Use aria-labelledby={modalTitleId} to ensure the modal is announced to screen readers. :::
Default
Modals use an EuiOverlayMask to obscure content beneath.
text
/* Detailed source-code truncated for AI context efficiency. */
import { Example } from '@site/src/components';
Focus management
EuiModal manages focus for keyboard navigation and accessibility. This ensures users can navigate modal content effectively, especially those using screen readers or keyboard-only navigation.
When a EuiModal is used, it will automatically:
Move focus to the modal when opened
Trap focus inside the modal while open
Return focus to the trigger element when closed
Manual return focus control
Use focusTrapProps to customize the focus behavior:
tsx
<EuiModal
focusTrapProps={{
returnFocus: (triggerElement) => {
// Return true to use default behavior (focus triggerElement)
// Return false to prevent default behavior
if (customElement.current) {
customElement.current.focus();
return false;
}
return true;
},
}}
/>
:::warning Manually return focus when the trigger element is removed If the trigger element is removed from the DOM before the modal closes, focus will be "lost" to the document body. In this case, manually return focus to an appropriate element using focusTrapProps.returnFocus. :::
Confirm modal
import ConfirmModal from './_confirm_modal.mdx';
Usage
import Patterns from './_patterns.mdx';
Guidelines
import Guidelines from './_guidelines.mdx';
Props
import docgen from '@elastic/eui-docgen/dist/components/modal';
Packages Website Docs Components Containers Panel Index (`packages/website/docs/components/containers/panel/index.mdx`)
keywords: [EuiPanel, EuiSplitPanel]
Panel
EuiPanel is a building block component. Use it as a layout helper for containing content. It is also commonly used as a base for other larger components like EuiPage, EuiPopover and EuiCard.
Component
Default
Panels default to a flat bordered look (hasBorder=true, hasShadow=false). Use shadow only when you need elevation.
tsx interactive
import React from 'react';
import { EuiPanel } from '@elastic/eui';
export default () => (
<EuiPanel>
Default panel
</EuiPanel>
);
Split
import SplitPanel from './_split_panel.mdx';
Usage
Padding
The most basic use is to simply add padding around your content. The preferred padding options are none, s, m (default) and l.
By default, EuiPanel is flat with hasBorder=true on and hasShadow=false. Use hasShadow when you need elevation. Be mindful of not using too many nested panels with these settings.
:::warning Certain allowed combinations of shadow, border, and color depend on the current theme.
For instance, only plain or transparent panels can have a border and/or shadow. The default theme doesn't allow combining the hasBorder option with hasShadow.
Use color to add background shading to your panel and provide an additional helpful aesthetic to your container in context. Be mindful to use color sparingly. You can also remove the rounded corners depending on the placement of your panel with borderRadius="none"
Passing color="transparent" can give you a quick empty box simply for adding padding to all sides.
tsx interactive
import React from 'react';
import { EuiPanel, EuiSpacer } from '@elastic/eui';
export default () => (
<div>
<EuiPanel color="subdued" borderRadius="none" hasShadow={false}>
<p>I am a simple shaded box</p>
</EuiPanel>
<EuiSpacer />
<EuiPanel color="transparent" hasBorder={false}>
<p>I am a transparent box simply for padding</p>
</EuiPanel>
</div>
);
Growing height
Using EuiPanel in an EuiFlexItem will always grow its height to match. This is great for rows of panels. However, you can also turn this feature off by setting grow={false}.
tsx interactive
import React from 'react';
import { EuiPanel, EuiFlexGroup, EuiFlexItem, EuiText } from '@elastic/eui';
export default () => (
<EuiFlexGroup gutterSize="l">
<EuiFlexItem>
<EuiPanel>
<EuiText>
<p>
I am some panel content...
<br /> ... <br /> ... <br />
whose content is tall
</p>
</EuiText>
</EuiPanel>
</EuiFlexItem>
<EuiFlexItem>
<EuiPanel grow={true}>
<EuiText>
<p>I am some panel content... whose content will grow</p>
</EuiText>
</EuiPanel>
</EuiFlexItem>
<EuiFlexItem>
<EuiPanel grow={false}>
<EuiText>
<p>I am some panel content... whose content did not grow</p>
</EuiText>
</EuiPanel>
</EuiFlexItem>
</EuiFlexGroup>
);
Guidelines
import Guidelines from './_guidelines.mdx';
Props
import docgen from '@elastic/eui-docgen/dist/components/panel';
import PropTableSplitPanel from './_split_panel_props';
Packages Website Docs Components Containers Tabs Index (`packages/website/docs/components/containers/tabs/index.mdx`)
keywords: [EuiTabs, EuiTab]
Tabs
Use tabs to organize related but unique content for a single idea or subject. Tabs show and hide content using in-page navigation UI.
EuiTabs is a wrapping component that requires EuiTab components as direct children. You control the displayed contents and current state through props on EuiTab like isSelected and onClick.
Use the prepend and append tab props to add content before and after the tab label respectively.
Component
text
/* Detailed source-code truncated for AI context efficiency. */
Usage
Tab sizes
EuiTabs allow a size prop. In general you should always use the default (medium) size. The small size is best for when placing inside popovers or other small containers. Reserve using the large size for when using as primary page navigation, like inside of EuiPageHeader.
You can also use the expand prop to evenly stretch each tab horizontally.
text
/* Detailed source-code truncated for AI context efficiency. */
Bottom border
The bottomBorder helps to separate the tab group from the content below and is on by default. However, some components like flyouts already provide borders which can act as the divider. In this case you can turn the border off with bottomBorder={false}.
text
/* Detailed source-code truncated for AI context efficiency. */
Tabbed content
EuiTabbedContent makes it easier to associate tabs with content based on the selected tab. Use the initialSelectedTab prop to specify which tab to initially select.
text
/* Detailed source-code truncated for AI context efficiency. */
Controlled tabbed content
You can also use the selectedTab and onTabClick props to take complete control over tab selection. This can be useful if you want to change tabs based on user interaction with another part of the UI.
text
/* Detailed source-code truncated for AI context efficiency. */
Guidelines
import Guidelines from './_guidelines.mdx';
Props
import docgen from '@elastic/eui-docgen/dist/components/tabs';
Packages Website Docs Components Display Badge Index (`packages/website/docs/components/display/badge/index.mdx`)
EuiBadge highlights key pieces of information. While badges will space themselves automatically when used repeatedly, it's best practice to wrap them in an EuiBadgeGroup. This ensures they wrap properly when space is limited (as shown below).
Components
Default
tsx interactive
import React, { useState } from 'react';
import {
EuiBadge,
} from '@elastic/eui';
export default () => <EuiBadge>Default badge</EuiBadge>;
Notification badge
import NotificationBadge from './_notification_badge.mdx';
Beta badge
import BetaBadge from './_beta_badge.mdx';
Usage
Color options
Badge colors can be configured with the color prop and the related fill prop. When a named color like primary is passed to the color prop, the fill prop determines whether the badge will use filled (more intense) or non-filled (less intense) colors. By default, all badges use non-filled colors.
text
/* Detailed source-code truncated for AI context efficiency. */
Badge with Icon
Badges can use icons on the left and right (default) sides.
tsx interactive
import React from 'react';
import { EuiBadge } from '@elastic/eui';
export default () => (
<div>
<EuiBadge color="hollow" iconType="cross" iconSide="right">
Hollow
</EuiBadge>
<EuiBadge iconType="check">Default</EuiBadge>
<EuiBadge iconType="lensApp" color="primary">
Primary with an app icon
</EuiBadge>
<EuiBadge iconType="return" />
</div>
);
Badge with onClick events
Badges can have onClick events applied to the badge itself or the icon within the badge. The latter option is useful for when you might use badges in other components (like a tag system with autocomplete where you need close events).
:::note onClick with iconOnClick
When providing both these click handlers, EuiBadge must alter the contents so that it does not contain nested button tags. Please make note that if you provide props other than those explicit to EuiBadge, they will always be applied to the main button tag which may be inside of the outer most tag.
:::
text
/* Detailed source-code truncated for AI context efficiency. */
Badge for health status
Badges can work as health status indicators in places where there are a lot of repeated statuses, e.g. in tables.
Badges can also be made to render anchor tags by passing an href.
tsx interactive
import React from 'react';
import { EuiBadge, EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
export default () => (
<EuiFlexGroup wrap responsive={false} gutterSize="xs">
<EuiFlexItem grow={false}>
<EuiBadge color="#BADA55" href="#/display/badge">
badge as an anchor
</EuiBadge>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiBadge color="hollow" href="#/display/badge" target="blank">
anchor with target specified
</EuiBadge>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiBadge
color="accent"
href="#/display/badge"
iconType="bolt"
iconSide="right"
iconOnClick={() => {}}
iconOnClickAriaLabel="Example of onClick event for icon within the anchor"
>
anchor with an icon and iconOnClick
</EuiBadge>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiBadge color="success" href="#/display/badge" isDisabled={true}>
disabled anchor badge
</EuiBadge>
</EuiFlexItem>
</EuiFlexGroup>
);
Badge groups and truncation
Badges, like buttons, will only every be a single line of text. This means text will not wrap, but be truncated if the badge's width reaches that of its parent's.
For this reason, badges also auto-apply the inner text of the badge to the title attribute of the element to provide default browser tooltips with the full badge text.
To ensure proper wrapping, truncation and spacing of multiple badges, it is advisable to wrap them in a EuiBadgeGroup.
tsx interactive
import React from 'react';
import { EuiBadge, EuiPanel, EuiBadgeGroup } from '@elastic/eui';
export default () => (
<EuiPanel style={{ maxWidth: 200 }}>
<EuiBadgeGroup gutterSize="s">
<EuiBadge>Badge with simple text being truncated</EuiBadge>
<EuiBadge iconType="clock">Badge with icon being truncated</EuiBadge>
<EuiBadge onClick={() => {}} onClickAriaLabel="Click this badge to...">
Badge with onClick being truncated
</EuiBadge>
<EuiBadge
iconType="cross"
iconSide="right"
iconOnClick={() => {}}
iconOnClickAriaLabel="Click this icon to..."
>
Badge with iconOnClick being truncated
</EuiBadge>
<EuiBadge
iconType="cross"
iconSide="right"
onClick={() => {}}
onClickAriaLabel="Click this badge to..."
iconOnClick={() => {}}
iconOnClickAriaLabel="Click this icon to..."
>
Badge with both onClicks being truncated
</EuiBadge>
</EuiBadgeGroup>
</EuiPanel>
);
Guidelines
import Guidelines from './_guidelines.mdx';
Props
import docgen from '@elastic/eui-docgen/dist/components/badge';
Packages Website Docs Components Display Comment List Index (`packages/website/docs/components/display/comment-list/index.mdx`)
keywords: [EuiCommentList, EuiComment]
Comment list
The EuiCommentList is a timeline component built on top of EuiTimeline. It allows you to display comments or logged actions that either a user or a system has performed.
:::accessibility Accessibility recommendation Provide a descriptive aria-label or the ID of an external label to the aria-labelledby prop of the EuiCommentList. A timelineAvatarAriaLabel should be provided for every EuiComment with or without a timelineAvatar as IconType. :::
Basic comment list
Use EuiCommentList to display a list of EuiComments. Pass an array of EuiComment objects and EuiCommentList will generate a comment thread.
text
/* Detailed source-code truncated for AI context efficiency. */
Comment
The EuiComment is flexible and adapts the design according to the props passed.
import { CommentListProps, CommentListStyle } from './comment_list_props.tsx';
timelineAvatar: Shows an avatar that should indicate who is the author of the comment. To customize, pass a string as an EuiIcon['type'] or an EuiAvatar. Use in conjunction with timelineAvatarAriaLabel to pass an aria label to the avatar. If no avatar is provided, it will default to an avatar with a user icon.
eventIcon: Icon that shows before the username. Use in conjunction with eventIconAriaLabel to pass an aria label to the event icon.
username: Author of the comment.
event: Describes the event that took place.
timestamp: Time of occurrence of the event.
actions: Custom actions that the user can perform from the comment's header. When having multiple actions, consider grouping them in a nested menu system using a EuiPopover with a EuiContextMenu.
children: A user message or any custom component.
The following demo shows how you can combine different props to create different types of comments events like a regular, update, update with a danger background and a custom one.
text
/* Detailed source-code truncated for AI context efficiency. */
Timeline avatar
The timeline icon is a very important part of the comment:
By default, each EuiComment shows an avatar with the user icon. A timelineAvatarAriaLabelshould be provided when using this default option.
You can customize your avatar by passing to the timelineAvatar any of the icon types that EuiIcon supports. The icon will show inside a subdued avatar. Consider this option when showing a system update. Providing a timelineAvatarAriaLabel is recommended.
You can further customize the timeline icon by passing to the timelineAvatar a EuiAvatar.
tsx interactive
import React from 'react';
import {
EuiCommentList,
EuiComment,
EuiCode,
EuiText,
EuiAvatar,
} from '@elastic/eui';
export default () => (
<EuiCommentList aria-label="An example with different timeline icons">
<EuiComment
username="andred"
timelineAvatarAriaLabel="Andre Diaz"
event="is using a default avatar"
>
<EuiText size="s">
<p>
The avatar initials is generated from the <EuiCode>username</EuiCode>{' '}
prop.
</p>
</EuiText>
</EuiComment>
<EuiComment
username="system"
timelineAvatarAriaLabel="System"
timelineAvatar="dot"
event={
<>
The <EuiCode>timelineAvatar</EuiCode> is using a{' '}
<EuiCode>dot</EuiCode> icon.
</>
}
/>
<EuiComment
username="cat"
timelineAvatarAriaLabel="Beautiful cat"
event="is using a custom avatar"
timelineAvatar={
<EuiAvatar name="Cat" imageUrl="https://picsum.photos/id/40/64" />
}
>
<EuiText size="s">
<p>
The <EuiCode>timelineAvatar</EuiCode> is using a custom{' '}
<strong>EuiAvatar</strong>.
</p>
</EuiText>
</EuiComment>
</EuiCommentList>
);
Comment event actions
There are scenarios where you might want to allow the user to perform actions related to each comment. Some common actions include: editing, deleting, sharing and copying. To add custom actions to a comment, use the actionsprop. These will be placed to the right of the metadata in the comment's header. We recommend using a EuiButtonIcon to trigger an action. When having multiple actions, consider grouping them in a nested menu system using a EuiPopover with a EuiContextMenu.
text
/* Detailed source-code truncated for AI context efficiency. */
We use the EuiMarkdownEditor to post the EuiComment.children. This means the content uses Markdown.
When the new EuiComment is posted, we use the EuiMarkdownFormat to wrap the EuiComment.children and render the Markdown correctly.
When dealing with asynchronous events like posting a message we recommend setting the EuiMarkdownEditor to a readOnly state and the "Add comment" EuiButton to a isLoading state. This will ensure users understand that the content cannot be changed while the comment is being submitted.
text
/* Detailed source-code truncated for AI context efficiency. */
Props
import docgen from '@elastic/eui-docgen/dist/components/comment_list';
Packages Website Docs Components Display Empty Prompt Index (`packages/website/docs/components/display/empty-prompt/index.mdx`)
keywords: [EuiEmptyPrompt]
Empty prompt
import illustrationUrl from '!url-loader!../../../../static/images/empty_prompt/illustration.svg'; import illustrationSource from '!raw-loader!../../../../static/images/empty_prompt/illustration.svg';
The EuiEmptyPrompt is the building block to create an empty state. You can use it as a placeholder for any type of empty content. They are especially helpful for replacing entire pages or parts of a product that contain no content.
Component
While no one piece of content is required, each EuiEmptyPrompt should contain at least a title (wrapped in an HTML heading element) and/or a description. They usually contain one or more actions that promotes the primary call-to-actions. You can also provide a footer to direct users towards making informed decisions.
tsx interactive
import React from 'react';
import { EuiEmptyPrompt, EuiButton, EuiTitle, EuiLink } from '@elastic/eui';
export default () => (
<EuiEmptyPrompt
iconType="logoSecurity"
title={<h2>Start adding cases</h2>}
body={<p>Add a new case or change your filter settings.</p>}
actions={
<EuiButton color="primary" fill>
Add a case
</EuiButton>
}
footer={
<>
<EuiTitle size="xxs">
<h3>Want to learn more?</h3>
</EuiTitle>
<EuiLink href="#" target="_blank">
Read the docs
</EuiLink>
</>
}
/>
);
Usage
Less content, more actions
You can remove parts of the prompt to simplify it. You can also provide an array of multiple actions. Be sure to list primary actions first and secondary actions as empty buttons.
tsx interactive
import React from 'react';
import { EuiEmptyPrompt, EuiButton, EuiButtonEmpty } from '@elastic/eui';
export default () => (
<EuiEmptyPrompt
title={<h2>Upgrade your license to use Machine Learning</h2>}
actions={[
<EuiButton color="primary" fill>
Upgrade
</EuiButton>,
<EuiButtonEmpty color="primary">Start a trial</EuiButtonEmpty>,
]}
/>
);
Panel options
The EuiEmptyPrompt is wrapped by EuiPanel. By default, the panel is set to transparent but you can customize other panel options like color, hasBorder and paddingSize. Changing the color prop will also attempt to adjust the iconColor and footer color.
Read the usage guidelines to better understand when to use certain panel props.
tsx interactive
import React, { FunctionComponent } from 'react';
import {
EuiEmptyPrompt,
EuiButton,
EuiTitle,
EuiLink,
EuiEmptyPromptProps,
} from '@elastic/eui';
export default ({ color }: { color: EuiEmptyPromptProps['color'] }) => {
return (
<EuiEmptyPrompt
iconType="securityAnalyticsApp"
title={<h2>Start adding cases</h2>}
color={color}
body={<p>Add a new case or change your filter settings.</p>}
actions={
<EuiButton color="primary" fill>
Add a case
</EuiButton>
}
footer={
<>
<EuiTitle size="xxs">
<h3>Want to learn more?</h3>
</EuiTitle>
<EuiLink href="#" target="_blank">
Read the docs
</EuiLink>
</>
}
/>
);
};
Title sizes and icon colors
Other customization options include changing thetitleSize to any of the EuiTitle sizes and iconColor. When using an application or solution logo as the iconType, you can reset to the multi-tone colors with iconColor="default"
tsx interactive
import React from 'react';
import { EuiEmptyPrompt, EuiButton } from '@elastic/eui';
export default () => (
<EuiEmptyPrompt
iconType="securityAnalyticsApp"
iconColor="default"
title={<h2>Start adding cases</h2>}
titleSize="xs"
body={<p>Add a new case or change your filter settings.</p>}
actions={
<EuiButton size="s" color="primary" fill>
Add a case
</EuiButton>
}
/>
);
Loading and error prompts
Empty prompts can also be used to emulate loading and error states, by utilizing the same patterns.
For loading states, instead of passing a iconType, you can provide a custom icon and pass in one of our loading components.
For error states, you can simply set the color to danger.
tsx interactive
import React from 'react';
import { EuiEmptyPrompt } from '@elastic/eui';
export default () => (
<EuiEmptyPrompt
iconType="error"
color="danger"
title={<h2>Unable to load your dashboards</h2>}
body={
<p>
There was an error loading the Dashboard application. Contact your
administrator for help.
</p>
}
/>
);
Layout
You can supply a layout of either "horizontal" or "vertical" with the default being vertical. When creating empty states we want the content to be short and straight to the point. So most of the time, the vertical layout is enough. All the content will be center aligned and this type of text alignment only works with small content.
When you have longer body text with multiple calls to action, you can use the horizontal layout. This layout works best when you can provide a larger graphic like an illustration as the icon. For consistency, we recommend providing the illustration using a EuiImage with size="fullWidth".
import React from 'react';
import {
EuiEmptyPrompt,
EuiButton,
EuiTitle,
EuiLink,
EuiImage,
} from '@elastic/eui';
import illustration from './illustration.svg';
export default () => {
return (
<>
<EuiEmptyPrompt
icon={<EuiImage size="fullWidth" src={illustration} alt="" />}
title={<h2>Create your first visualization</h2>}
layout="horizontal"
color="plain"
body={
<>
<p>
There are no visualizations to display. This tool allows you to
create a wide range of charts, graphs, maps, and other graphics.
</p>
<p>
The visualizations you create can be easily shared with your
peers.
</p>
</>
}
actions={
<EuiButton color="primary" fill>
Create visualization
</EuiButton>
}
footer={
<>
<EuiTitle size="xxs">
<span>Want to learn more?</span>
</EuiTitle>{' '}
<EuiLink href="#" target="_blank">
Read the docs
</EuiLink>
</>
}
/>
</>
);
};
More types of empty states
EuiEmptyPrompt can be used for more than just empty pages. The following example showcases different types of empty states that you can create with the EuiEmptyPrompt. For a full list see the usage guidelines.
import MultipleTypes from './_multiple_types';
Using in a page template
When using a EuiEmptyPrompt in a EuiPageTemplate, we recommend using the namespaced component so the template can determine which how to display the empty prompt based on the rest of the template configuration.
import React from 'react';
import {
EuiPageTemplate,
EuiButton,
EuiTitle,
EuiLink,
EuiImage,
} from '@elastic/eui';
import illustration from './illustration.svg';
export default () => (
<EuiPageTemplate minHeight="0">
<EuiPageTemplate.EmptyPrompt
title={<h2>Create your first visualization</h2>}
icon={<EuiImage size="fullWidth" src={illustration} alt="" />}
color="plain"
layout="horizontal"
body={
<>
<p>
There are no visualizations to display. This tool allows you to
create a wide range of charts, graphs, maps, and other graphics.
</p>
<p>
The visualizations you create can be easily shared with your peers.
</p>
</>
}
actions={
<EuiButton color="primary" fill>
Create visualization
</EuiButton>
}
footer={
<>
<EuiTitle size="xxs">
<span>Want to learn more?</span>
</EuiTitle>{' '}
<EuiLink href="#" target="_blank">
Read the docs
</EuiLink>
</>
}
/>
</EuiPageTemplate>
);
You can then tie multiple types of empty states together to create a seamless loading to empty or loading to error experience. The following example shows how to incorporate these states with EuiPageTemplate.EmptyPrompt.
{/* setting a fixed height to avoid jumps on the page as the state changes */} <Demo previewWrapper={({ children }) => ( {children} )}>
text
/* Detailed source-code truncated for AI context efficiency. */
Guidelines
import Guidelines from './_guidelines.mdx';
Props
import docgen from '@elastic/eui-docgen/dist/components/empty_prompt';
Packages Website Docs Components Display Icons Index (`packages/website/docs/components/display/icons/index.mdx`)
keywords: [EuiIcon]
Icons
import { EuiCodeBlock, EuiSpacer, EuiSplitPanel, EuiToken } from '@elastic/eui';
EuiIcon is a handy component for using our custom glyphs and logos. The type prop accepts either an enumerated name from one of the sets below, a location to a custom SVG asset, or a React Element.
Component
tsx interactive
import React from 'react';
import { EuiIcon } from '@elastic/eui';
export default () => <EuiIcon type="grid" />;
:::accessibility Accessibility recommendation If possible, provide a descriptive title based on the icon use. If no title is provided, the icon is considered purely decorative, and it will default to aria-hidden=true. :::
All icons
Glyphs
Glyphs are small, monochromatic icons that typically should always use the default size of size="m". They are named according to their appearance or the action they represent. For example, star, play, etc.
If you would like to contribute to our growing list of glyphs, you can follow these guidelines.
import { IconSearch } from '@site/src/components/display/icons'; import { typeToPathMapDocsGlyphIconTypes as iconTypes, typeToPathMapSynonyms as iconSynonyms, } from '../../../../../eui/src/components/icon/icon_map';
import React from 'react';
import { css } from '@emotion/react';
import { EuiCodeBlock, EuiSpacer } from '@elastic/eui';
import { IconSearch } from '@site/src/components/display/icons';
import {
typeToPathMapDocsGlyphIconTypes as iconTypes,
typeToPathMapSynonyms as iconSynonyms,
} from '../../../../../eui/src/components/icon/icon_map';
export default () => (
<div css={css`height: fit-content;`}>
<EuiCodeBlock language="tsx" isCopyable paddingSize="m">
{'<EuiIcon type="warning" />'}
</EuiCodeBlock>
<EuiSpacer />
<IconSearch iconTypes={iconTypes} iconSynonyms={iconSynonyms} />
</div>
);
Elastic logos
These logos are restricted in use to Elastic products or when referencing Elastic products. They are multi-color with some internal paths having a class of .euiIcon__fillNegative to handle flipping colors for dark mode. The only other colors most of them support are ghost and text which turn them into a solid shape.
import { typeToPathMapDocsLogoIconTypes as iconTypesLogos } from '../../../../../eui/src/components/icon/icon_map';
/* Detailed source-code truncated for AI context efficiency. */
Apps
App logos are logos for Elastic Apps, and can contain multiple colors. Normally the Elastic Design team creates those icons. They are not meant to be used outside of Elastic, because they represent our apps. App logos are usually displayed at 32x32 or above and can contain multiple colors.
Our set of App Icons are a subset of what the Elastic Design team provides. If you require an icon from that set which EUI does not currently provide, please open a Feature Request for the EUI team to add it.
import { typeToPathMapDocsAppIconTypes as iconTypesApps } from '../../../../../eui/src/components/icon/icon_map';
The default behavior of icons is to inherit from the text color. You can use the color prop to assign a custom color which accepts a named color from our palette or a valid CSS color data type which will be passed down through the inline-style fill property. We recommend relying on the EUI named color palette unless the custom color is initiated by the user (like as a graph color).
Two-tone icons, like our app style icons, will behave similarly to normal glyphs when provided a specific color by applying the color to all the shapes within. You can force the icon to match the parent's text color by passing color="inherit" to the icon.
import { iconColors } from './icon_colors.ts' import iconColorsSource from '!raw-loader!./icon_colors';
The type prop can accept a valid enum, string or React SVG Element. When using a custom SVG, please make sure it sits on a square canvas and preferably utilizes one of EUI's sizes (16x16 or 32x32).
When using custom SVGs for simple glyphs, remove all fill attributes on the SVG and utilize the CSS helpers if you have complex logos that need to work with theming.
:::warning Important considerations when importing SVGs as components
When importing an SVG as a component like import { ReactComponent as ReactLogo } from './logo.svg';, keep in mind that the component will not support the title prop. The title prop is designed to only work with our icons or SVGs imported as images or passed as a Data URL. So, if you're importing your SVG as a component, be sure to use anaria-label instead of a title prop to enhance accessibility and avoid potential issues.
:::
import customSvg from '!url-loader!./custom.svg'; import customSvgSource from '!raw-loader!./custom.svg';
/* Detailed source-code truncated for AI context efficiency. */
Using icons outside of React
EUI publishes raw SVG files and a JSON manifest for consumers who need to use icons outside of the React component layer - for example, to generate canvas images, produce CSS background images at build time, or render icons in non-React environments.
Individual SVG files are available under @elastic/eui/dist/svgs/. For example:
:::warning Server-side and build-time use only eui-icons.json is intended for server-side or build-time use (e.g. Node.js scripts, SSR, canvas rasterization). Importing it in client-side code will inline all SVG strings into your JavaScript bundle. :::
Tokens
Tokens are most commonly used to visually signify field or code types. An EuiToken accepts any valid EuiIcon as its iconType property. However, icons designed specifically for use in the EuiToken are prefixed with "token" in their name and have pre-defined styles.
import { typeToPathMapDocsTokenIconTypes as iconTypesTokens } from '../../../../../eui/src/components/icon/icon_map';
By default, an iconType with the token prefix (i.e. those listed above) will have predefined styles. However, any valid iconType can be passed and, in either case, the shape, size, color, and fill can be customized.
import docgen from '@elastic/eui-docgen/dist/components/icon';
Packages Website Docs Components Display Illustrations Index (`packages/website/docs/components/display/illustrations/index.mdx`)
keywords: [EuiIllustration, illustrations, svg]
Illustrations
EuiIllustration renders theme-adaptive SVGs. Each illustration ships a light and a dark variant and the component inlines the one that matches the current theme.
SVGs are sourced from @elastic/eui-illustrations, a standalone package that is independently versioned and published from @elastic/eui. This means the catalog can grow and change without an @elastic/eui release.
Component
Pass an illustration asset to the type prop. The component picks the correct color-mode variant automatically, so try toggling the theme in the top toolbar.
import { illustrations } from '@elastic/eui-illustrations';
```tsx
import React from 'react';
import { EuiIllustration } from '@elastic/eui';
import { dashboard } from '@elastic/eui-illustrations';
import { css } from '@emotion/react';
</Demo>
:::accessibility Accessibility recommendation
The accessible label defaults to the illustration's `title`. Provide a more specific `alt` when the illustration conveys meaning
or pass `alt=""` to mark it as purely decorative (it will be hidden from assistive technology).
:::
## All illustrations
Search the full catalog exported from `@elastic/eui-illustrations`. Select an illustration to reveal the code.
import { IllustrationGallery } from './illustration_gallery';
## Props
import docgen from '@elastic/eui-docgen/dist/components/illustration';
---
### Packages Website Docs Components Display Toast Index (`packages/website/docs/components/display/toast/index.mdx`)
---
keywords: [EuiToast, EuiGlobalToastList]
---
# Toast
import previewWrapper from './_preview_wrapper';
**EuiToast** allows for small notes that appear in the bottom right of the screen. They should be used for ephemeral, live actions (think **save complete** or **something just finished right now**).
They should not be used for historical actions (**your report built 30 minutes ago**). This means that a user should never be greeted with toasts when starting a session. Toasts should be brief and avoid long paragraphs of text or titling.
## Component
<Demo previewWrapper={previewWrapper}>
```tsx interactive
import React from 'react';
import { EuiToast } from '@elastic/eui';
export default () => (
<EuiToast
title="Example of a good toast"
text="A good toast message is short and to the point. It should very rarely span multiple lines."
onClose={() => {}}
/>
);
Usage
Toast list
text
/* Detailed source-code truncated for AI context efficiency. */
Colors
Each color variant displays a specific visual type toast and renders by default a corresponding icon.
Use primary for general information.
Use success to let the user know that something completed successfully. Show a success message when the program detects that something completed successfully.
Use warning to warn the user against decisions they might regret. Show a warning message when the program detects that something is not behaving right, but it didn't cause any termination.
Use danger to let the user know that something went wrong. For example if you want to communicate an error. You should show an error message when the issue is terminal, this doesn't always mean that the operation stops completely, but the task is not complete.
If you need to use a custom icon, use the iconType prop to override it.
```tsx interactive
import React from 'react';
import { EuiToast, EuiSpacer } from '@elastic/eui';
export default () => ( <>
text
</>
);
text
</Demo>
### Actions
Use `actionProps` to add a primary and an optional secondary action. A standalone secondary action without a primary is not supported.
<Demo previewWrapper={previewWrapper}>
```tsx interactive
import React from 'react';
import { EuiToast } from '@elastic/eui';
export default () => (
<EuiToast
title="Created report for dashboard 'Tuba Sales by Region'"
color="success"
text="Your export has finished processing."
actionProps={{
primary: { children: 'Download' },
secondary: { children: 'Dismiss' },
}}
/>
);
Countdown animation
To show a countdown animation on top of the toast to indicate the remaining time until it dismisses, use animationMs.
```tsx interactive
import React, { useEffect, useState } from 'react';
import { EuiToast, EuiButton, EuiFlexGroup } from '@elastic/eui';
</Demo>
## Guidelines
import Guidelines from './_guidelines.mdx';
<Guidelines />
## Props
import docgen from '@elastic/eui-docgen/dist/components/toast';
---
### Packages Website Docs Components Display Tour Index (`packages/website/docs/components/display/tour/index.mdx`)
---
keywords: [EuiTour, EuiTourStep, useEuiTour]
---
# Tour
The tour components provided by EUI allow for a flexible and customizable way to showcase items on a page in an ordered manner by augmenting existing elements on the page without altering functionality.
:::note
Examples on this page use `localStorage` to persist state.
:::
## Usage
### Wrap target element
The **EuiTourStep** component is the base for building a feature tour or an individual popover for onboarding.
All content and actions including titles, headings, and buttons are customizable via props.
```tsx interactive
import React, { useState } from 'react';
import { EuiButtonEmpty, EuiFieldText, EuiText, EuiSpacer, EuiTourStep } from '@elastic/eui';
export default () => {
const [isOpen, setIsOpen] = useState(false);
const beginTour = () => {
setIsOpen(true);
};
return (
<div>
<EuiButtonEmpty size="s" flush="left" iconType="refresh" onClick={beginTour}>
Begin tour
</EuiButtonEmpty>
<EuiSpacer size="m" />
<EuiTourStep
content={
<EuiText size="s">
<p>The tour step content.</p>
</EuiText>
}
isStepOpen={isOpen}
minWidth={300}
onFinish={() => setIsOpen(false)}
step={1}
stepsTotal={1}
title="Title of the current step"
subtitle="Title of the full tour (optional)"
anchorPosition="rightCenter"
zIndex={1}
>
<EuiFieldText
placeholder="Placeholder text"
/>
</EuiTourStep>
</div>
);
};
Anchor to DOM element
Instead of wrapping the target element, use the anchor prop to specify a DOM node. Accepted values include an HTML element reference, a function returning an HTML element, or a CSS selector string such as anchor="#anchorTarget".
text
/* Detailed source-code truncated for AI context efficiency. */
Guided tour
Users proceed through tour steps without needing to complete actions on the underlying page. In this scenario, consider showing both Close tour and Next buttons.
text
/* Detailed source-code truncated for AI context efficiency. */
Standalone steps
Each EuiTourStep can be configured independently via props. In this case, each component is stateless and needs to be paired with some form of state management for navigation. The later examples showcase other ways to handle state management via useEuiTour and EuiTour.
text
/* Detailed source-code truncated for AI context efficiency. */
useEuiTour hook
The useEuiTour hook provides minimal state management using a predefined React reducer. Pass an array of steps consisting of accepted props, and an object of global configuration. The result is a full configuration object for each step, a set of reducer actions to perform state changes, and an up-to-date state object derived from the internal reducer.
text
/* Detailed source-code truncated for AI context efficiency. */
EuiTour render prop component
The EuiTour render prop component provides minimal state management. An alternative to the useEuiTour hook, EuiTour can be used for React class components and use cases with a single wrapping component.
text
/* Detailed source-code truncated for AI context efficiency. */
Tour demo
A complete tour set in a more realistic application UI. Unlike other examples on this page, the demo does not use localStorage to persist state.
text
/* Detailed source-code truncated for AI context efficiency. */
Guidelines
import Guidelines from './_guidelines.mdx';
Props
import docgen from '@elastic/eui-docgen/dist/components/tour';
Packages Website Docs Components Forms Selection Overview (`packages/website/docs/components/forms/selection/overview.mdx`)
EUI currently offers over 4 ways to select an option from a list (6 including checkboxes and radios).
Choosing the right selection component may seem tricky at first - with so many similar options, it can feel overwhelming figuring out which component to use. This comprehensive guide will help you decide which component to use and when, including detailed explanations and comparisons for each component.
Summary
When choosing a selection component, consider these key factors:
Number of options: small sets (<7) work well with radio/checkbox groups, medium sets (<12) with EuiSelect, and larger sets need search functionality.
Selection type: single vs. multiple selection requirements.
Display needs: whether you need custom option rendering or simple text options.
User input: whether users should be able to add custom values.
Accessibility: native components (EuiSelect, radio/checkbox groups) are generally more accessible.
For most use cases, start with the simpler native components (EuiSelect, EuiRadioGroup, EuiCheckboxGroup) and only move to more complex components when you need their specific features.
Which selection component should I use?
The following decision tree will help you choose the right component based on your specific needs.
Single selection
When you have a small set of options (<7) and all options are equally important in terms of visibility, use EuiRadioGroup.
When you have a small set of options (<12) and only the current selected option has UI importance, reach for EuiSelect first when possible, over other more complex selection components.
EuiSelect renders a basic HTML <select> element, providing the best cross-browser and cross-device affordances (particularly for mobile devices).
Native form controls are more familiar to end users due to their ubiquity across the internet.
When you have a small set of options (<12) and if you need to customize how the options are rendered and selected, use EuiSuperSelect.
EuiSuperSelect gives you control over option display through the dropdownDisplay prop, allowing for multi-line option text or descriptions.
We recommend reaching for EuiSuperSelect only if custom option display is absolutely necessary, as EuiSelect is natively more accessible.
Large sets of options (>12) should use a selection component that supports searching/filtering options. This requires the use of one of the more advanced multiselect components with singleSelection set.
Multiple selections
When you have a small set of options (<7), and all options are equally important in terms of visibility, use EuiCheckboxGroup.
In general, we recommend using EuiSelectable for most multi-selection purposes. It renders a searchable, vertically-oriented list with icons next to the option representing selection state.
EuiSelectable can optionally be rendered with a search box for filtering through many options, or as just a list for fewer options.
EuiSelectable is by far our most accessible and flexible selection component (although customization may require extra development). It can be used within popovers and flyouts, or as a standalone list.
If you need the ability to exclude options, EuiSelectable is the only component that offers that functionality.
In general, for multi-select scenarios, we recommend using EuiComboBox over EuiSelectable primarily when selections should be immediately visible and unselected options should not. Selections are rendered as a horizontally-oriented list of pills.
EuiComboBox allows users to search through a list of dropdown options. This functionality is core to the component and cannot be removed, unlike EuiSelectable.
If you need the ability to dynamically add custom user-generated input outside of the available options, EuiComboBox is the only component that offers that functionality.
Feature comparison chart
Below is a handy reference for comparing the selection components.
Feature[EuiSelect](./basic-select.mdx)[EuiSuperSelect](./super-select.mdx)[EuiSelectable](./selectable.mdx)[EuiComboBox](./combo-box.mdx)Select a single option✅✅✅✅Select multiple options❌❌✅✅Accepts custom values from user input❌❌❌✅Option exclusion❌❌✅❌Customizable option display❌✅✅✅Customizable loading/error messages❌❌✅❌Searchable
❌ ❌✅✅`disabled` state
✅
✅
✅
✅ `readOnly` state❌✅❌❌Built in utility function for clearing user input❌❌
✅
✅ Supports virtualization❌❌✅✅
Packages Website Docs Components Layout Flex Index (`packages/website/docs/components/layout/flex/index.mdx`)
Flex
mdx-code-block
import { createDemo } from '@elastic/eui-docusaurus-theme/components';
import FlexPreviewWrapper from './flex_preview_wrapper';
import flexPreviewWrapperSource from '!raw-loader!./flex_preview_wrapper';
import DemoNote from './_flex_preview_note.mdx';
Use cases
EuiFlexGroup is useful for single row layouts and for quick alignment of items.
EuiFlexGrid should be used for repeated wrapping rows of same-width items.
EuiFlexItem should be used as a direct child of both groups or grids. It can also, depending on the layout, be omitted if its automatic flex or grow behavior is not desired.
EuiFlexGroup and EuiFlexGrid can nest within themselves indefinitely. For example, here we turn off the growth on a EuiFlexGroup, then nest a grid inside of it.
:::warning Flex items are also a flexbox
To support nested stretching of items, EuiFlexItem also has display: flex on it. If your children are not behaving correctly, consider using one of these flex item workarounds.
:::
```tsx
import React from 'react';
import { EuiFlexGrid, EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui';
export default () => ( <>
Flex Group
Nested Group One Nested Group Two
Flex Grid
Nested Grid One Nested Grid Two Nested Grid Three Nested Grid Four
</> );
text
</FlexDemo>
### Overriding output component type
The `component` prop can be passed to either the parent **EuiFlexGroup** or **EuiFlexGrid**, or the child **EuiFlexItem**, to change the rendered component type from the default `div`. It can be any valid React component type like a tag name string such as `div`or `span` or a React component.
This allows you to use semantic tags such as `<form>` with flex layouts, or obey rules around nesting block elements within inline elements.
<FlexDemo>
```tsx
import React from 'react';
import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
export default () => (
<EuiFlexGroup component="p">
<EuiFlexItem component="span">
These items are nested within a paragraph tag
</EuiFlexItem>
<EuiFlexItem component="span">
So they use inline span tags to avoid HTML validation issues
</EuiFlexItem>
</EuiFlexGroup>
);
Packages Website Docs Components Layout Page Components Index (`packages/website/docs/components/layout/page_components/index.mdx`)
import fakeSidebar from '!url-loader!./fake_sidebar.svg';
import fakeParagraph from '!url-loader!./fake_paragraph.svg';
import { PageComponentsPreviewWrapper, FixedHeightPreviewWrapper } from './_preview_wrapper';
import fakeSidebarSource from '!raw-loader!./fake_sidebar.svg'; import fakeParagraphSource from '!raw-loader!./fake_paragraph.svg';
Page layouts are modular and fit together in a precise manner, though certain parts can also be added or removed as needed. EUI provides both the individual page components and an over-arching template for easily creating some pre-defined layouts.
Components
:::note The following examples showcase the individual components only.
If you're looking for full page layout examples, we recommend using the EuiPageTemplate and use this page to modify the props of each part of the template.
:::
Page, body, and sidebar
EuiPage is simply a flex wrapper that will automatically grow to fill the height of a flex container. You can also control the flex direction and the maximum width usingrestrictWidth which centers the page when it reaches the provided value (or 1200px if set to true).
EuiPageSidebar doesn't contain many configurations itself, but it does dictate how the rest of the page contents should be displayed. Typically you'll want to wrap all your page contents in EuiPageBody and set panelled={true} when you have a side bar.
/* Detailed source-code truncated for AI context efficiency. */
Page sections
EuiPageSection is a stackable component that is essentially an EuiPanel with props for quickly creating common usages. It is meant to be a direct descendent of EuiPageBody You'll need to set grow={false} to any content that you don't want to stretch within the page.
To create dividers between contents, use the bottomBorder prop. The 'extended' version ensures the border touches the sides of the parent. It also supports restrictWidth and alignment to align with common usages.
/* Detailed source-code truncated for AI context efficiency. */
Usage
Page configurations
When piecing all of the different page components together, the state of your application will dictate how best to configure each component. Ideally, your main content should always live within a 'plain' colored body or section.
When using EuiEmptyPrompt to replace the main contents of your page you will want to use a panel color opposite that of the section color. For example:
mdx-code-block
import { ConfigurationsTable } from './configurations_table';
Configuration example
:::tip Reminder: EuiPageTemplate can handle all these configurations for you. :::
/* Detailed source-code truncated for AI context efficiency. */
Props
import docgen from '@elastic/eui-docgen/dist/components/page';
Packages Website Docs Components Layout Spacer Index (`packages/website/docs/components/layout/spacer/index.mdx`)
keywords: [EuiSpacer]
Spacer
The EuiSpacer component is for adding vertical space between items and should be used in place of the `` tag. There are many different heights you can specify via the size prop which align to the EUI vertical grid sizing.
import { SpacerPreviewWrapper } from './spacer_preview_wrapper';
```tsx
import React from 'react';
import { EuiSpacer, EuiCode, EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
</Demo>
## Props
import docgen from '@elastic/eui-docgen/dist/components/spacer';
---
### Packages Website Docs Components Navigation Pagination Index (`packages/website/docs/components/navigation/pagination/index.mdx`)
---
keywords: [EuiPagination]
---
# Pagination
Some EUI components have pagination built-in, like [EuiBasicTable](../../tables/basic.mdx), but for custom built paginated interfaces you can use **EuiPagination** manually.
## Component
**EuiPagination** accepts a total `pageCount` and only shows up to 5 consecutive pages, with shortcuts to the first and/or last page. It also requires the parent component to maintain the current `activePage` and handle the `onPageClick`.
:::accessibility Accessibility recommendation
Provide a descriptive `aria-label` for each pagination set.
:::
```tsx interactive
import React, { useState } from 'react';
import { EuiPagination } from '@elastic/eui';
export default function () {
const [activePage, setActivePage] = useState(0);
const PAGE_COUNT = 22;
return (
<EuiPagination
aria-label="Many pages example"
pageCount={PAGE_COUNT}
activePage={activePage}
onPageClick={(activePage) => setActivePage(activePage)}
/>
);
}
Usage
Few pages
The UI simplifies when we have fewer than the maximum number of visible pages.
Use the compressed prop to minimize the horizontal footprint. This will replace the numbered buttons with static numbers and rely on the first, last, next and previous icon buttons to navigate.
This is also the same display that will occur when responsive is notfalse. You can adjust the responsiveness by supplying an array of named breakpoints to responsive. The default is ['xs', 's'].
If the total number of pages cannot be accurately determined, you can pass 0 as the pageCount. This will remove the button numbers and rely solely on the arrow icon buttons for navigation. Without a total page count, the last page button will pass back -1 for the activePage.
You can use EuiTablePagination to create a combination "Rows per page" and pagination set, commonly used with tables. If you pass 0 in as one of the itemsPerPageOptions, it will create a "Show all" option and hide the pagination.
/* Detailed source-code truncated for AI context efficiency. */
Guidelines
import Guidelines from './_guidelines.mdx';
Props
import paginationDocgen from '@elastic/eui-docgen/dist/components/pagination'; import tableDocgen from '@elastic/eui-docgen/dist/components/table';
Packages Website Docs Components Navigation Steps Index (`packages/website/docs/components/navigation/steps/index.mdx`)
keywords: [EuiSteps, EuiStepsHorizontal]
Steps
EuiSteps presents procedural content in a numbered outline format. It is best used when presenting instructional content that must be conducted in a particular order. It requires a title and children to be present and will automatically increment the step number based on the initial firstStepNumber.
Component
Default
tsx interactive
import React from 'react';
import { EuiCode, EuiSpacer, EuiSteps, EuiText } from '@elastic/eui';
const firstSetOfSteps = [
{
title: 'Step 1',
children: (
<EuiText>
<p>Do this first</p>
</EuiText>
),
},
{
title: 'Step 2',
children: (
<EuiText>
<p>Then this</p>
</EuiText>
),
},
];
const nextSetOfSteps = [
{
title: 'Good step',
children: (
<EuiText>
<p>Do this first</p>
</EuiText>
),
},
{
title: 'Better step',
children: (
<EuiText>
<p>Then this</p>
</EuiText>
),
},
];
export default () => (
<>
<EuiSteps steps={firstSetOfSteps} />
<EuiSpacer size="m" />
<EuiText>
<p>
Set <EuiCode>firstStepNumber</EuiCode> to continue step numbering after
any type of break in the content
</p>
</EuiText>
<EuiSpacer size="m" />
<EuiSteps
firstStepNumber={firstSetOfSteps.length + 1}
steps={nextSetOfSteps}
/>
</>
);
Horizontal Steps
import HorizontalSteps from './_horizontal_steps.mdx';
Usage
Complex steps
If you need to call out a set of substeps that are not lines of code, most likely a <ol/>, wrap the block in a <EuiSubSteps/>.
text
/* Detailed source-code truncated for AI context efficiency. */
Heading elements
To aid with accessibility and hierarchical headings, you can and should pass in a heading element to use for each step title. The example below shows that the logical heading element should be an h2and therefore adds headingElement="h2" to the EuiSteps component.
The style of the title will not be affected.
tsx interactive
import React from 'react';
import { EuiSteps, EuiTitle, EuiSpacer } from '@elastic/eui';
const steps = [
{
title: 'Inspect me',
children: (
<EuiTitle size="xs">
<h3>Did you notice the step title is inside a Heading 2 element?</h3>
</EuiTitle>
),
},
];
export default () => (
<>
<EuiTitle size="l">
<h1>Heading 1</h1>
</EuiTitle>
<EuiSpacer size="xl" />
<EuiSteps steps={steps} headingElement="h2" />
</>
);
Conveying status
Steps can optionally include status prop that will alter the look of the number prefix. The options are incomplete, complete, warning, danger, disabled and loading. This is used mostly as a final step when you need to make some sort of final check.
text
/* Detailed source-code truncated for AI context efficiency. */
Custom title sizes
You can set a different title size using titleSize. If titleSize is set in both EuiSteps and EuiStep, the latter value will override the former. Additionally, the title size xs will automatically generate smaller steps circles.
The title size xxs affects only the size of the accompanying unnumbered step indicator, but not the title itself.
tsx interactive
import React, { useState } from 'react';
import {
EuiButtonGroup,
EuiCode,
EuiSpacer,
EuiSteps,
EuiTitle,
EuiStepInterface,
} from '@elastic/eui';
const firstSetOfSteps: EuiStepInterface[] = [
{
title: 'Step 1',
children: (
<p>
Both step title and circle indicators will adjust in size based on the{' '}
<EuiCode>titleSize</EuiCode> prop
</p>
),
},
{
title: 'Step 2',
children: (
<p>
Both step title and circle indicators will adjust in size based on the{' '}
<EuiCode>titleSize</EuiCode> prop
</p>
),
},
];
const sizeButtons = [
{
id: 'xxs',
label: 'XXSmall',
},
{
id: 'xs',
label: 'XSmall',
},
{
id: 's',
label: 'Small',
},
{
id: 'm',
label: 'Medium',
},
];
type StepSizes = NonNullable<EuiStepInterface['titleSize']>;
export default () => {
const [titleSize, setTitleSize] = useState<StepSizes>('m');
const sizeOnChange = (optionId: StepSizes) => {
setTitleSize(optionId);
};
return (
<>
<EuiTitle size="xxs">
<h3>Step Size</h3>
</EuiTitle>
<EuiSpacer size="s" />
<EuiButtonGroup
legend="Horizontal step size toggle"
options={sizeButtons}
idSelected={titleSize}
onChange={(id) => sizeOnChange(id as StepSizes)}
/>
<EuiSpacer size="m" />
<EuiSteps titleSize={titleSize} steps={firstSetOfSteps} />
</>
);
};
Props
import docgen from '@elastic/eui-docgen/dist/components/steps';
Packages Website Docs Components Templates Page Template Index (`packages/website/docs/components/templates/page-template/index.mdx`)
keywords: [EuiPageTemplate]
Page template
EuiPageTemplate is a namespaced component for creating the different types of page layout patterns described in these docs. It is somewhat opinionated, but still has the ability to customize most of the inner components directly on their instance.
Simple page with header and sections
All templates should start with a wrapping EuiPageTemplate to control some shared settings like paddingSize, bottomBorder, restrictWidth, and panelled. Then each direct child will be evaluated for if it is one of the other namespaced components. If so, it will place it in the appropriate spot and apply the appropriate props based on the full configuration of child elements. These namespaced components include:
With the exception of .Sidebar and .BottomBar, the stacking order of the page contents is determined by the order they are passed in. You can also override the outer props by simply applying them directly to the child element.
:::tip If you have a fixed position headers, the template will automatically account for the padding offset based on the total number of page headers. If you do not want the template to calculate this, you can pass 0 in as the manual offset. :::
mdx-code-block
import contentSvg from '!url-loader!./content_paragraph.svg';
import sideNavSvg from '!url-loader!./content_side_nav.svg';
import centerSvg from '!url-loader!./content_center.svg';
import contentSvgSource from '!raw-loader!./content_paragraph.svg';
import sideNavSvgSource from '!raw-loader!./content_side_nav.svg';
import centerSvgSource from '!raw-loader!./content_center.svg';
If your application requires the use of side navigation or other sidebar content, you can pass and EuiPageTemplate.Sidebar component containing your sidebar content. The template will automatically adjust the layout when this content is provided.
This component will set its content to stick to the top of the browser window and scroll independently of the rest of the layout. If you have any fixed EuiHeaders, these will be accounted for as well. You can turn this behavior off with sticky={false}.
Typically when a sidebar is included and restrictedWidth is defined, we recommend keeping the borderBottom={true} though you can also expand particular sections with borderBottom="extended".
:::warning Sidebars must be direct children declared in the same component. In order for the template configurations to properly account for the existence of a sidebar, it needs to clone the element which can only be performed on direct children. :::
Adding an EuiBottomBar can be tricky to use and account for any sidebars. EuiPageTemplate handles this nicely by supplying a EuiPageTemplate.BottomBar component for passing the contents of your bottom bar that extends EuiBottomBar.
It uses the sticky position so that it sticks to the bottom of and remains within the bounds of page body. This way it will never overlap the sidebar, no matter the screen size. It also means not needing to accommodate for the height of the bar in the body element.
It will also keep its content constrained to the restrictedWidth value so the contents are always horizontally aligned.
:::warning For proper alignment in case of short content, at least one EuiPageTemplate.Section must have grow={true}. :::
/* Detailed source-code truncated for AI context efficiency. */
Empty pages or content
When the content is in an empty/pre-setup state, we recommend then using an EuiEmptyPrompt to direct users on next steps. Using EuiPageTemplate.EmptyPrompt will automatically center the prompt vertically and horizontally.
The prompt's panel color depends on the other configurations but can be manually passed in via the color prop.
import docgen from '@elastic/eui-docgen/dist/components/page_template';
Packages Website Docs Components Templates Sitewide Search Index (`packages/website/docs/components/templates/sitewide-search/index.mdx`)
keywords: [EuiSelectableTemplateSitewide]
Sitewide search
EuiSelectableTemplateSitewide is an opinionated wrapper around EuiSelectable to provide a reusable template across the Elastic products that will share the same global search capabilities. It creates the search input that triggers a popover containing the list of options.
Basic setup
Search input
The search ability of EuiSelectable is still hooked up to the options provided. It will highlight the portion of the label that matches the search string.
:::note
The demo showcases the possibility to allow a keyboard shortcut (command + K) to trigger the search input focus, but the template does not come with this ability.
:::
Popover
The popover itself allows you to display a popoverTitle node, popoverFooter node, or pass any of the popoverProps to the EuiPopover component.
Selection
The options themselves are simply rendered as list items with no interactive behavior like buttons or links. You must handle the interaction when the component passes back the altered array of options with the selected option having checked: 'on'.
Popover toggle and responsiveness
The default display is to render the search input inline which triggers a popover with the results. Or you can decide to trigger the whole selectable component from a single button. By passing your own button to popoverButton, the component will use this to trigger the popover, putting the search inside.
This is a great way to handle reducing the size of the component for smaller screens. The component offers a helper prop called popoverButtonBreakpoints which will only render the popoverButton if the window size matches named breakpoint(s).
text
/* Detailed source-code truncated for AI context efficiency. */
ColorModes
EuiSelectableTemplateSitewide by default has the colorMode of the nearest EuiThemeProvider applied. If you want to override the this manually, you can pass a colorModes prop to the component. This allows you to apply different color modes to the search and popover components.
The options are the most opinionated portion of the template. Their display is determined by the props passed in and extends the normal EuiSelectable.option type. All parts are optional with the exception of the label (A).
import { SitewideOption } from './sitewide_option';
label (required): The name of the item itself. By default, the search box will only use this to match the search term against, but you can supply a separate searchableLabel that combines the label and meta data to search on.
icon: Only display the solution or application's logo when the option links to the application itself (Dashboard app) and not lower-level items such as individual dashboards. Size and color are predetermined but can be overridden.
avatar: Represents the Kibana Space that the item is located in, if multiple spaces are present. Type and size are predetermined but can be overridden.
meta: This bottom line should only contain items if the option is a lower-level item (individual dashboard). The display of which defaults to simple text, but if you pass one of the predetermined types, they will be styled according to the design pattern.
:::note The demo shows how you can temporarily replace the icon for a subset of options to display a short list of recently viewed options. :::
Props
import docgen from '@elastic/eui-docgen/dist/components/selectable';
Packages Eui Src Components Date Picker React Datepicker Docs Index (`packages/eui/src/components/date_picker/react-datepicker/docs/index.md`)
Packages Website Docs Components Data Grid Advanced Custom Body Rendering Index (`packages/website/docs/components/data-grid/advanced/custom-body-rendering/index.mdx`)
sidebar_position: 2
Custom body rendering
For extremely advanced use cases, the renderCustomGridBody prop may be used to take complete control over rendering the grid body. This may be useful for scenarios where the default virtualized rendering is not desired, or where custom row layouts (e.g., the conditional row details cell below) are required.
:::warning This prop is meant to be an escape hatch, and should only be used if you know exactly what you are doing. Once a custom renderer is used, you are in charge of ensuring the grid has all the correct semantic and aria labels required by the data grid spec, and that keyboard focus and navigation still work in an accessible manner. :::
import Demo from './demo';
Props
import docgen from '@elastic/eui-docgen/dist/components/datagrid/data_grid_types.docgen.json';
Packages Website Docs Getting Started Theming Tokens Borders Index (`packages/website/docs/getting-started/theming/tokens/borders/index.mdx`)
sidebar_position: 1
Borders
mdx-code-block
import { Example } from '@site/src/components';
The border tokens contain both individual border property values and full shorthand border properties.
Types
mdx-code-block
import { BorderPreview } from './border_preview';
import { BordersTable } from './borders_table';
### `euiTheme.border[type]` token
text
These common border types string together the base properties of color, width and style to form common full `border` properties.
EUI has one main base color it uses for almost all borders (or calculated borders).
Only form specific borders use the euiTheme.colors.borderBasePlain color token instead.
mdx-code-block
import { ColorPreview } from './color_preview';
### `euiTheme.border.color` token
text
You can apply this color directly to any `border-color` property.
- Default is `colors.lightShade`
- In high contrast mode, this value becomes `colors.fullShade` and cannot be overridden.
Use the semantic tokens border.radius.inline (4px), control (8px), panel (12px), and frame (16px). Legacy small / medium still resolve to 4px until migrated.
:::
Packages Website Docs Getting Started Theming Tokens Breakpoints Index (`packages/website/docs/getting-started/theming/tokens/breakpoints/index.mdx`)
sidebar_position: 2
Breakpoints
mdx-code-block
import {
EuiIcon,
EuiText,
EuiThemeProvider,
useCurrentEuiBreakpoint,
useEuiBreakpoint,
useEuiTheme,
useIsWithinBreakpoints,
useIsWithinMaxBreakpoint,
useIsWithinMinBreakpoint,
} from '@elastic/eui';
import { Example, ExampleDescription } from '@site/src/components';
import { UseEuiBreakpointPreview } from './use_eui_breakpoint_preview';
import { UseEuiMinMaxMaxBreakpointPreview } from './use_eui_min_max_breakpoint_preview';
import { BreakpointsTable } from './breakpoints_table.tsx';
import { CurrentCustomBreakpointPreview } from './current_custom_breakpoint_preview';
For most of your usages we recommend using the responsive utilitiesinstead of consuming the theme values directly.
Default values
If you want to align your custom responsive styles with EUI's breakpoints, or when using components that accept our named breakpoints, you'll want to utilize the following default values and provided hooks.
### `useCurrentEuiBreakpoint()` hook
text
This hook returns the named breakpoint size of the current browser window width, responsively updating whenever the window is resized.
The hook automatically inherits breakpoint sizes from the current EUI theme and any theme overrides.
This hook returns true or false if the current browser window width is within the passed breakpoint `sizes`. The `isResponsive` parameter allows it to easily be turned on/off from within your component.
You can also use `useIsWithinMaxBreakpoint(size)` and `useIsWithinMinBreakpoint(size)`. The min/max hooks return true or false if the current browser window width is above or below the passed breakpoint `size` respectively.
These hooks automatically inherits breakpoint sizes from the current EUI theme and any theme overrides.
Theme breakpoints can be overridden or added via `EuiProvider`'s or `EuiThemeProvider`'s `modify` prop.
Excluding a default breakpoint key in your `breakpoint` override will use the EUI default value for that size as a fallback.
</Example.Description>
<Example.Preview>
<CurrentCustomBreakpointPreview />
</Example.Preview>
<Example.Snippet>
```tsx
<EuiThemeProvider
modify={{
breakpoint: {
xxs: 0,
xs: 250,
s: 500,
m: 1000,
l: 1500,
xl: 2000,
xxl: 2500,
},
}}
>
<App />
</EuiThemeProvider>
```
</Example.Snippet>
Packages Website Docs Getting Started Theming Tokens Colors Index (`packages/website/docs/getting-started/theming/tokens/colors/index.mdx`)
sidebar_position: 3
Colors
import { EuiSpacer, EuiCodeBlock } from '@elastic/eui';
Elastic UI builds with a color palette that is based on predefined 14-step scales for a core set of three colors (blue / teal / pink) as well as a green / yellow / red qualitative set and combined with a 28-step grayscale. Colors are defined to work well when combined for their semantic purpose.
When switching between light and dark color modes, the theme keys do not change, only their values do.
import ForcedColorsWarning from './_forced_colors_warning.mdx';
Brand colors
Elastic has two main brand colors. The other three are used for statefulness like indicating between successful and dangerous actions.
import { Example } from '@site/src/components'; import { BrandColorPreview } from './brand_color_preview'; import { BrandColorsTable } from './brand_colors_table';
### `euiTheme.colors[brand]` token
text
Most usages of the colors can be implemented simply by pulling and applying the values.
Semantic border colors. These should be used according to their semantic purpose.
The default border color (used as border.color) borderBaseSubdued. Use the base border colors for most cases. Use borderBasePlain for a slightly stronger border (e.g. for forms).
import { BorderColorPreview } from './border_color_preview'; import { BorderColorsTable } from './border_colors_table';
A six-color grayscale palette. Variation beyond these colors is minimal and always done through computations against this set.
import { ShadeColorPreview } from './shade_color_preview'; import { ShadeColorsTable } from './shade_colors_table';
### `euiTheme.colors[colorShade]` token
text
Since the EUI colors usually evaluate to a hex value, the easiest way to perform color operations like transparency, shading, or tinting is by using the EUI provided methods of `transparentize()`, `shade()`, and `tint()` respectively.
For all available color functions see the [Colors Utilities page](../../utilities/colors.mdx).
Data visualization colors are semantically tied to data visualizations and are not meant for use in other contexts. Do not use these colors in other contexts. :::
import { DataVisColorsTable, DataVisBackgroundColor } from './data_vis_colors_table'; import { DataVisColorsPreview } from './data_vis_colors_preview'
### `euiTheme.colors.vis[token]` token
text
The following colors are color-blind safe and should be used in categorically seried visualizations and graphics.
They are meant to be contrasted against the value of `colors.backgroundBasePlain` for the current theme.
See the [Data visualization text colors](#dataVisText) section for a note about text colors.
See the [Color Contrast](#contrast) section for a note about color contrast.
import { SeverityColorsTable } from './severity_colors_table'; import { SeverityColorsPreview } from './severity_colors_preview'
### `euiTheme.colors.severity[token]` token
text
Severity indicates an increasing level of risk and Health describes the status of an element that would
typically be associated with positive or negative values.
More guidance is available in the [**Health and Color Pattern**](../../../../patterns/severity/).
See the [Color Contrast](#contrast) section for a note about color contrast.
Data visualization text colors are semantically tied to data visualizations colors and are not meant for use in other contexts. :::
import { DataVisTextColorsTable } from './data_vis_colors_table'; import { DataVisTextColorsPreview } from './data_vis_colors_preview'
### `euiTheme.colors.vis[token]` token
text
The following colors are special text colors that are directly related to the data visualization colors. In dark mode these colors
equal the values of the regular data visualization colors and only differ in light mode to provide higher color contrasts. Their only purpose is to be used as possible
text fallbacks for data visualization colors.
Our Data visualization colors and Health and Severity colors prioritize visual clarity and aesthetic appeal over strict contrast compliance in light mode. This means some colors may not meet WCAG contrast requirements against light backgrounds, as lighter and more saturated tones help differentiate data and create a more visually engaging experience.
However, in dark mode, all colors meet the minimum 3:1 contrast ratio, ensuring accessibility compliance.
import { EuiTable, EuiTableHeader, EuiTableHeaderCell, EuiTableRow, EuiTableRowCell } from '@elastic/eui';
Color ModeMeets 3:1 contrast ratios?DarkYesLightNoLight (high contrast)YesDark (high contrast)Yes
Packages Website Docs Getting Started Theming Tokens Component Index (`packages/website/docs/getting-started/theming/tokens/component/index.mdx`)
Components
Besides the global semantic tokens, EUI also provides a set of component-scoped tokens for customizing the appearance of specific EUI components in more detail. These tokens are available via the euiTheme.components object and allow you to override or extend the default styles for specific EUI components. See the available component tokens here.
:::warning Token stability Not all component tokens are equally stable yet and might be changed or removed in the future. Use with caution. :::
import { ButtonsTable } from './buttons_table'; import { FormsTable } from './forms_table';
Buttons
import { Example } from '@site/src/components'; import { ButtonsPreview } from './buttons_preview';
Packages Website Docs Getting Started Theming Tokens Typography Index (`packages/website/docs/getting-started/theming/tokens/typography/index.mdx`)
Typography
For most of your usages we recommend using EuiText or EuiTitle components directly instead of these theme tokens. Or head to the text utilities page for helper classes and functions.
Packages Website Docs Getting Started Theming Utilities Shadows Index (`packages/website/docs/getting-started/theming/utilities/shadows/index.mdx`)
sidebar_position: 10
Shadows
EUI provides 5 shadow sizes (xs, s, m, l, xl) that can be applied in two directions: down (default) and up.
The provided EUI shadow utilities use the underlying theme shadow tokens and automatically add required adjustments for dark mode and high contrast mode that ensure expected visibility.
:::warning Never use the underlying shadow tokens separately Always use the shadow utilities to ensure proper styling. :::
These shadow utilities handle the following additional style adjustments:
adds a light, 4-sided border style in Dark mode to increase elevated item visibility
replaces the box-shadow in High Contrast mode with border to ensure visibility in high contrast themes
import { Example } from '@site/src/components'; import { EuiSpacer } from '@elastic/eui';
By default the shadow utilities add a light, 4-sided border style in Dark mode. In some cases where different shadowed elements are being composed together, this might result in duplicate neighboring borders. To adjust this, use the border option on the euiShadow utility to adjust or remove the border output.
:::info The additional border is only applied and visible in dark mode. :::
/* Detailed source-code truncated for AI context efficiency. */
Scroll behavior
:::warning Do not combine shadow utils and scroll behavior styles on the same element :::
Combining EUI shadow utils like euiShadow() and scroll behavior (e.g. via overflow: scroll or overflow: auto) on the same element will cause the floating border in DARK mode to be scrolled. If you want to combine both, separate the styles onto different elements by placing the shadow util on the outer wrapper and the scroll behavior on the inner one.
/* Detailed source-code truncated for AI context efficiency. */
Hover states
Each shadowed surface, when interactive, follows the same hover effect rule: it moves up one level.
xs hovered → s
s hovered → m
m hovered → l
l hovered → xl
xl hovered → hover.xl
You don't need to remember this rule though. You can instead use the available euiShadowHover utility which applies the expected hover shadow based on the base size.
EUI doesn't prescribe the use of any particular routing library, and we also don't want to incur the maintenance burden of supporting router-specific components. For these reasons, EUI doesn't publish any tools for working with react-router (or any other routing lib). However, integrating EUI with react-router on the consumer's side is fairly straightforward.
How react-router works
Links in react-router accept a to prop and convert this to both href and onClick props under the hood. The onClick is used to push a new history location, and the href allows you to open the link in a new tab. Any mechanism for integrating EUI with react-router needs to bridge this to prop with EUI components' href and onClick props.
There are many techniques for integrating EUI with react-router (see below for some techniques we don't recommend), but we think these two are the strongest:
1) Conversion function (recommended)
You can use a conversion function to convert a to value to href and onClick values, which you can then pass to any EUI button or link component. Many EUI components are designed to accept both props if they accept one.
This technique is recommended because of its flexibility. As a consumer, you have the option to use either the href or onClick values, or both. It's also terser than the second option.
Alternatively, you can create a component which will consume or encapsulate the getRouterLinkProps logic, and use that in conjunction with a render prop.
To enable these techniques, you'll need to make the router instance available outside of React's context. One method for doing this is to assign it to a globally-available singleton within your app's root component.
jsx
import { registerRouter } from './routing';
// App is your app's root component.
class App extends Component {
// NOTE: As an alternative to consuming context directly, you could use the `withRouter` HOC
// (https://github.com/ReactTraining/react-router/blob/v3/docs/API.md#withroutercomponent-options)
static contextTypes = {
router: PropTypes.shape({
createHref: PropTypes.func.isRequired,
push: PropTypes.func.isRequired,
}).isRequired,
}
constructor(...args) {
super(...args);
// Share the router with the app without requiring React or context.
registerRouter(this.context.router);
}
}
// <App> *must* be a child of <Router> because <App> depends on the context provided by <Router>
ReactDOM.render(
<Router history={history}>
<Route path="/" component={App} />,
</Router>,
appRoot
)
Hot module reloading
Note that if using HMR, you'll need to re-register the router after a hot reload.
js
componentDidUpdate() {
// You may want to add some conditions here to cull this logic from a production build,
// e.g. `if (process.env.NODE_ENV !== 'production' && module.hot)`
this.registerRouter();
}
`routing.js` service
You can create a routing.js service to surface the registerRouter method as well as your conversion function (called getRouterLinkProps here).
js
// routing.js
const isModifiedEvent = event => !!(event.metaKey || event.altKey || event.ctrlKey || event.shiftKey);
const isLeftClickEvent = event => event.button === 0;
const resolveToLocation = (to, router) => typeof to === 'function' ? to(router.location) : to;
let router;
export const registerRouter = reactRouter => {
router = reactRouter;
};
/**
* The logic for generating href and onClick handlers from the `to` prop is largely borrowed from
* https://github.com/ReactTraining/react-router/blob/v3/modules/Link.js.
*/
export const getRouterLinkProps = to => {
const location = resolveToLocation(to, router);
const href = router.createHref(location);
const onClick = event => {
if (event.defaultPrevented) {
return;
}
// If target prop is set (e.g. to "_blank"), let browser handle link.
if (event.target.getAttribute('target')) {
return;
}
if (isModifiedEvent(event) || !isLeftClickEvent(event)) {
return;
}
// Prevent regular link behavior, which causes a browser refresh.
event.preventDefault();
router.push(location);
};
return {href, onClick}
};
react-router 4.x
Share `router` globally
Setup is slightly different with react-router 4.x. To enable these techniques, you'll need to make the router instance available outside of React's context. One method for doing this is to assign it to a globally-available singleton within your app's root component.
jsx
import { registerRouter } from './routing';
// App is your app's root component.
class App extends Component {
static contextTypes = {
router: PropTypes.shape({
history: PropTypes.shape({
push: PropTypes.func.isRequired,
createHref: PropTypes.func.isRequired
}).isRequired
}).isRequired
}
constructor(...args) {
super(...args);
// Share the router with the app without requiring React or context.
registerRouter(this.context.router);
}
}
// <App> *must* be a child of <Router> because <App> depends on the context provided by <Router>
ReactDOM.render(
<Router>
<App />,
</Router>,
appRoot
)
You can create a routing.js service to surface the registerRouter method as well as your conversion function (called getRouterLinkProps here).
jsx
// routing.js
import { createLocation } from 'history';
const isModifiedEvent = event => !!(event.metaKey || event.altKey || event.ctrlKey || event.shiftKey);
const isLeftClickEvent = event => event.button === 0;
let router;
export const registerRouter = reactRouter => {
router = reactRouter;
};
/**
* The logic for generating href and onClick handlers from the `to` prop is largely borrowed from
* https://github.com/ReactTraining/react-router/blob/master/packages/react-router-dom/modules/Link.js.
*/
export const getRouterLinkProps = to => {
const location = typeof to === "string"
? createLocation(to, null, null, router.history.location)
: to;
const href = router.history.createHref(location);
const onClick = event => {
if (event.defaultPrevented) {
return;
}
// If target prop is set (e.g. to "_blank"), let browser handle link.
if (event.target.getAttribute('target')) {
return;
}
if (isModifiedEvent(event) || !isLeftClickEvent(event)) {
return;
}
// Prevent regular link behavior, which causes a browser refresh.
event.preventDefault();
router.history.push(location);
};
return {href, onClick}
};
react-router 5.x
react-router 5.0
The React Context handling has changed in in 5.0 and we can't rely on it anymore. A solution is to create an extractRouter HOC that will intercept the router and send it to your custom handler.
import { extractRouter } from './hoc';
import { registerRouter } from './routing';
// App is your app's root component.
class App extends Component {
...
}
const AppMount = extractRouter(registerRouter)(App);
// <App> *must* be a child of <Router> because <App> depends on the context provided by <Router>
ReactDOM.render(
<Router>
<AppMount />,
</Router>,
appRoot
)
react-router 5.1
In react-router 5.1, we can fully capitalize in the React Hooks utility, in this case, useHistory. Using this, we do not need other HOC wrapper files and global router variable. We just need to create the file below, and then use it anywhere by importing EuiCustomLink. There is an example repository for this: https://github.com/Imballinst/elastic-react-router-hooks.
jsx
// File name: "EuiCustomLink.js".
import React from 'react';
import { EuiLink } from '@elastic/eui';
import { useHistory } from 'react-router';
const isModifiedEvent = (event) =>
!!(event.metaKey || event.altKey || event.ctrlKey || event.shiftKey);
const isLeftClickEvent = (event) => event.button === 0;
const isTargetBlank = (event) => {
const target = event.target.getAttribute('target');
return target && target !== '_self';
};
export default function EuiCustomLink({ to, ...rest }) {
// This is the key!
const history = useHistory();
function onClick(event) {
if (event.defaultPrevented) {
return;
}
// Let the browser handle links that open new tabs/windows
if (isModifiedEvent(event) || !isLeftClickEvent(event) || isTargetBlank(event)) {
return;
}
// Prevent regular link behavior, which causes a browser refresh.
event.preventDefault();
// Push the route to the history.
history.push(to);
}
// Generate the correct link href (with basename accounted for)
const href = history.createHref({ pathname: to });
const props = { ...rest, href, onClick };
return <EuiLink {...props} />;
}
jsx
// App is your app's root component.
class App extends Component {
...
}
// <App> *must* be a child of <Router> because <App> depends on the context provided by <Router>
ReactDOM.render(
<Router>
<App />,
</Router>,
appRoot
)
react-router 6.x / 7.x
Cloud UI uses react-router-dom 6.x. AutoOps uses 7.x. The same useNavigate / useHref pattern works for both.
But it's important to be aware of two caveats to this approach:
EUI's components contain a lot of useful behavior. For example, EuiLink will render either a button or an anchor tag depending on the presence of onClick and href props. It will also create a secure rel attribute if you add target="_blank". Consumers lose out on these features if they use EUI's CSS instead of its React components.
This creates a brittle dependency upon the euiLink CSS class. If we were to rename this class in EUI, this would constitute a breaking change and we'd make a note of it in the change log. But if a consumer doesn't notice this note then the only way they could detect that something in their UI has changed (and possibly broken) would be through manual testing.
Contributing To Eui Running Eui Locally (`wiki/contributing-to-eui/running-eui-locally.md`)
Setting up and running EUI locally
The below instructions run EUI's documentation site locally, with any changes made in packages/eui/src/ reflected.
[!IMPORTANT] This repository is a monorepo, meaning that it contains sources of multiple packages within a single git repository. You can find all packages in the packages directory. When dealing with EUI (@elastic/eui package), most of the work will be done from within the packages/eui directory.
Setup
Node
We depend upon the version of node defined in .nvmrc.
You will probably want to install a node version manager. nvm is recommended.
To install and use the correct node version with nvm:
shell
nvm install
Dependencies
EUI uses Yarn v4 for dependency management. We use npm for release purposes only.
Unlike Yarn Classic and other package managers, Yarn v4 isn't supposed to be installed globally. Instead, it utilizes corepack - a modern way to manage package managers built straight into Node.js.
First, enable corepack:
shell
corepack enable
yarn is now configured on your machine, and you can use it just like any other CLI command. Corepack will always use the right Yarn version, even when switching branches.
With yarn set up and ready to go, use it to install EUI dependencies:
shell
yarn
Remember to run yarn whenever you're switching branches to ensure all dependencies are installed and have correct versions.
Running
Storybook
This is the recommended way to work with components locally. Storybook runs at http://localhost:6006/.
[!IMPORTANT] The commands below must be run from the packages/eui directory. Running yarn start from the repo root will exit with an error message pointing you there.
If running for the first time, build dependent workspaces:
shell
yarn build:workspaces
You can run Storybook with the following command:
shell
yarn start
Website
If running for the first time, build dependent workspaces:
Theme specific code lives in standalone workspace packages in the EUI repo. /eui-theme-common holds shared code for base theme building and types, while /eui-theme-borealis holds theme specific token and style definitions. When making changes to either of these packages you need to manually build the packages for the updates to be available in your local development environment.
For building both packages at the same time, run the following in /packages/eui:
shell
yarn build:workspaces
For building the packages separately, run the following in the respective package directory:
shell
yarn build
VSCode setup
Eslint
If you're developing in VSCode and you are using the ESLint extension for VSCode you will notice that ESLint is showing errors about not finding config files since the monorepo setup changes. To fix this add eslint.workingDirectories to your VSCode settings and pass all working directories as relative paths.
Contributing To Eui Developing Creating Component Files (`wiki/contributing-to-eui/developing/creating-component-files.md`)
Component file & folder architecture
Creating components manually
Create the component directory
Create a directory for your component in packages/eui/src/components/. All folder and file names should be snake_cased.
Logical groupings of directories
If a component has any of the following:
Subcomponents (<EuiToolBar> and <EuiToolBarSearch>),
Tightly-coupled components (<EuiButton> and <EuiButtonGroup>)
Or you just want to group some related components together (<EuiTextInput>, <EuiTextArea>, and <EuiCheckBox>)
then they belong in the same logical grouping (i.e., directory). A component directory can have as many subcomponents and corresponding *.test.tsx or *.style.ts as needed. If a component directory starts to feel overwhelmingly cluttered due to multiple internal sub-components (e.g. EuiDataGrid), make liberal use of subdirectories to help organize your component by concern or by feature.
Create the React component
Create the React component(s) as TypeScript .tsx file(s) in your new component directory.
Export these components from an index.ts file.
Re-export these components from packages/eui/src/components/index.ts.
This makes your React component available for import into your project.
Create component style files
Create a {component name}.styles.ts file inside the same component directory.
First, you'll be prompted for what kind of component to create:
Choice
Description
Stateless function
A stateless functional React component
Component class
A class-based React component
Next, you'll reply to a series of prompts.
"What's the name of this component?"
Yeoman will ask you what to name the file. It expects you to provide the name in snake case. Yeoman will automatically add file extensions and a "eui" prefix so you should leave those out.
"Where do you want to create this component's files?"
The path to the directory where the files should live. Defaults to the last directory you specified for this prompt, or to EUI's components directory if you haven't specified one.
If you want Yeoman to automatically generate a directory to organize the files, that directory will be created inside of the location you specify (see next prompt).
"Does it need its own directory?"
This will automatically generate a directory with the same name as the file, but without a "eui" prefix. This defaults to YES.
Done!
Yeoman will generate the files you need in your project's folder system.
For your convenience, it will also output some snippets you can tweak to import and re-export the generated JS and style files.
Contributing To Eui Developing Creating Icons (`wiki/contributing-to-eui/developing/creating-icons.md`)
Creating icons
EUI provides an ever-growing set of icons, but our set can be incomplete. If you find you need an icon that does not exist, create a new icon request issue. A designer from the EUI team will respond to discuss your needs.
If you are willing and able to design the icon yourself, this document describes the design guidelines, cleaning up the SVG, and getting it added to EUI. While designers on the EUI team are available to assist, we greatly appreciate your contributions and pull requests.
If you read through these guidelines or begin designing your icon and realize you're in too deep, then create an issue in this repo and request assistance. An EUI team member will reply and discuss options.
Note on 3rd-party / custom SVGs
The EuiIcon component accepts external references to custom SVG files, so you can maintain the icon in your consuming application.
This practice should also be used for any 3rd-party logos. For a number of reasons, the EUI team has moved away from maintaining a set of 3rd party logos. Please use the custom SVG option going forward.
Content
While we're pretty much open to all requests, we ask that you first try to use an existing icon as this helps us avoid having multiple versions of the same glyph. Likewise, if there is a universally known icon that represents an action, we recommend leveraging those existing patterns (e.g. a scissors for cut).
Finding and sharing reference icons is a great way to get moving if you're uncertain of the general shape. Post these examples to your issue and we'll provide feedback.
Lastly, we reserve the right to reject any icons that do not fit the EUI style or may be deemed inappropriate.
Design guidelines
Linear icon scaling
Each icon should be comprised of a single vector glyph that can be scaled linearly, up or down.
This approach greatly reduces maintenance and implementation complexity.
Note that minor icon blurriness/anti-aliasing can occur when icons are sized to dimensions other than those divisible by the source viewbox dimensions.
16x16px viewbox
All icons should be crafted on a 16x16px viewbox.
As this is the default size for all icons in EUI, this will help ensure all icons look their best by default.
Strokes only (with some exceptions)
Nearly all icons should be lined/stroked, using a 1px stroke width (on a 16x16px canvas).
In special cases, a filled variant of an icon can be created for concepts that convey a toggle action and need to represent this toggle action in an interface. For example, a star icon may have a filled variant to indicate toggling of “favorite” status.
In a very limited subset of icons, a filled variant of an icon can be created for concepts that convey status (check, error, warning).
In special cases, a circled variant of an icon can be created for use in empty/tertiary buttons when it comprises of a simple shape that may inadvertently appear awkward or overly diminished otherwise. Examples include simple icons such as a plus or minus.
Snap to pixel grid (when possible)
When possible, align/snap your icon to the 16x16px viewbox grid. Doing so will ensure that your icon appears as sharp as possible, with little or no blurring/anti-aliasing.
It will not always be possible to snap to the grid in all situations. For example, if you have an element of your icon that is 1px and needs to be centered on the 16x16 viewbox, it is impossible to align to the grid in this case. In such situations, it is fine to snap to half-pixel increments in order to center that odd-width element on an even-width canvas.
Do not offset your glyph in the viewbox only for the purpose of aligning it to the pixel grid. Doing so may inadvertantly make the icon appear misaligned agaisnt other elements in your design (due to the offset) or create other inconsistencies when compared with similar icons.
Square endpoints only
All icon strokes should apply square endpoints. Rounded endpoints should never be used.
Round outer corners, square inner corners
All stroke corners should be round on the outside and square on the inside.
Outer rounded corner radius should be 1px. How this is put into effect in Figma depends on how your stroke is aligned (ex. inside, center, outside).
If stroke is inside, set corner radius to 1px.
If stroke is center, set corner radius to 0.5px.
If stroke is outside, set stroke join to be round.
The only exception to this rule is when the application of rounded outer corners comes at the detriment to the icon being conveyed. In such situations, having both inner and outer corners square is acceptable.
2D perspective only (with some exceptions)
Icons with three dimensional perspectives should generally be avoided, unless the concept being conveyed is significantly more identifiable with that perspective (ex. layers).
Showing depth or a stacking order of objects (i.e. when something is in front of something else) should be achieved with the use of negative (subtraction) space.
1px safe zone
At the default 16x16px viewbox, a safe zone of 1px around the viewbox should be kept free of icon elements if possible. Doing so will avoid any inadvertent clipping if/when the icon is scaled.
Consistent optical sizing and alignment
Icons in close proximity to each other should have similar optical sizing, so as to not appear out-of-place. While this is subjective, designers should still evaluate newly created icons against a variety of existing icons to ensure icons are not disproportionately sized.
Utilize the provided keyline shapes for general sizing guidance.
Icons should appear optically in the vertical and horizontal center of their viewbox. In most cases, this means simply center aligning the icon in the frame. In other scenarios (ex. triagular shapes), the designer may need to offset the icon slightly in order to appear in the optical center.
Descriptive icon naming
When possible, try to name icons by the object being conveyed.
If the object is too abstract or obscure to use for the icon name, naming the icon by the action it is attempting to convey is an acceptable alternative.
Grouped/related icons should have a common prefix string to ensure they are in close proximity when in alphabetical order.
Common variants should use a consistent suffix string across (ex. Circle, Fill, Slash). Note that if the icon is not a variant, there is no need to apply such a suffix (ex. see the new info icon)
Icon component and EUI prop names should be in camel case (ex. myIcon).
SVG files for inclusion in the EUI repo should be in snake case (ex. my_icon.svg).
Apply synonyms for the icon in the Figma component description to ensure designers are able to easily find icons, even if not searching for the exact name.
While we use Figma to maintain our internal design library, you can use any design tool to produce the SVG file.
Add the icon to the EUI repo
Once you've designed your new icon, the last step is adding it to the EUI repo.
Clean the SVG
EUI provides SVG icon formats only. After exporting your icon as an SVG from your design tool, open it in a code or text editor and remove any unnecessary elements and attributes, such as:
<def>...</def>
...
id: <value>
fill: <value>
Note: Figma users can use the SVG Export plugin to optimize SVG sizes and code.
Prepare the pull request
Create a new feature branch against this repo and make the following changes:
1. Add your glyph to the EuiIcon component
Add your SVG file to the /packages/eui/src/components/icon/svgs folder.
Add a reference to the SVG import in /packages/eui/src/components/icon/icon_map.ts in alphabetical order.
2. Add metadata to the icon loader(optional)
If you want the icon to participate in docs search or be grouped into a specific set, attach metadata next to the import.
Switch the docs to dark mode and verify that the icon is visible (all paths should be filled with the reverse color)
Run yarn run test-unit icon -u to create/update the jest snapshots
If everything looks good, then commit your changes, push up your branch, and open a PR! :raised_hands:
Opening a PR will notify the EUI team that your work is ready for review. Please include a screenshot in the description and reference the issue that your PR fixes.
Ship it
Once your PR is approved, you will be able to merge it and give yourself a well-deserved pat on the back. Finally, stay tuned for the next release of EUI at which point your icon will become available to the masses and appear on the EUI docs site.
:trophy: Welcome to the Official EUI Icon Design Club :beers:
** Run yarn workspace @elastic/eui-website start to view the EUI docs site locally.
Contributing To Eui Developing Developing In Kibana (`wiki/contributing-to-eui/developing/developing-in-kibana.md`)
Developing EUI locally in Kibana
This guide explains how to develop EUI library locally while seeing changes reflected in a local Kibana instance.
[!TIP] This is the workflow for rapid local development — a custom watcher rebuilds EUI packages and syncs them into your local Kibana's node_modules as you edit. To validate your EUI changes against Kibana CI (in a Kibana draft PR), use yarn build-pack instead — see Testing EUI features in Kibana.
Prerequisites
You have this repository forked and cloned:
bash
git clone https://github.com/<your-username>/eui.git
cd eui
nvm use
yarn
git clone https://github.com/<your-username>/kibana.git
cd kibana
nvm use
corepack enable
corepack prepare pnpm@$(node -pe "require('./package.json').engines.pnpm.replace(/^\D*/, '')") --activate
pnpm kbn bootstrap
(Optional) EUI and Kibana should be sibling directories for simplest DX:
text
Projects/
├── eui/
└── kibana/
Usage
Start Kibana from EUI
In the EUI repository root, run the Kibana processes in one terminal:
bash
yarn watch:kibana
This starts the EUI watcher, then starts Elasticsearch and Kibana after the initial EUI build. Kibana's Rspack MultiCompiler watches the synced EUI output and rebuilds the affected shared and Kibana bundles. Press Ctrl+C to stop all managed processes.
If Elasticsearch already uses port 9200, or Kibana uses port 5601, the corresponding process is not started.
The integration relies on a chain of file watchers and build triggers to propagate changes from EUI source code to the browser running Kibana.
Data flow
The script watches src directories using chokidar.
With --kibana, @elastic/eui runs one complete build:optimize-es, then Babel-compiles only changed source files. Changed JSON and SVG files are copied directly.
@elastic/eui syncs only changed optimize/es files into Kibana's node_modules and touches package.json once per batch. Theme packages still rebuild and copy their full files list.
Kibana's Rspack MultiCompiler detects the change and rebuilds shared dependencies before rebuilding Kibana.
When Rspack has rebuilt, the browser reloads.
Architecture diagram
mermaid
flowchart TD
%% EUI
subgraph EUI [EUI repository]
Change([Source change]) --> |Watch| Script(watch-eui.js)
Script --> |Build| Artifacts(Artifacts)
end
Artifacts --> |Sync| NodeModules[node_modules]
%% Kibana
subgraph Kibana [Kibana repository]
NodeModules --> |Detect| Rspack(Rspack MultiCompiler)
end
Rspack --> |Build shared deps and Kibana| Browser((Update in the browser))
Troubleshooting
Change not showing up?
Check the terminal output of the EUI watcher. If the node_modules propagation succeeded, check the Kibana terminal for Rspack rebuild output. Ensure you started the workflow with yarn watch:kibana.
Slow feedback loop?
For @elastic/eui, the watcher skips lib/, es/, types and the rest of the package build. After the initial optimize/es build, only changed files are compiled. Theme packages still run a full build. Most of the remaining feedback time is Kibana's Rspack rebuild.
Contributing To Eui Developing Props (`wiki/contributing-to-eui/developing/props.md`)
Component props
Props are the main APIs through which consumers/developers interact with our components. As such, it's important that our props remain as consistent as possible between all components, as something like changing the name of a prop or removing a prop would be considered a breaking change.
Naming props
Enums
String literals should be used wherever possible and prioritized over booleans. This allows for the most extensibility when it comes to adding more features/options in the future. For example, use layout: 'horizontal' | 'vertical' instead of isHorizontal: boolean.
Size enums
We use abbreviations to refer to sizes, e.g. xxl, xl, l, m, s, xs, and xxs.
Typing enums
Enums should typically be declared as an as const array.
tsx
// We first define the enum values as an array
export const COLORS = ['primary', 'success', 'warning', 'danger'] as const;
// We can then refer to the enum array for prop types.
export type EuiComponentProps = {
color: COLORS[number];
isDisabled?: boolean;
/* ... */
};
// We can continue to use the enum array for iteration
const isNamedColor = (color: string) => COLORS.includes(color);
Booleans
Generally, boolean props should have an is prefix, e.g. isPlaceholder or isReadOnly. The exception to this is when the prop matches an existing HTML attribute such as disabled; to avoid confusion the prop name should align with the HTML specification. Mirroring the attributes this way makes the most sense when the component is a thin wrapper around an existing HTML element, e.g. EuiButton -> and EuiRadio -> .
Event handlers
All event handlers should take the form onEvent and accurately describe when it will be called. e.g. onClick indicates the handler is called when the component is clicked, but if there is more granularity the handler should reflect that with onItemClick, onRowClick, etc.
Common and required props
Try to leverage the children prop wherever possible. This will create a simpler more uniform API throughout our components.
We also require some props to be supported by all components, as reflected in our tests; for example, className. These are easily added via the CommonProps mentioned above.
Pass-through props
To give the consumer as much flexibility as possible we use the destructuring assignment to pull expected props out of the received props and pass ...rest to one of the elements in the render() method. This element is typically the root element, though in rare cases another element makes more sense.
The main benefit behind this practice is that the consumer can specify any of the DOM attributes supported by React, including custom ones with the data- prefix.
In Typescript, it makes sense to then extend the props of that element when declaring the component's type. EUI also provides a shortlist of commonly used props like className, aria-label, and data-test-subj that you should extend as well.
jsx
import { HTMLAttributes, FunctionComponent } from 'react';
import { CommonProps } from '../common';
export type EuiMegaMenuProps = HTMLAttributes<HTMLDivElement> &
CommonProps & {
color: EuiMegaMenuColor;
isDisabled?: boolean;
/* ... */
};
export const EuiMegaMenu: FunctionComponent<EuiMegaMenuProps> = ({
children,
className,
color = 'primary',
size,
className,
isDisabled = false,
...rest
}) => {
// Anything else specified by the consumer will be applied to the div as a DOM attribute.
return (
<div {...rest}>
{/* ... */}
</div>
);
}
The component's TypeScript definition needs to properly include the target DOM element's props. A Foo component that passes ...rest through to a button element would have the HTMLButtonElement props interface.
ts
// passes extra props to a button
interface FooProps extends ButtonHTMLAttributes<HTMLButtonElement> {
title: string
}
Props documentation from Typescript
We use react-docgen-typescript combined with some custom props filters to automatically generate our Props tab/table from our Typescript component types.
:warning: react-docgen-typescript currently has a bug that does not correctly generate props for all components if a file has multiple components that set a displayName. To avoid this bug and broken props tables, keep your component files atomic / limited to 1 major component per file.
Contributing To Eui Developing Refs (`wiki/contributing-to-eui/developing/refs.md`)
Component refs
Components should always accept a ref prop which should typically pass back the primary underlying native DOM element to the ref callback.
However, more complex components (e.g., EuiDataGrid and EuiMarkdownEditor) may instead pass back a ref of imperative APIs as a way to control said component.
forwardRef
React's forwardRef should be used to provide access to the component's outermost element. We impose two additional requirements when using forwardRef:
Use forwardRef instead of React.forwardRef, otherwise react-docgen-typescript does not understand it and the component's props table will error in our documentation
The resulting component must have a displayName, which is useful when the component is included in a snapshot or when inspected in devtools. There is an eslint rule which checks for this.
Simple forward/pass-through
tsx
import React, { forwardRef } from 'react';
interface MyComponentProps {...}
export const MyComponent = forwardRef<
HTMLDivElement, // type of element or component the ref will be passed to
MyComponentProps // what properties apart from `ref` the component accepts
>(
(
{ destructure, props, here, ...rest },
ref
) => {
return (
<div ref={ref} {...rest}>
...
</div>
);
}
);
MyComponent.displayName = 'MyComponent';
Combining with additional refs
Sometimes an element needs to have 2+ refs passed to it, for example a component interacts with the same element the forwarded ref needs to be given to. For this EUI provides a useCombinedRefs hook:
tsx
import React, { forwardRef, createRef } from 'react';
import { useCombinedRefs } from '../../services';
interface MyComponentProps {...}
export const MyComponent = forwardRef<
HTMLDivElement, // type of element or component the ref will be passed to
MyComponentProps // what properties apart from `ref` the component accepts
>(
(
{ destructure, props, here, ...rest },
ref
) => {
const localRef = useRef<HTMLDivElement>(null);
const combinedRefs = useCombinedRefs([ref, localRef]);
return (
<div ref={combinedRefs} {...rest}>
...
</div>
);
}
);
MyComponent.displayName = 'MyComponent';
Providing custom or additional data
Rarely, a component's ref needs to be something other than a DOM element, or provide additional information. In these cases, React's useImperativeHandle can be used to provide a custom object as the ref's value. For example, EuiMarkdownEditor's ref includes both its textarea element and the replaceNode method to interact with the abstract syntax tree. https://github.com/elastic/eui/blob/v31.10.0/src/components/markdown_editor/markdown_editor.tsx#L331
tsx
import React, { useImperativeHandle } from 'react';
export const EuiMarkdownEditor = forwardRef<
EuiMarkdownEditorRef,
EuiMarkdownEditorProps
>(
(props, ref) => {
...
// combines the textarea element & `replaceNode` into a single object, which is then passed back to the forwarded `ref`
useImperativeHandle(
ref,
() => ({ textarea: textareaRef.current, replaceNode }),
[replaceNode]
);
...
}
);
Contributing To Eui Developing Writing Styles With Emotion (`wiki/contributing-to-eui/developing/writing-styles-with-emotion.md`)
Writing styles with Emotion
EUI uses Emotion when writing CSS-in-JS styles. A general knowledge of writing CSS is enough in most cases, but there are some JavaScript-related differences that can result in unintended output. Similarly, there are feaures that don't exist in CSS of which we like to take advantage.
File patterns
ts
/* {component name}.styles.ts */
import { css } from '@emotion/react';
import { UseEuiTheme } from '../../services';
// Always start the object with the first key being the name of the component
export const euiComponentNameStyles = ({ euiTheme }: UseEuiTheme) => {
return {
euiComponentName: css`
color: ${euiTheme.colors.textPrimary};
`,
};
};
🎉 ProTip: VS Code snippet To make generating component boilerplate just a little bit easier, you can add the following block to a global or local snippet file in VS Code. Once saved, you'll be able to generate the boilerplate by typing euisctab. Learn how to add snippets in VS Code:
json
"euiStyledComponent": {
"prefix": "euisc",
"body": [
"/*",
"* 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 { css } from '@emotion/react';",
"import {",
" euiFontSize,",
" logicalCSS,",
"} from '../../global_styling';",
"import { UseEuiTheme } from '../../services';",
"",
"export const ${1:componentName}Styles = ({ euiTheme }: UseEuiTheme) => {",
" return {",
" ${1:componentName}: css`",
" ${2:property}: tomato;",
" `",
" };",
"};"
],
"description": "EUI styled component"
}
If a prop/value pair maps 1:1 to the CSS property: value, pass the value straight through. We encounter this scenario when it is apparent that a given css property is core to configuring a component, and it doesn't make sense to use an abstraction.
tsx
position?: CSSProperties['position'];
const cssStyles = [
{ position }
];
Component props that enable styles
Building an array of styles
Use an array inside of the css prop for optimal style composition and class name generation. This is relevant even if only a single style object is passed.
When building styles based on an array of possible prop values, you'll want to establish the array of values first in the component file then use that array to create your prop values and your styles map.
Note that the helper function returns a string literal instead of a css method from @emotion/react. This reduces the serialization work at runtime and makes the helper more flexible (e.g., could be used with a style attribute). Also note that the css method from @emotion/react can be called as a normal function instead of as a template literal.
Conditional styles
Styles can be added conditionally based on environment variables, such as the active theme, using nested string template literals.
Although possible in some contexts, it is not recommended to "shortcut" logic using the && operator. Use ternary statements to avoid undefined statments from entering the compiled code.
When writing styles for prop enums (e.g. sizing enums: s, m, l, etc.), some props may have duplicated styles between two values. If the duplicated styles are just a line or two, repeating the CSS is not particularly problematic.
However, if the repeated CSS starts to get lengthy or unintuitive, consider using a JS getter to return duplicate styles, for example:
ts
export const euiComponentNameStyles = ({ euiTheme }: UseEuiTheme) => ({
// Sizes
s: css`
/* lengthy or complex styles */
`,
get m() {
// Same as `s`
return this.s;
},
l: css`
/* different styles */
`,
});
For a production example of this scenario, see EuiStep's styles.
Child selectors
Most components also contain child elements that have their own styles. If you have just a few child elements, consider having them in the same function.
If you have multiple child elements, consider grouping them in different theme functions to keep things tidy. Keep them within a single styles.ts file if they exist in the same .tsx file.
Please keep in mind that while Emotion will automatically merge css props for top level components, it will not do so for any child elements. Take for example the following component:
tsx
// This example is incorrect!
export const EuiComponentName: FunctionComponent<EuiComponentNameProps> = ({ iconProps, ...rest }) => {
const euiTheme = useEuiTheme();
const styles = euiComponentNameStyles(euiTheme);
const cssStyles = [styles.euiComponentName];
const cssIconStyles = [styles.euiComponentName__icon];
return (
// This will merge Emotion CSS as expected
<div css={cssStyles} {...rest}>
<EuiIcon
// This will not!
css={cssIconStyles}
{...iconProps}
/>
</div>
)
}
If a consumer passes <EuiComponentName css={{ color: 'red' }}>, Emotion will automatically correctly combine EUI's component styles and the passed custom styles.
However, if a consumer passes <EuiComponentName iconProps={{ css: { color: red } }}>, Emotion will not handle merging in the child css props and will simply override/ignore whichever css prop came first in the prop order.
To ensure consumers do not either accidentally wipe our EUI's default styling, or are unable to pass in child css props, always check that you're manually merging in any childProps.css like so:
tsx
// This example will correctly merge child CSS
export const EuiComponentName: FunctionComponent<EuiComponentNameProps> = ({ iconProps, ...rest }) => {
const euiTheme = useEuiTheme();
const styles = euiComponentNameStyles(euiTheme);
const cssStyles = [styles.euiComponentName];
// Include `childProps?.css` in the css array
const cssIconStyles = [styles.euiComponentName__icon, iconProps?.css];
return (
<div css={cssStyles} {...rest}>
<EuiIcon
// Ensure that your merged `css` array comes after the props spread
{...iconProps}
css={cssIconStyles}
/>
</div>
)
}
You can confirm that this behavior correctly merges Emotion CSS by using the shouldRenderCustomStyles test utility. Example usage:
For the most part, nested selectors should not be necessary. If a child element requires styling based on the parent's variant, pass the same variant type to the child element.
If for other reasons, it is absolutely necessary to target a child from within another selector, you should use the class attribute selector to match a part of the class string you expect to find.
When creating components that rely on a specific colorMode from <EuiThemeProvider>, use this pattern to create a wrapper that will pass the entire component <EuiThemeProvider> details.
_EuiComponentName is an internal component that contains the desired functionality and styles.
EuiComponentName is the exportable component that wraps _EuiComponentName inside of <EuiThemeProvider>.
If the mixin is simple and does not reference euiTheme, you do not need to create a hook version of it.
In general, prefer returning CSS strings in your mixin.
However, you should consider creating a 2nd util that returns a style object instead of a CSS string if the following scenarios apply to your mixin usage:
If you anticipate your mixin being used in the style prop instead of css (since React will want an object and camelCased CSS properties)
If you want your mixin to be partially composable, so if you think developers will want to obtain a single line/property from your mixin instead of the entire thing (e.g. euiFontSize.lineHeight)
JS vs. CSS component variables
In general, most component-specific style variables can remain JS-only (e.g., euiStepVariables, euiFormVariables). These JS variable examples are generally used internally by EUI, and are not public top-level exports.
There are some scenarios, however, where certain component style variables are important enough to be made globally available via a CSS variable.
An example of this is EuiHeader: Fixed header height(s) and the page offset they cause need to be accounted for by multiple other EUI components (e.g. EuiFlyout, EuiPageTemplate), and potentially by custom consumer layouts. Using a global CSS variable allows EuiHeader to dynamically track the number of fixed headers and calculate total height in a single place. Other components can reuse that CSS variable without extra JS logic needed (#7144).
EUI components can set CSS variables in two places: globally, or at the nearest EuiThemeProvider wrapper level:
tsx
import React, { useEffect } from 'react';
import { useEuiTheme, useEuiThemeCSSVariables } from '../../services';
const EuiComponent = ({ ...props }) => {
const { euiTheme } = useEuiTheme();
const {
setGlobalCSSVariables,
setNearestThemeCSSvariables,
} = useEuiThemeCSSVariables();
useEffect(() => {
// Sets the CSS variable at `:root`
setGlobalCSSVariables({ '--euiSomeGlobalVariable': euiTheme.color.success });
// Sets the CSS variable on the nearest parent theme provider wrapper
// If the nearest provider is EuiProvider, the variable is set globally on `:root` in any case
setNearestThemeCSSVariables({ '--euiSomeThemeVariable': euiTheme.size.m });
}, []);
return <></>;
}
While a global CSS variable makes sense for EuiHeader, for most components, nearest theme variables would likely make more sense. For example, EuiForm should respect any custom theme modifications and pass its modified form variables to any children, but not siblings or parent forms that do not have modifications.
tsx
// Normal form
<EuiForm>
{/* ... Form controls that inherit global form variables */}
</EuiForm>
// Form with a custom size scale
<EuiThemeProvider modify={{ base: 10 }}>
<EuiForm>
{/* ... Form controls that inherit from the nearest theme variables */}
</EuiForm>
</EuiThemeProvider>
When naming your mixins & utilities, consider the following statements:
Always prefix publicly-exported functions with eui unless it's purely a generic helper utility with no specific EUI consideration
When creating both a returned string version and object version, append the function name with CSS for strings and Style for objects. Example: euiMixinCSS() vs euiMixinStyle().
API pattern
For consistency, use the following pattern for style mixins that accept required and/or optional arguments:
If the mixin does not accept required or optional properties, the argument can be removed.
Writing unit tests for output styles
If using complex utilities or calculations that leaves you unsure as to the output of your styles, it may be worth writing Jest snapshot tests to capture the final output. See EuiText's stylesnapshots or EuiTitle for an example of this.
If writing straightforward or static CSS, unit tests should be unnecessary.
FAQ
Can the `css` prop be forwarded to a nested element?
Emotion converts the css prop to a computed className value, merging it into any existing className prop on an element. We do not parse or handle these in any special way, so whichever element the className prop is applied to receives the styles created by Emotion. See https://codesandbox.io/s/emotion-css-and-classname-ohmqe7 for a playground demonstration.
Sometimes apps want or need to provide styles (or other props) to multiple elements in a component, and in these cases we add a prop to the component that captures the extra information, spreading it onto the element. We can continue with this approach, allowing the css prop to be added for flexible styling.
Which element in a custom component gets the `css` styling?
Same as the above answer, whichever element is given the generated className is the styles' target.
How should `createElement` usages be converted?
Emotion provides its own createElement function; existing uses of import {createElement} from 'react' can be converted to import {createElement} from '@emotion/react'
Why is stylelint / `yarn lint-css-in-js` giving me an `Unknown word (CssSyntaxError)` error?
You must convert all // comments to standard CSS /* */ comments instead.
Should I use Emotion's `css={theme => {}}` API?
No. The Emotion theme context that we include by default in EuiThemeProvider is intended for consumer usage and convenience, particularly with the goal of making adoption by Kibana devs easier.
It is not intended for internal EUI usage, primarily because it can be too easily overridden by consumers who want to use their own custom Emotion theme vars and set their own ``. If this happens, and we're relying on Emotion's theme context, all of EUI's styles will break.
When you're styling EUI components internally, you should use only EUI's theme context/useEuiTheme(), and not on Emotion's theme context (i.e., do not use the css={theme => {}} API).
Contributing To Eui Documenting Changelogs (`wiki/contributing-to-eui/documenting/changelogs.md`)
Changelogs
All upcoming changelogs should go into the changelogs/upcoming/ directories.
Our release script automatically handles collating these files on new EUI versions and appends a PR link.
When to write a changelog
Any updates to the packages/eui/src/ folder should include a changelog
dependency upgrades in package.json should contain a changelog
When to skip a changelog
Changes that do not impact consumers or end-users may use the skip-changelog label on PRs to bypass CI checks. Examples of these types of changes:
Documentation-only changes (e.g. changes to wiki/ or packages/website/)
Infra/dev-only changes (e.g. changes to .github/, .buildkite/ or scripts/)
Test-only changes (e.g. changes to *.test.tsx, *.spec.tsx, or *.stories.tsx files)
dev-dependency upgrades in package.json
How to write a changelog
Create a new file with the ID/number of the pull request.
In packages/eui, you can use yarn yo-changelog ##### (passing the PR ID/number) to automatically generate a changelog boilerplate file.
Create ** wrapped subheadings for bug fixes, deprecations, or breaking changes. New features/enhancements should go at the top, and do not need a subheading.
Add a - list item for each significant change in the PR. Larger PRs may have multiple changes, whereas smaller PRs may have only one.
Write your entry in the past tense, starting with a verb (e.g. Added... , Fixed...).
Add a summary of what has changed, making sure it's informative to consumers who might be unaware of implementation details.
Avoid documenting internal implementation changes that don't affect the public interface.
Make sure the entry is as concise as can be.
Contributing To Eui Documenting Writing Documentation (`wiki/contributing-to-eui/documenting/writing-documentation.md`)
Code for the Elastic UI documentation site can be found in the packages/website directory. The site is built with Docusaurus.
Files and folders
Documentation is written in Markdown.
Files are stored in the docs folder and document files use MDX, thus the .mdx file extension. This format allows writing React code alongside Markdown. It's also possible to import React code from .tsx files, which is useful for more complex examples.
[!TIP] For formatting content always prefer using Markdown over React
The folder structure matches the navigation and content structure, similar to common static site builders like Jekyll. For instance, for editing the Components / Containers / Accordion page, the content will be found in the docs/components/containers/accordion.mdx file.
Files and folder names must use kebab-case, unlike the EUI convention to use snake_case. This makes it easier to keep URLs consistent without having to override Docusaurus defaults in the front matter.
Custom components available
There are a few React components that can be used in .mdx files without being imported. They are part of the MDX scope. Some of them, documented below, are:
The front matter is a section at the top of a Markdown file that contains metadata about the document. It's enclosed in triple dashes (---) and written in YAML format.
A few important aspects of the document can be configured here:
To create a collapsible item in the sidebar, just add a folder in the file system containing one or more .mdx files. In Docusaurus these are called category items, and they can be configured by adding a _category_.yml file to the folder. By default the category label will be the folder name, this can be changed with the label field in the _category_.yml file. Other useful fields are position and collapsed.
It's possible to make a collapsible item be a document as well, though this is not recommended. Set the link field to have type: doc and id: <DOCUMENT_ID>, with the id from the document it should point to. If the id is not explicitly set in the document file's front matter, it's the relative path starting from the docs folder, without the file extension, e.g. docs/components/containers/accordion.
[!WARNING] Nesting items in the sidebar should be avoided when possible, deeply nested items can make the menu harder to use
Documenting components
All component pages should follow the following architecture:
Intro
Component: a bare-bones example of the component in its default form
Usage (optional): examples of different states and use-cases
Guidelines (optional): e.g. dos and don'ts
Props
Partials
A document can be split into different files when content is too long, for easier editing.
Markdown files prefixed with an underscore will be ignored when generating the sidebar navigation. These hidden files can be imported and rendered in the main document.
For example, the document for the Modal page is containers/modal/index.mdx:
As previously mentioned, using Markdown is highly encouraged. It makes it easier for anybody to contribute to the documentation.
During the build step of the website, elements with a counterpart in EUI will be accordingly replaced, e.g. a link with EuiLink, a inline code block with EuiCode, etc.
Headings
Headings in Markdown are created with a hash symbol in a new line. The number of hashes will determine the heading level, # for 1, ## for 2, etc.
[!IMPORTANT] Do not use heading levels for styling, use them to properly structure the document hierarchically
Links
Markdown is the recommended syntax for creating links. Docusaurus will take care of rendering it correctly.
md
[This is a link](https://example.com)
To link between documents, use relative file paths, including the file extension, e.g. [EuiPanel](../containers/panel/index.mdx)
The syntax for creating admonitions is wrapping a paragraph in three colons, e.g. :::. The type of admonition and title can be defined after the first three colons. This will render EuiCallOut.
mdx
:::info This is the title
Here's some content
:::
There are 6 types of admonitions supported, with icon and color respectively.
note
tip
info
accessibility
warning
danger
Code blocks
A code block or code snippet can be created by wrapping text in backticks, e.g. `code`.
For a simple one-liner, single backticks are enough. To create a multiline snippet or code block, use three backticks. For syntax highlighting, the language name can be added right after the first three backticks.
text
```html
<span>Hello World</span>
```
Code blocks can be used to generate live demos, where a React component that is exported in the snippet will be rendered on the page. To do that, append the word "interactive" after the language name, usually tsx.
The <Demo /> component includes built-in CodeSandbox integration. It displays a button that, when clicked, opens a new CodeSandbox instance containing your snippet.
To ensure CodeSandbox functions properly, verify that all references are resolvable:
all necessary React and EUI components are imported directly,
all relative imports in the snippet are direct siblings (e.g. import { Component } from './component';),
all local files used (such as components or SVGs) are passed to the extraFiles prop as stringified content:
scope and extraFiles serve different purposes. scope is used to inject variables into the snippet execution context while extraFiles is used to pass additional files to the CodeSandbox instance.
Full-screen example
The convention for creating full-screen examples, where the entire browser screen is needed to demo a certain component combination or pattern, is to create a Storybook story and link to it from the docs.
There's a StorybookLink component that makes this easy and consistent, by handling the URL appropriately depending on the environment.
It's expected for the link to be preceded by a heading and a short description.
The story's id can be found in the URL. It's the part after /index.html?path=/story/.
text
## A heading for the example
Here it's explained what the examples represents.
```mdx-code-block
import { StorybookLink } from '@site/src/components';
```
<StorybookLink id="<STORY_ID>" />
Tables
There are two supported ways to create tables in MDX - using Markdown tables syntax or using the EUI table components like EuiTable or EuiBasicTable:
Markdown tables syntax should only be used for simple, text-only tables. The Markdown representation of a table must be easily readable.
EuiTable components should be used for longer and more complex tables
EuiBasicTable should be used for multi-column tables, when the data comes from JS objects or isn't easily readable when directly defined using MDX syntax.
Just like with partials, the code for the table can be separated in its own file and imported into the MDX document where it'll be displayed. For example:
## A heading for the table
```mdx-code-block
import { NiceTable } from './nice_table';
```
<NiceTable />
Dos and Don'ts
For dos and don'ts sections there's a Guideline component with the appropriate styles built-in. To display one next to the other, a EuiFlexGroup can be used:
tsx
<EuiFlexGroup gutterSize="m">
<Guideline type="do" text="Lorem ipsum dolor sit amet consectetur adipiscing elit.">
Yep content
</Guideline>
<Guideline type="dont" text="Quisque faucibus ex sapien vitae pellentesque sem placerat.">
Nope content
</Guideline>
</EuiFlexGroup>
`Guideline` props
children - The content inside, can be other components
type - do, dont or default
text - The actual guideline text
panelPadding (optional) - To adjust the padding of the panel
panelStyle (optional) - To further customize the panel styles
Figma asset
Use FigmaAsset to show a Figma design in docs. By default it renders a static image (SVG preferred). Pass type="embed" to show an interactive Figma iframe instead.
Props are a discriminated union on type: image mode requires src (and forbids embedding-only combinations); embed mode forbids src. title is always required for accessibility.
Image (default) — import a colocated asset as a URL (use !url-loader! for SVGs; bare .svg imports become React components via SVGR). WebP/PNG imports work the same way if you need them later:
mdx
import confirmationModal from '!url-loader!./assets/modal_confirming-an-action.svg';
<FigmaAsset
url="https://www.figma.com/file/Uo8i…DX-0"
src={confirmationModal}
title="Title of the asset"
/>
Embed — interactive iframe from the Figma url:
Important note: Use it only when necessary — only when users need to zoom into UI details. It impacts rendering performance and stability in the sections where applied.
mdx
<FigmaAsset
type="embed"
url="https://www.figma.com/file/Uo8i…DX-0"
title="Title of the embed"
/>
Prop
Description
url
Figma file/node URL (used for embed; kept as source reference for images)
src
Image URL string — SVG preferred; WebP/PNG supported (required for type="image"; not allowed for type="embed")
type
"image" (default) or "embed"
title
Required accessible text (image alt / iframe title)
Props table
The Props table should always go last in a document. There is a PropTable component available without being imported that will take the props information from the @elastic/eui-docgen package in the definition prop.
If more than one table is needed because the component has many exports, place one table after the other. It should look like this:
To customize the table PropTable also takes a headingLevel prop (h1–h6) and a showTitle boolean.
Images
Typically, just use markdown:
mdx
If you need to add CSS to do something like constraining the width, use an img tag:
mdx
import error404 from './images/error-page-404.webp';
Contributing To Eui Testing Automated Accessibility Testing (`wiki/contributing-to-eui/testing/automated-accessibility-testing.md`)
Accessibility testing
The goal is for all EUI components to be run through axe. Please refer to issue #6300 to see components under test.
What is axe?
axe is an "accessibility engine for automated Web UI testing". Automated tests cover ~30% of accessibility requirements but ~60% of accessibility bugs are caught by automated tests. Automated testing can't replace manual testing, but it's an important baseline for all of our components.
How to run the Cypress accessibility tests?
test-cypress-a11y runs the accessibility test suite in a local headless Cypress environment.
test-cypress-dev runs the test suite using your dev server (assumed to be http://localhost:8030) and the Cypress test runner.
How to run accessibility tests against 1 component?
Though it's not setup to be run this way, there are two ways to do it.
The recommended route is to install the axe addon (for Chrome, Firefox, or Edge). Navigate to any page and run the analyzer from your browser's developer tools. This will return the same* results to you while also giving you some convenience utilities like highlighting the exact element that's failing.
* It might not actually be the same in a couple cases (e.g., we've disabled some rules or a recent update that was pushed to the addon but we haven't updated yet) but it will generally be more strict than we are, so you should never see something in CI that you can't see in the addon.
Deconstructing an error message
We have updated how accessibility violations appear in your terminal or CI logs. Each URL with violations will write a table like the one shown here.
index
id
impact
description
nodes
0
'aria-valid-attr-value'
'critical'
'ARIA attributes must conform to valid values'
1
1
'nested-interactive'
'serious'
'Nested interactive elements are not announced by screen readers'
3
All error messages follow this same structure:
The index is a 0-based count of accessibility violations on a page.
The id maps to an axe-core rule description on GitHub. These rules link to more information on the Deque University site.
The impact describes how much a violation will prevent or degrade the user experience.
The description will give a one sentence explanation of the problem.
The nodes will tell you how many violations of this type are on the page.
The test runner will give you a total count of issues at the end of the run.
Any violations should be confirmed using the axe browser plugin in the EUI design system site, or running a server locally. The axe browser plugin is available for Chrome, Firefox, and Edge.
Testing environment
Run the suite locally with yarn test-cypress-a11y. CI already runs it on every pull request (Buildkite job Cypress accessibility (a11y) tests on React 18).
Contributing To Eui Testing Cypress Testing (`wiki/contributing-to-eui/testing/cypress-testing.md`)
Cypress testing
Currently, EUI only uses Cypress component testing as opposed to its full E2E test runner. This is ideal for isolating/focusing on specific components while reducing traditional E2E start times.
Running tests
yarn test-cypress runs component tests headlessly (no window appears)
yarn test-cypress-a11y runs component accessibility tests headlessly (no window appears)
yarn test-cypress-dev launches a chrome window controlled by Cypress, which lists out the discovered tests and allows executing/interacting from that window.
Setting the theme
By default tests are run using the light theme. Dark mode can be enabled by passing the --theme=dark option.
Testing specific version of React
By default, EUI Cypress tests are run using the latest supported version of React (18). You can change that behavior by passing --react-version set to 17 or 18 (peer range).
# Only run a single specific test file, e.g. onBoarding.js
yarn test-cypress --spec '**/{file}.spec.tsx'
# Opt to run Chrome headlessly
yarn test-cypress --headless
# Overriding config settings, e.g. enabling video recording
yarn test-cypress-dev --config video=true
# Overriding env variables
yarn test-cypress-dev --env password=foobar
Writing tests
When to write Cypress tests
Cypress tests should primarily be written for functionality that Jest/JSDom can't realistically replicate, for example:
Focus state
Portals
3rd party dependencies
Native DOM behaviors (e.g., scrolling, label<>input clicks)
How to write Cypress tests
Cypress has its own specific cy. API/commands, of which you will likely be using cy.get(), cy.find(), and either cy.click() or cy.type() to interact with DOM elements. Here's a small example test:
jsx
import { mount } from '@cypress/react';
import TestComponent from './test_component';
describe('TestComponent', () => {
it('takes user input, submits it, and displays the resulting output', () => {
mount(<TestComponent />);
cy.get('[data-test-subj="someInput"]').type('hello world');
cy.get('[data-test-subj="submitButton"]').click();
cy.get('[data-test-subj="someOutput"]').contains('HELLO WORLD');
});
});
Naming your test files
Create Cypress test files with the following name patterns to run in specific situations:
{component name}.spec.tsx will run a full component test as part of every build
{component name}.a11y.tsx will run an accessibility test using Cypress Axe
These test files should be in the same directory as {component_name}.tsx.
Do's and don'ts
DO read through Cypress's best practices recommendations
DO use the data-test-subj attribute to mark parts of a component you want to find later.
DON'T depend upon class names or other implementation details for finding nodes, if possible.
DON'T extend the cy. global namespace - instead prefer to import helper functions directly
Recording failed Cypress tests on CI
EUI now has the ability to record failed Cypress tests as Buildkite CI artifacts. This feature is turned off by default, but can be turned on by changing video: false to video: true in packages/eui/cypress.config.ts. You can verify videos are being recorded by changing a Cypress test to fail on purpose, then running yarn test-cypress locally. Video files will be stored in packages/eui/cypress/videos/.
The EUI team has configured Cypress to only keep videos for failing tests. Videos are automatically compressed to make the artifact upload smaller.
Cypress Axe
EUI components are tested for accessibility on every pull request (Buildkite Cypress a11y job) and locally via yarn test-cypress-a11y. This allows us to test changes to the DOM such as accordions being opened, or modal dialogs being triggered, more comprehensively. We use cypress-axe to access the underlying axe-core API methods and rulesets.
How to write cypress-axe tests
jsx
// Names must follow the `{component name}.a11y.tsx` pattern to be run correctly
// accordion.a11y.tsx
describe('Automated accessibility check', () => {
it('has zero violations when expanded', () => {
cy.mount(
<EuiAccordion {...noArrowProps}>
<EuiPanel color="subdued">
Any content inside of **EuiAccordion** will appear
here. We will include [a link](#) to confirm focus.
</EuiPanel>
</EuiAccordion>
);
cy.get('button.euiAccordion__button').click();
cy.checkAxe();
});
});
Configuring `cy.checkAxe()`
The cy.checkAxe() helper method has four optional parameters:
skipFailures - Set to true to enable report-only mode. Report-only will run the entire suite without causing tests to return early.
context - This could be the document or a selector such as a class name, id, or element. The context default is div#__cy_root.
axeConfig - The axe.run API can be modified to include or exclude elements, individual rules, or entire rulesets.
callback - Define a custom violations callbac if you need to add side effects or change the reporting structure
jsx
// Create a custom ruleset by extending EUI defaults
import { defaultAxeConfig } from '../../cypress/support/a11y/axeCheck';
const customAxeConfig = {
...defaultAxeConfig,
runOnly: {
type: 'tag',
// Add best-practices to existing rulesets
values: [...defaultAxeConfig.runOnly.values, 'best-practices'],
},
};
// Violations will use the custome ruleset and cause tests to fail
cy.checkAxe(false, customAxeConfig);
Cypress Real Events
Cypress default events are simulated. That means that all events like cy.click or cy.type are fired from JavaScript. That's why these events will be untrusted (event.isTrusted will be false) and they can behave a little different from real native events. But for some cases, it can be impossible to use simulated events, for example, to fill a native alert or copy to the clipboard. This plugin solves this problem.
Cypress Real Events uses the Chrome Devtools Protocol to handle behaviors like a real browser. This gives us a better way to test complex events like mouse hover and keyboard focus. By using real events and making assertions against them, we can test keyboard and screen reader accessibility as users change the local state.
How to write Cypress (real event) tests
The Cypress Real Events API works seamlessly with existing cy() methods. If you want to press a button using Cypress Real Events, you could use realPress('Tab') as a replacement for the cy.tab() synthetic method. If you want to press multiple keys (also known as a chord), you should pass an array like ['Shift', 'Tab'] to your helper method.
All Cypress Real Events methods are prefixed with the string "real". Here's a small example test:
jsx
import TestComponent from './test_component';
describe('TestComponent', () => {
it('presses a button using the Enter key', () => {
/* Use the `realMount()` command to set focus in the test window */
cy.realMount(<TestComponent />);
/* Activate a button with a real keypress event */
cy.get('[data-test-subj="submitButton"]').realPress('Enter');
/* Assert the button has focus and the aria-expanded attribute has updated */
cy.focused().invoke('attr', 'aria-expanded').should('equal', 'true');
});
it('presses a button using the Space key', () => {
/* Assert the button also accepts the Spacebar keypress */
cy.realMount(<TestComponent />);
cy.get('[data-test-subj="submitButton"]').realPress('Space');
cy.focused().invoke('attr', 'aria-expanded').should('equal', 'true');
});
});
Use cy.realMount() if your component doesn't receive focus automatically OR
Use cy.mount() for components that receive focus on render
DO be on the lookout for new features!
Debugging tests
For debugging failures locally, use yarn test-cypress-dev, which allows you to run a single specific test suite and runs tests in a browser window, making dev tools available to you so you can pause and inspect DOM as needed.
:warning: As a general rule, we generally recommend taking a break and not clicking around while tests are running. This can eliminate or lower the possibility of hard-to-reproduce/intermittently flaky behavior and timeouts due to user interference.
Artifacts
All failed tests will output a screenshot to a screenshots/ folder (cypress/screenshots). We strongly recommend starting there for debugging failed tests to inspect error messages and UI state at point of failure.
To track what Cypress is doing while running tests, you can pass in --config video=true which will output screencaptures to the cypress/videos/ folder for all tests (both successful and failing). This can potentially provide more context leading up to the failure point, if a static screenshot isn't providing enough information.
ℹ️ We have videos turned off in our config to reduce test runtime, especially on CI, but suggest re-enabling it for any deep debugging.
CI debugging
Failing screenshot artifacts (as well as Cypress logs) are generated by our Jenkins CI.
TODO: Instructions on where to click to see the relevant artifact(s)/screenshots
Contributing To Eui Testing Eui Test Helpers (`wiki/contributing-to-eui/testing/eui-test-helpers.md`)
EUI test helpers
@elastic/eui-test-helpers ships Playwright Component Objects used by downstream end-to-end tests (Kibana Scout). Each helper has validation tests that run its public API against the real component in Storybook. This page covers how those tests run in CI; for the package itself (directory layout, adding a Component Object) see the package CONTRIBUTING.
Running locally
Start Storybook, then run the suite (it attaches to the running dev server):
shell
yarn workspace @elastic/eui build:workspaces # one-time
yarn workspace @elastic/eui start # Storybook on http://localhost:6006
# in another terminal:
yarn workspace @elastic/eui-test-helpers exec playwright install chromium # one-time
yarn workspace @elastic/eui-test-helpers test
See the package CONTRIBUTING for the full local workflow and the HTML report.
On every pull request
eui-pull-request-test runs two helper steps (test-helpers task in pipeline_test.sh):
Validation — builds the EUI workspaces and Storybook, then runs the full Playwright suite once. Storybook is served by Playwright's webServer from the static build.
Flake detection — pipeline_test_helpers_flaky.sh diffs the PR against its merge base and, for any changed component with a correlated helper, runs that helper's specs --repeat-each=25 to surface flakiness — even when the helper itself was not touched. A flaky failure fails the build. It is a no-op when no correlated component changed.
Component ↔ helper correlation
The correlation is by directory path, so keeping the helper's directory in parity with the EUI component's is all that is required. See Flake detection in the package CONTRIBUTING guide for the exact rule.
Contributing To Eui Testing Screen Reader Testing Matrix (`wiki/contributing-to-eui/testing/screen-reader-testing-matrix.md`)
Screen reader testing matrix
This document was created based on analytics compiled in July 2022. The analytics reflect the most common browsers, screen resolutions, and operating systems among Elastic Cloud users. The data was compiled anonymously and will only be used to inform testing priorities.
Testing tiers
Tier 1 (T1): Should be tested for all new components or significant experience updates
Tier 2 (T2): Test as time allows during a regular PR review cycle
Tier 3 (T3): Test as requested by customers or teammates
Most PRs should be tested in Kibana before merging into EUI main. Test a built version of EUI against Kibana and consider staging the integration to ensure the upgrade process is as painless as possible.
Staging Integrations
Use this Staging Workflow to assist the EUI maintainers in integrating your PR into Kibana during an upgrade. Useful when:
PR involves breaking changes
You want to apply a new feature in Kibana to ensure adoption
Existing styles may need to be tweaked
There are test failures, especially integration tests
Stage in Kibana: Create a Draft PR in the Kibana repo. Use this to handle test failures, style tweaks, or API migrations.
Reference in EUI PR: Link the Kibana Draft in your EUI PR description.
Prep commits: Add each staged commit URL under # @next in packages/release-cli/kibana-prep-commits (one URL per line). Nightly and the upgrade PR cherry-pick that list.
Final Upgrade: The upgrader will cherry-pick your staged commits into the final Kibana version bump PR.
Upgrader cherry-picks staged commits into the version bump.
Recommended Commit Structure
Keep staged commits atomic to simplify cherry-picking for the upgrader:
refactor: update [some Kibana code] to use new EUI API
style: adjust styles for [some EUI change]
test: update snapshots for [some EUI change]
test: fix broken integration tests for [some EUI change]
Testing local EUI in local Kibana
yarn link doesn't work between EUI and Kibana - EUI is on Yarn v4 and Kibana is on pnpm, and their package managers are not interoperable. Instead, you have two options depending on your goal:
For local development against a Kibana instance running on your machine, use the yarn watch --kibana watcher.
For CI validation — opening a Kibana draft PR so Kibana's CI runs against your EUI changes — use yarn build-pack (described below) to produce a .tgz that you commit to the Kibana PR.
In EUI:
EUI is a monorepo and to build and test eui locally in Kibana you'll need to ensure that the internal dependency packages are correctly linked as well.
First, update the package.json in eui/packages/eui as follows:
remove @elastic/eui-theme-common from dependencies
add "@elastic/eui-theme-common": "workspace:^" to devDependencies
add "@elastic/eui-theme-common": "{CURRENT_VERSION}" to peerDependencies, e.g. "@elastic/eui-theme-common": "1.0.0"
Then run the following command:
bash
# eui/packages/eui
yarn build-pack
If you made changes to eui-theme-common and/or eui-theme-borealis directories you'll need to build those packages as well:
bash
# eui/packages/eui-theme-common
yarn build-pack
bash
# eui/packages/eui-theme-borealis
yarn build-pack
This will create the required .tgz file(s) with the changes in your eui (and eui-theme-common/ eui-theme-borealis) directory. At this point you can move it anywhere.
[!IMPORTANT] If the .tgz is going to be committed to Kibana, rename it to snake_case (e.g. elastic_eui_xx.x.x.tgz). Kibana's file_casing quick check fails the PR on hyphenated names like the elastic-eui-xx.x.x.tgz that yarn pack produces.
In Kibana:
Point the package.json file in Kibana to that file: "@elastic/eui": "/path/to/elastic_eui_xx.x.x.tgz". Then run the following commands at Kibana's root folder:
bash
pnpm kbn bootstrap --no-validate && pnpm start
The --no-validate flag is required when bootstrapping with a .tgz.
Change the name of the .tgz after subsequent yarn build-pack steps (e.g., elastic_eui_xx.x.x_1.tgz, elastic_eui_xx.x.x_2.tgz). This is required for the package manager to recognize new changes to the package.
Running Kibana with pnpm start ensures it starts in dev mode and doesn't use a previously cached version of EUI.
Deploying local EUI in Kibana
Elastic engineers have the option to deploy a local EUI package in Kibana. To do so, do the following:
Generate and link a local EUI package
Follow the steps above to create a local package of EUI using yarn build-pack
Copy the generated .tgz package file(s) to the Kibana root and rename them to snake_case (see the note above)
Point the package.json file in Kibana to that local file: "@elastic/eui": "file:./elastic_eui_xx.x.x.tgz".
Add "@elastic/eui-theme-common" to the package.json and point it either to the local package you copied, or add the published version that matches the version listed as a dependency in your local @elastic/eui package
bash
# default release version, no local package for @elastic/eui-theme-common
"@elastic/eui-theme-common": "3.0.0"
# with local package added
"@elastic/eui-theme-common": "file:./elastic_eui_theme_common_xx.x.x.tgz"
Commit the changed files (package.json, pnpm-lock.yaml and EUI .tgz package) and push your branch
Create a Kibana (draft) pull request
Kibana CI will run tests on this instance with your custom EUI package
To ensure CI works correctly, you'll need to add @elastic/eui-theme-common to packages/kbn-dependency-ownership/src/rule.ts (example) and src/dev/license_checker/config.ts (example)
Deploy the custom EUI package
After you have created a Kibana PR following the steps above, there are two ways you can deploy your local EUI package in Kibana.
Using labels
Add the labels ci:cloud-deploy and ci:cloud-persist-deployment on your PR and ensure the CI pipelines run by checking the "Click to trigger kibana-deploy-cloud-from-pr for this PR!" checkbox in the comment from elasticmachine
Once the pipelines are finished, kibanamachine will post a message with a link to the credentials on Buildkite, there you'll find a link to the deployment e.g. https://kibana-pr-228477.kb.us-west2.gcp.elastic-cloud.com and a command to get the credentials
Run the vault command to get the credentials in your terminal (if you never used the vault CLI, continue reading)
yarn test-unit -u will update your snapshots. Note: if you are experiencing failed CI builds (Buildkite) related to snapshots, then try clearing the cache first yarn test-unit --clearCache.
yarn test-unit --watch watches for changes and runs the tests as you code.
yarn test-unit --coverage generates a code coverage report showing you how fully-tested the code is, located at reports/jest-coverage.
Naming your test files
Create test files with the name pattern of {component name}.test.tsx in the same directory which contains {component name}.tsx.
Targeting files to test
You can also add any string to the end of the command to run the tests only on test files whose path contains that string. For example, yarn test-unit button will match both accordion/button.test.tsx (filename match) and button/icon.test.tsx (parent directory match), but not card.test.tsx.
yarn test-unit --testMatch=react / --testMatch=non-react will test specifically only .tsx files vs. non-.tsx files. If not specified, both types of tests will run automatically.
yarn test-unit --react-version=18 will run tests against a specific React version that EUI supports (currently 17-18).
Test helpers
The packages/eui/src/test module exports functions and constants to help you write better tests. Use these for new tests:
requiredProps is a list of all props almost all components should support.
shouldRenderCustomStyles automatically asserts that consumer classNames, Emotion CSS, and custom styles are merged correctly with EUI's styles.
The exports within test/rtl (render, screen, and within) provide out-of-the-box data-test-subj querying. render provides automatic EuiProvider wrapping.
Note: Unlike RTL's recommendation to use screen for queries, EUI prefers, for consistency, to destructure test queries from the render() API.
Test helper naming pattern
If the test helper includes enzyme or other libraries included only in devDependencies, use the *.test_helper.[ts, tsx] naming pattern to exclude the component from production builds, or place the helper in a namespaced folder.
Legacy: Enzyme
EUI is migrating away from Enzyme. Do not write new Enzyme tests — use RTL (see Test helpers above). The helpers below exist only to support existing Enzyme tests until the migration completes:
findTestSubject helps you find DOM nodes in mounted components.
takeMountedSnapshot generates a snapshot of a mounted component.
When touching a legacy Enzyme test, prefer porting it to RTL if the change is non-trivial.
Test design
Do's and don'ts
DO use the data-test-subj attribute to mark parts of a component you want to find later.
DON'T depend upon class names or other implementation details for finding nodes, if possible.
DON'T use snapshots, except for an initial it renders test. Prefer using specific assertions instead.
Anatomy of a test
A good test will document:
The default state of the component.
The inputs for each prop, and the associated outputs.
Errors.
Special behavior, e.g. keyboard navigation, async behavior, DOM manipulation under the hood.
text
/* Detailed source-code truncated for AI context efficiency. */
Writing mock component files
A component file can be mocked for snapshot simplification or to mitigate nondeterministic rendering in test environments. See src/components/icon for a example.
Mock component files are currently only used as part of consuming project test environments. To use mock components in EUI's own testing environments, use jest.mock() manually.
Using the mock namespace
Component mocking relies on using the [name].testenv.* namespace for identification. The mocked module will replace the standard import in the test-env build. Both index files and individual component files can mocked.
Mapping all module exports
The rendered output of a mocked component is at the author's discretion, however, all public exports from a module must be preserved in the mock file. Note that this does not apply to exported TypeScript types and interfaces, which will always be derived from the original component file.
Contributing To Eui Testing Visual Regression Testing (`wiki/contributing-to-eui/testing/visual-regression-testing.md`)
Visual regression testing
EUI uses Playwright Test Runner with jest-image-snapshot for component visual regression testing. Tests run against a live Storybook instance and compare screenshots of stories against previously approved reference images.
Visual regression tests run automatically on every pull request against the Storybook build from that pipeline, served locally on the VRT agent (the same files that are deployed as the preview). When differences are found, a diff table is posted as a PR comment and a Buildkite block step appears for human approval before baselines are updated.
[!IMPORTANT] VRT runs in CI which owns the baselines and auto-commits them to your PR. Run it locally only when you really need to verify that many stories render as expected.
Running VRT locally
Make sure you have Docker installed and running. It's used to take screenshots in a Linux environment matching CI.
Locally this builds and serves a static Storybook (like CI) and rebuilds it each run, so no dev server is needed and every run reflects your current stories. To test against a specific URL instead (e.g. a deployed PR preview):
The static build is served from flat files (no HMR) which keeps networkidle settling instantly. The dev server's HMR can otherwise stall it under emulation and make stories time out (see Troubleshooting). The build happens inside the container (Linux, matching CI) and is reused across variants.
shell
# Reuse the existing build for fast iteration (skip the rebuild)
yarn workspace @elastic/eui test-visual-regression -- --no-build
# Use the dev server instead (requires `yarn storybook --no-open` running)
yarn workspace @elastic/eui test-visual-regression -- --no-static
--static/--build default locally and are ignored when --url is set.
Updating baseline screenshots
shell
# Update all baselines
yarn workspace @elastic/eui test-visual-regression update
# Update baselines against a specific URL
yarn workspace @elastic/eui test-visual-regression update -- --url https://eui.elastic.co/pr_1234/storybook
Reference images are stored in packages/eui/.vrt/reference/.
Variants
Each story is snapshotted under multiple variants to catch e.g. responsive-layout regressions. Every variant produces its own baseline file, suffixed with the variant name:
The test-runner is invoked once per variant (similar to Playwright projects), so each variant runs in its own process and browser context with the viewport applied before the story renders. Variants are defined in .storybook/vrt-variants.json. CI runs one variant per parallel job. Locally, scripts/test-visual-regression.js reads that file and sets VRT_VARIANT for each run.
Current variants:
Variant
Viewport
desktop
1440 × 900
mobile
390 × 844
Skipping specific variants
If a story can't render correctly under a particular variant, opt out of just that variant by passing an array to parameters.vrt.skip (see Skipping stories). The story still runs under the remaining variants.
Filtering stories
Pass any test-storybook flags after --. Jest path filters need -- before the pattern:
shell
# By story name
yarn workspace @elastic/eui test-visual-regression -- -- euibutton
# By story file
yarn workspace @elastic/eui test-visual-regression -- -- --testPathPattern=button.stories
# By tag
yarn workspace @elastic/eui test-visual-regression -- --includeTags vrt-only
yarn workspace @elastic/eui test-visual-regression -- --excludeTags skip-vrt
The wrapper runs both variants. For one variant only:
Set parameters.vrt.skip to opt a story out of VRT. Leave a comment explaining why.
skip: true skips the story under all variants.
skip: ['mobile'] skips only the listed variants; the story still runs under the rest.
tsx
export const MyStory: Story = {
parameters: {
vrt: {
// Skipped: this story is interaction-only, not a visual state
skip: true,
},
},
};
export const MobileUnsupported: Story = {
parameters: {
vrt: {
// Skipped on mobile: the toolbar control is hidden below this breakpoint
skip: ['mobile'],
},
},
};
Skipping a variant also skips the story's play body for that variant, so e.g. interactions don't run at a viewport the story isn't built for.
When you add vrt.skip to a story that previously had a baseline, manually delete the affected snapshot files from packages/eui/.vrt/reference/ (all variants for true or just the listed ones for an array).
Using non-default selectors
By default the test runner screenshots #story-wrapper > *. For components that render outside the story wrapper (portals, popovers, dropdowns) specify a custom selector in parameters.vrt.selector.
Portalled elements (popover, tooltip, dropdown); takes a full-page screenshot
Using interactions for specific states
Wrap play functions with playDecorator when they need to reach elements outside the story wrapper (e.g. portals). It injects bodyElement (document.body) into the context so within(bodyElement) can query portal content.
tsx
import { userEvent, waitFor, within, expect } from '@storybook/test';
import { VRT_SELECTORS, playDecorator } from '../../../.storybook/vrt';
export const OpenDropdown: Story = {
parameters: {
vrt: { selector: VRT_SELECTORS.portal },
},
play: playDecorator(async (context) => {
const { canvasElement, bodyElement } = context;
// canvasElement: content inside the story wrapper
const canvas = within(canvasElement);
// bodyElement: use this to reach portalled elements
const body = within(bodyElement);
await userEvent.click(canvas.getByRole('combobox'));
await waitFor(() => {
expect(body.getByRole('listbox')).toBeVisible();
});
}),
};
By default the play function is skipped when not running under Playwright (i.e. in the Storybook UI). Pass false as the second argument to run it everywhere:
VRT compares screenshots pixel-for-pixel (4-pixel allowance for subpixel diff), so anything non-deterministic (network requests, animations, randomness, timing or capturing the wrong element) produces false diffs or flaky failures. The test-runner already neutralizes several sources globally:
CSS animations are paused before the screenshot (animations: 'disabled') and prefers-reduced-motion: reduce is emulated.
The runner waits for the page to be ready: for all <img> elements to finish loading before capturing, for all fonts to be ready and for layout to be stabilized.
Failed screenshots are retried automatically.
Some common failures and how to fix them:
Only the first element is captured (fragment roots)
The default selector #story-wrapper > * screenshots the first child element of the wrapper. A render function or decorator that returns a fragment with multiple siblings therefore captures only the first one and clips the rest:
tsx
// ❌ Only the first <EuiBanner> is captured
render: () => (
<>
<EuiBanner />
<EuiBanner />
</>
),
// ✅ Wrap in a single element so the whole set is captured
render: () => (
<EuiBanner />
<EuiBanner />
),
This also applies to decorators that add sibling content (e.g. wrapping the story in explanatory text) - wrap them in an element, not a fragment.
Portalled content isn't captured
Popovers, tooltips, modals, flyouts and dropdowns render outside the story wrapper, so the default selector misses them. Use the portal selector to take a full-page screenshot (see Using non-default selectors):
An overlay opened on mount (through isOpen or initial state) may not be rendered/positioned when the screenshot fires, producing a blank or half-positioned capture that flakes. Add a play that waits for it to be visible:
tsx
import { within } from '../../../.storybook/test';
import { playDecorator } from '../../../.storybook/vrt';
export const Open: Story = {
parameters: { vrt: { selector: VRT_SELECTORS.portal } },
play: playDecorator(async ({ canvasElement }) => {
await within(canvasElement).waitForEuiPopoverVisible();
}),
};
For portalled panels you can also assert against bodyElement:
Never reference remote URLs (placehold.co, images.unsplash.com, picsum.photos, gravatar etc.). Network fetches are slow and unreliable in CI, causing instability. Inline the asset as a data: URI instead:
If a story genuinely needs a real raster image (e.g. the EuiImage showcase), either bundle a committed local asset or vrt.skip the story.
Randomized or time-based data
faker without a seed, Math.random(), Date.now() and new Date() render different output on every run. Pin them:
tsx
import { faker } from '@faker-js/faker';
faker.seed(123); // consistent data across runs
Use fixed dates/values for anything rendered (e.g. date="January 1st 1970").
Oversized stories
Stories that render hundreds of rows/items produce very tall screenshots that are slow to capture and can time out (especially under the mobile variant where responsive layouts stack every cell vertically). Keep datasets small. Note that EuiBasicTable/EuiInMemoryTable don't paginate items themselves, so passing a large array renders every row.
Interaction- or behavior-only stories
If a story exists to demonstrate behavior (focus return, callbacks, keyboard handling) and is visually identical to another captured story, it adds no VRT value and only risks flake - skip it with vrt.skip and a comment.
Text-only components
A bare text node can have zero height under #story-wrapper > *. Use VRT_SELECTORS.textOnly so the wrapper itself is captured (see Using non-default selectors).
Troubleshooting
Every story fails with `page.waitForLoadState: Timeout 30000ms exceeded`
waitForPageReady waits for networkidle on Playwright's own 30s timeout (separate from Jest's --testTimeout). The dev server's HMR keep the network busy. Under emulation it never goes idle in time, so every story times out and suites take 10-20 minutes.
Fix: don't pass --no-static locally. The default static build has no HMR, so networkidle settles instantly.
VRT is slow or times out locally on Apple Silicon
Local runs use a linux/amd64 container to match CI. On Apple Silicon (arm64) that image is emulated and much slower than native, making large stories hang or time out:
Enable Rosetta (recommended). In Docker Desktop, turn on Settings → General → "Use Rosetta for x86_64/amd64 emulation on Apple Silicon", and raise CPU/memory under Settings → Resources. Far faster than the default QEMU emulation while still linux/amd64, so screenshots stay byte-identical to CI.
Run natively for quick checks only. Dropping --platform linux/amd64 runs the native arm64 image (no emulation, much faster). Renders are usually identical but architecture can introduce 1-2px anti-aliasing differences, so don't commit baselines this way - use it only to check stories run and let CI produce baselines.
VRT runs automatically on every pull request as part of the eui-deploy-docs Buildkite pipeline.
mermaid
flowchart TD
PR[Pull request] --> WS[Build and deploy website]
PR --> SB[Build and deploy Storybook]
SB --> VRT[Test visual regression]
VRT --> R[Report visual regression]
R -->|skip-vrt label| N[Notify]
R -->|Pass, new stories| C1[Commit baselines\nto PR branch]
R -->|Pass, no changes| N
R -->|Fail, infrastructure error| N
R -->|Fail, visual diffs found| D[Post diff table\nto PR comment]
D --> B[Approve visual changes]
B --> U[Update VRT baselines]
U --> N
C1 --> N
WS --> N
CI commits baselines directly to the PR branch:
chore(eui): add VRT baseline screenshots - the PR adds new stories. Automatic, regardless of pass/fail.
chore(eui): update VRT baseline screenshots - after approving the Approve visual changes block step in Buildkite.
A PR with both new and changed stories gets both commits, add first. Either commit retriggers CI.
Skipping in a PR
If VRT itself is broken and blocking merges, add the skip-vrt label to the GitHub PR. The VRT step will detect the label, exit without running any tests and the notify comment will clearly state that VRT was skipped.
[!WARNING] skip-vrt doesn't run the test runner, so new stories don't get baselines. Be especially careful if you're adding it on your PR that introduces visual changes.
The label is captured at build-trigger time. To affect an existing build, trigger a fresh one:
Comment buildkite test it on the PR
Push any new commit
Close and reopen the PR
Buildkite UI: click New Build (not "Rebuild", which reuses the original env)
This page shares common tips and tricks for writing accessible UI copy. It is not exhaustive and does not replace the official WCAG 2.0 guidelines.
Writing accessible UI copy is only one part of improving web accessibility. Check the EUI documentation for more accessibility guidelines.
What is accessible content and why does it matter
Accessibility for content means making sure that the content can be consumed properly, independently of how users choose or have to interact with it.
Our users and readers are diverse, with different abilities and disabilities. They also interact with our content in different ways: screen readers, mobile devices, Braille, and so on. The list is long, and always evolving.
✔️ Make your content scannable. A clear structure and meaningful words tell users if the feature or flow is relevant to them within seconds.
✔️ Add alt text for all images, icons and media files. Screen readers, Braille output devices, and search engines love concise, accurate alt text that describes what cannot always be displayed, viewed or heard on screen.
Inaccessible
Accessible
Image of in progress rule creation form.
→
User is setting up a new threshold rule.
Logs integrations
→
Integrations that let you to ingest logs.
✔️ Use plain language. Clear, concise UI copy helps users perform tasks. Jargon and complex sentences can confuse or slow them down. You can find very helpful and detailed plain language guidance on the plainlanguage.gov website.
Inaccessible
Accessible
Update the sourcerer selection to be the default detections data view.
→
Choose the default Security data view.
Cancel update rule? You will lose changes to the rule.
→
Discard rule changes? Select Discard to reject these changes.
✔️ Use meaningful link text. Descriptive text instead of "click here" or "read more" helps users understand what to expect when they open the link. Screen readers jump between links by generating a list of them, and spell out URLs.
Inaccessible
Accessible
Click here for more information.
→
Learn more about indices.
Create a query. Learn more.
→
Learn how to create an ES|QL query.
✔️ Use device agnostic language. Users can access content and products in many ways. We do not know if they use a mouse, keyboard, tablet, or another device.
Inaccessible
Accessible
Type in your username and password.
→
Enter your username and password.
Select a time range for the Alerts table.
→
Choose a time range for the Alerts table.
✔️ Avoid directional language. Do not use above, below, left, right, and so on. All these terms assume that UI layouts never varies, that users can actually see the UI, and that they see it the way you think they do. Users might have disabilities or impairments that affect how they understand the UI.
Inaccessible
Accessible
Complete the form below.
→
Complete the following form.
Import values from a file by clicking the Import values button under the table.
→
Select Import file to import values from supported file types.
Words to avoid
Refer to Word choice to learn more about words to avoid and their replacements.
Packages Website Docs Content Adapt Tone (`packages/website/docs/content/adapt-tone.mdx`)
sidebar_position: 4
Tone
Adapt the way you write to each situation users can face.
During the users' journey with Elastic, there are times for excitement and discovery, but also times for help, support, and empathy.
Stimulating
Attributes
text
We're proud to show new and cool stuff. Be:
- Motivational
- Excited
- Enthusiastic
When using this tone, you may:
- Use exclamation marks, but sparingly.
- Take time to help the user understand how this feature benefits them.
- Use visuals to support your text.
When to use?
text
Although it can be very visible because of the way it's written and the UI elements it shows in, the **stimulating** tone is used **quite rarely** overall.
We use it when we want to show new things to users, especially in:
- Product tours
- New feature announcements
Informational
Attributes
text
E=MC². Be:
- Direct
- Straight to the point
- Neutral
When using this tone:
- Use a minimum of words.
- Write for scanning and help users quickly locate what matters to them.
- Use action verbs to:
- Label selection controls.
- Describe what settings do or what to do with a setting.
- Use clear nouns for
- menus, form field names
When to use?
text
The **informational** tone is the **most used** one. It is everywhere in the UI, and sometimes seems so normal that we forget about it. Yet, it is very important!
We use it for naming and describing things:
- Menus, titles, setting names, hint text, placeholders, introductions, and descriptions
- In-product assistance (tooltips, help popovers, setup guides, syntax references, etc.)
- Information callouts
- Confirmation modals
- Calls to action (buttons and links)
- Event logs
Supportive
Attributes
text
Something might be wrong and requires attention. Let us help you. Be:
- Reassuring
- Serious
- Urgent
When using this tone, you may:
- Be more action-oriented than usual to offer direct paths to users.
- Focus on what went wrong. Don't be apologetic.
- Help users learn how something happened, how to prevent it, how to fix it.
- Provide clear actions, or clearly state it if there's no action needed.
When to use?
text
The **supportive** tone is used **sparingly** overall and is usually shown in places that draw the user's attention, for example in warnings and error messages.
We use it when notifying users of things that require attention, especially in:
- Errors
- Licensing or billing-related content
- Vulnerability and remediation content
- Warnings and cautions
- Upgrade and deprecation notices
Stern
Attributes
text
We don't advise this action, but you're the master of your destiny. Be:
- Deterring
- Heavy with caution
- Advisory
When using this tone, you may:
- Use stronger words that highlight possible negative effects.
- Format the content in a way that make the risks stand out.
When to use?
text
The **stern** tone is **rarely** used. Reserve it for very specific cases, and for advanced or administrative features.
We use it when insisting on the high risks an action could pose, especially in:
- Danger type of warnings
- Unsupported actions
This page provides guidelines for writing inclusive UI copy for product content but it's not exhaustive.
What is inclusive content and why does it matter
Inclusivity for content means making sure that the content we serve reflects the diversity of our community, respects it, and promotes positive change.
Guidelines
Write for an international audience
Our products use American English (en_US) as a standard for written content. Yet they are used, read by persons all around the globe, for whom English is not always their primary language. Our content must take that into account.
✔️ Aim for simplicity. Indeed, we are a technical company, writing for technically-savvy users. However, it benefits everyone if we try to keep our writing as simple as possible.
Short sentences - They leave less space for interpretation. They are easier to scan, read, and translate.
Plain language - Use active voice, present tense, using examples, and so on. Some of these guidelines might already look familiar. Several countries have plain language initiatives to promote clearer communication. Do your best to embrace these guidelines and focus on the message.
Negation - It is easier to say what something IS versus what it is NOT. When you add a negative construction, it takes the reader longer to parse the meaning of the phrase. For example, instead of saying "You cannot access the content without signing up", say "Sign up to access the content."
Words with multiple meanings - Don't skip helper words if they make the sentence clearer and easier to read. In UI copy, we try to be as literal and unambiguous as possible to ensure that it can be easily consumed by our users from around the globe. One way to achieve that is to choose words that have fewer meanings, especially when a word's intended meaning is not its primary meaning.
✔️ Be aware of differences and diversity in content and examples. Different people are used to different names, currencies, date and time formats, different units of measurement (like for temperature, distance, speed), and so much more.
Avoid ambiguous values, like 04/05/06 for a date. Is it May 4 or April 5, and which year? 11/17/1987 leaves less room for interpretation if the exact format is not specified nearby.
If there is no obvious example standard (RFC) to follow, try to be diverse to represent our audience.
✔️ Avoid idioms or expressions that are not commonly known, a.k.a regionalisms.
In our Elastic documentation we aim for a fun, friendly, and sometimes quirky tone. To achieve that, it can help to use informal, playful language. However, we also have to be careful that our text is as inclusive as possible, so we try to avoid expressions that might be unknown or opaque for some users. Here are a few examples of what to avoid:
Idioms (for example, It's a piece of cake or So far so good)
Regional expressions (for example, G'day!, Y'all, or eh)
Sports expressions (for example, touched base or threw a curve ball)
Pop culture references (for example, Elvis has left the building or Same bat-time, same bat-channel)
We're all pretty good at avoiding these, but there's one problematic type of expression that sometimes shows up in UI copy. Latin terms and abbreviations are a common source of confusion, particularly for people whose first language does not have Latin roots.
Here are some terms to avoid and suggested alternatives:
Avoid
Use instead
e.g. (exempli gratia)
→
for example
etc. (et cetera)
→
and more or and so on
i.e. (id est)
→
that is
via
→
by way of, by means of, or through
✔️ Aim for readability. Tools like the Hemingway App can help you make content simpler. Be conversational, but prioritize clarity.
Use gender-neutral language
Writing gender-neutral mainly consists in avoiding gender bias and word choices implying that one gender is the norm.
✔️ Pronouns. In the UI, this can be avoided by addressing users directly. When it's not possible, use they/their, even for singular. There's more than one gender, and it's not binary either.
✔️ Biased words and expressions. Guys, mankind, policeman... Words that we're all used to hear and use. But the default is not male. Most expressions and words that perpetuate this bias (that exists in many cultures and languages!) can be replaced with neutral alternatives or synonyms: Folks, humanity, police officer...
Avoid violent, offensive, ableist terminology
Earlier in this page, we discussed avoiding ambiguous terms, especially when a word's intended meaning is not its primary meaning. Other types of words and phrases best avoided are:
buzzwords (incentivize, synergies)
superhero terms (rock star, wizard, ninja)
violent imagery (crush the competition)
non-specific superlatives (unrivaled, unparalleled, world class)
Some words have nuances that fall into the above categories, which may cause them to be misinterpreted. Here are some alternatives:
Avoid
Use instead
Abort
→
stop, cancel, or end
Boot
→
start or run
Execute
→
run or complete
Hack (noun)
→
tip or workaround
Hack (verb)
→
configure or modify
Hit (verb)
→
click or press
Hit (noun)
→
visits (as in website visits)
Invalid
→
not valid or incorrect
Kill
→
cancel or stop
Terminate
→
stop or exit
Packages Website Docs Content Language (`packages/website/docs/content/language.mdx`)
Guidelines for using consistent spelling and terminology in the UI.
Spelling
Use American English when writing UI copy. American English is a version of the English language used in the United States. It's sometimes called United States English or U.S. English.
Certain words are spelled differently in American English and British English. You'll find some of these key spelling differences in the following sections.
Verbs that end with 'ize' or 'yze'
In American English, verbs that end with 'ize' usually end with 'ise' in British English. Similarly, verbs that end with 'yze' in American English usually end with 'yse' in British English.
American English
British English
organize
organise
authorize
authorise
analyze
analyse
Nouns that end with 'or'
In American English, nouns that end with 'or' usually end with 'our' in British English.
American English
British English
flavor
flavour
color
colour
behavior
behaviour
Nouns that end with 'ense'
In American English, nouns that end with 'ense' usually end with 'ence' in British English.
American English
British English
license
licence
defense
defence
pretense
pretence
Nouns that end with 'og'
In American English, nouns that end with 'og' usually end with 'ogue' in British English.
American English
British English
dialog
dialogue
catalog
catalogue
epilog
epilogue
Case and capitalization
This goes for navigation menus, titles and headers, buttons, and so on.
Use sentence case by default for UI copy, including actions, views, settings, section headers, and dialog titles. Sentence case is easier to read, even in headings, and helps branded names stand out. It also makes those names easier to recognize and preserve when localizing an interface. Title Case can feel more formal, so don't use it just to make a label look prominent.
Reserve Title Case for proper names: branded products, apps, and features such as Entity Analytics and Machine Learning. A useful check is whether you'd refer to the item by that name in a product announcement, rather than using the words to describe what a section contains. For example, Stack management, GenAI settings, Budgets and notifications, API keys, and Trust management are descriptive labels, so use sentence case. Keep a proper name's established capitalization wherever it appears, and use sentence case for the surrounding words, as in "Open in Entity Analytics" or "Entity Analytics settings."
Even with sentence case, make sure to capitalize the following terms and expressions:
Branded terms like solution and application names. For example, always write Elastic Observability, not elastic observability.
Acronyms, such as URL and API.
Note that some names don't need to be capitalized when they're used as common names. For example, Elastic Cloud Serverless and our serverless architecture are both correct.
UI text
Casing
Why
Entity Analytics
Title Case
Feature name
Add data
Sentence case
Action, not a name
Open in Entity Analytics
Sentence case, with the name unchanged
Sentence-case action containing a capability name
Create Entity Analytics dashboard
Sentence case, with the name unchanged
"Entity Analytics" is a name; "dashboard" is generic
Our Voice represents the personality we want to convey through the UI copy in our products. The following principles define the foundation of our voice.
Conversational
Real people, talking to real people.
✔️ Be...
How?
User-focused
Address users directly. Don't abuse possessive markers like "yours".[1]
Plainspoken
Be conversational, yet professional. Write close to how you'd talk. Use contractions and active voice.
Inclusive
Write for all audiences. Our users are from everywhere. Don't use terms that might discriminate or offend and avoid complex language for non-native English speakers such as phrasal verbs.
Empathetic
Write what matters to users. Use words that you and users normally use.
[1] Sometimes, we give the user full ownership of an action. In that case, it's acceptable to use "I" and "my". For example, for a legal agreement checkbox "I agree to follow the terms of service". We can also use "We" when talking about actions Elastic takes for users.
Practical
Get the user's job done.
✔️ Be...
How?
Timely
Help users when it's needed. Not everything is critical to describe in more details or at once. Make information discovery progress at the same pace as the user.
Informative
Ensure all content serves a purpose and is not just decoration. Be straight to the point. It's not mandatory to have help text and descriptions for everything.
Action-oriented
Whenever possible, drive actions and support user intent with action verbs.
Precise
Avoid using verbs or determiners alone without a noun in buttons and action menus to add clarity. For example, prefer "Save dashboard" over "Save". Or "this setting" over "this".
Consistent
Use the same terminology to mean the same thing. Make sure spelling, capitalization, punctuation, labels, and use of abbreviations are all consistent.
Succinct
Shorten the content to something meaningful and scannable, without sacrificing clarity. Delete unnecessary or vague wording like most adverbs.
Authentic
Inspire trust.
✔️ Be...
How?
Optimistic
Help users progress and succeed with what they are doing right now. Do not blame them in case of errors and instead let them know what to do and encourage actions.
Confident
Encourage best practices but let users choose their path. Provide them with just enough information to help them make informed decisions.
Transparent
Help users connect the dots without jargon or vague statements. Provide values or realistic ranges to set the right expectations. For example "This operation can take up to 5 minutes" instead of "Wait a few minutes while we get you set up".
Packages Website Docs Content Word Choice (`packages/website/docs/content/word-choice.mdx`)
sidebar_position: 8
Word choice
Find the right term to use in your UI copy.
Use these preferred words and terms
Use these words and terms in the scenarios described in the following table:
Word
Usage notes
add
Use for establishing a new relationship. Often used in create-then-add scenarios. Create a dashboard, then add a visualization. In button labels, always follow add by an object. Remove is the correct opposite.
cancel
Use to stop an action without saving pending changes.
can't
Use to indicate you don't have the ability to do something. Often confused with unable.
create
Use for creating an object from scratch. In button labels, always followed by an object. Do not use create new. Delete is the correct opposite.
delete
Use when deleting data that users can no longer retrieve. Create is the correct opposite.
edit
Don't use change or modify. Edit is the better choice for localization.
enter
Use when the user enters text. Don't use type.
later
Use when referring to versions of the product. For example, Elastic Stack 8.13 and later.
open
Use when opening an application or program. Don't use launch.
press
Use press for keyboard keys. Don't use hit.
remove
Use when removing a relationship, but not permanently deleting the data. For example, you remove a visualization from a dashboard. Add is the correct opposite.
select
Select is preferred over choose.
use
Use instead of utilize and make use of.
view
Use instead of see because view is more inclusive.
Avoid these words and terms
This section includes terms that you should avoid in most instances. These terms might be offensive, non-inclusive, unclear, or unnecessary. Use the alternate suggestions in the following tables or a more specific term.
Latin abbreviations
Latin abbreviations can be unclear. Use the following suggestions instead:
Word
Usage notes
e.g.
Use for example or such as instead.
i.e.
Use that is instead.
via
Use with, by using, or through instead.
Directional language
Directional language does not meet accessibility requirements and you should avoid it, especially as the only way to find an element in the UI. Use the following suggestions instead:
Word
Usage notes
above
Use a link, previous, or preceding instead.
below
Use a link or following instead
Nouns created from verbs
Avoid using nouns created from adjectives or verbs. These words often make text unnecessarily complex. For example:
Use choose instead of make a choice.
Use register instead of complete your registration.
Use investigate instead of conduct an investigation.
General words and terms to avoid
Avoid the following words and terms, and use the suggestions instead:
Word
Usage notes
abort
Use shut down,cancel, or stop instead.
as well as
Use and instead.
blacklist
Use blocked list instead.
boot
Use start or run instead.
bottom left, bottom right
Use lower left or lower right instead. Hyphenate when using as an adjective. For example, lower-left corner.
choose
Use select instead. See Words for interacting with UI
disable
Use turn off, block, or hide.
enable
Use turn on or allow.
easy, easily
It can be frustrating for users to think that something is easy, but then struggle to do the task. Typically the same meaning can be conveyed without this word.
execute
Use run or start instead.
hack
For a noun, use tip or work-around instead. For a verb, use configure or modify.
hit
For a noun, use visits (as in web visits). For a verb, use select or press.
hear, hear about
Use learn instead.
impact
Don't use impact as a verb. Use affect instead.
in order to
Use simple verbs like to instead.
invalid
Use not valid or incorrect instead.
just
Don't use before a command, like "just click here."
launch
Use open instead.
ok
Don't use when labeling buttons. Use words that explain the action instead, for example, Delete rule.
normal, normally
Use usual or typical instead. For normally, use usually, typically, or generally instead.
please
In most cases, please is unnecessary. Only use when the user must wait or do something inconvenient.
simple, simply
Unnecessary because it doesn't add any information or value.
sorry
Only use sorry in error messages that cause serious problems for the user.
success
Unnecessary because success is generally implied.
terminate
Use stop or exit instead.
type
Use enter because there is typically more than one way to enter text.
top left, top right
Use upper left or upper right instead. Hyphenate when using as an adjective. For example, upper-left corner.
utilize
Don't use utilize when you mean use.
whitelist
Use allow list instead.
Use caution with these words and terms
These words and terms might be appropriate in some situations and inappropriate in others. Only use them when appropriate.
Word
Usage notes
app, application
Use only when needed for clarity. Otherwise, a Kibana application name can stand alone. App is a well-known abbreviation for application and is preferred.
begin
Use context to decide between begin and start. For example, begin a procedure, begin an analysis, or begin an installation are common phrases. Similarly, start a program, start an engine, or start a timer are frequently used. Start is considered less formal than begin. End is the correct opposite of begin.
can
Use for capability. Rewrite as an action if possible. For example, instead of you can add... use add....
click
Acceptable to use when describing mouse actions. Otherwise, use verbs that work with multiple devices, such as select.
clone
Use when creating a copy that is linked to the original. Often confused with copy and duplicate.
copy
Use when the file is added to a clipboard and can be pasted in a new location. Often confused with clone and duplicate.
disable
Don't use to describe something that is broken. Use inactive, unavailable, deactivate, turn off, or deselect, depending on the context.
duplicate
Use when immediately creating a copy of an object in the same location as the original. Often confused with copy and clone.
kill
Use cancel or stop unless the actual command is kill.
may
Use may for permissibility. Use can for capability. Use might for possibility.
start
Use context to decide between start and begin. For example, start a program, start an engine, or start a timer are common phrases. Similarly, begin a procedure, begin an analysis, or begin an installation are frequently used. Start is considered less formal than begin.
unable
Unable means not being able to perform an action. Often confused with cannot.
Packages Website Docs Dataviz Accessibility Features (`packages/website/docs/dataviz/accessibility-features.mdx`)
sidebar_position: 3
import useChartBaseThemeSource from '!raw-loader!./use_chart_base_theme';
Accessibility features
import VersionBadge from './version_badge';
mdx-code-block
import * as ElasticCharts from '@elastic/charts';
import { useChartBaseTheme } from './use_chart_base_theme.ts';
Elastic Charts provides some accessibility features for all users.
Available a11y-related features
Chart titles
Chart descriptions
Chart data in tabular form*
Texture fill*
Semantic groupings*
*Not all chart types that may benefit from this feature have it implemented as of yet
Titles
ariaLabel and ariaLabelledBy props can be set through the <Settings /> component.
ariaLabel takes a string label which will be visually hidden whereas theariaLabelledBy prop can tie the chart to other accessible elements elsewhere in your app.
tsx
<>
<h1 id="randomHeadingId">Days of the week: weekdays vs weekends</h1>
<Chart>
<Settings
ariaLabelledby="randomHeadingId"
/>
</Chart>
</>
If using ariaLabel, you can set a heading level for it by passing in ariaLabelHeadingLevel. h1 through h6 are valid options (p is the default).
You may want to set a heading level if there are multiple charts on the page but you want to draw attention to a select few or if a specific chart is the main content of the page.
tsx
<Chart>
<Settings
ariaLabel="Days of the week: weekdays vs weekends"
ariaLabelHeadingLevel="h1"
/>
</Chart>
Descriptions
ariaDescription and ariaDescribedBy props can be set through the <Settings /> component.ariaDescription takes a string description which will be visually hidden whereas ariaDescribedBy prop takes an id that you have rendered elsewhere in your app.
tsx
<Chart>
<Settings
ariaLabel="Days of the week: weekdays vs weekends"
ariaDescription="Bar one is labelled weekdays and has a value of 5. Bar two is labelled weekends and has a value of 2."
/>
</Chart>
:::warning ariaDescribedBy will override ariaDescription if both are specified. :::
ariaUseDefaultSummary is a boolean defaulted to true. The default summary is bare bones and only reports the chart type(s). Some chart types provide more information but today that is only Gauge and Goal charts. If you are passing in a custom description, you can disable this prop.
tsx
<>
<h1 id="randomHeadingId">Days of the week: weekdays vs weekends</h1>
Bar chart with two bars. Bar one is labelled weekdays and has a value of 5. Bar two is labelled weekends and has a value of 2.
<Chart>
<Settings
ariaLabelledby="randomHeadingId"
ariaDescribedBy="randomDescId"
ariaUseDefaultSummary={false}
/>
</Chart>
</>
Tables
A visually hidden data table is rendered for some chart types. ariaTableCaption sets a custom caption on that table. Consider the table caption supplementary to the description. Use it to fill in any gaps that might be created when converting the chart to a tabular format.
:::note In version 64.1.0, data tables are only available for partition charts.
Packages Website Docs Dataviz Dashboard Good Practices (`packages/website/docs/dataviz/dashboard-good-practices.mdx`)
sidebar_position: 2
import figmaDashboardGoodPracticesStructure from '!url-loader!./assets/dashboard-good-practices_structure.svg'; import figmaDashboardGoodPracticesClosure from '!url-loader!./assets/dashboard-good-practices_closure.svg'; import figmaDashboardGoodPracticesHierarchy from '!url-loader!./assets/dashboard-good-practices_hierarchy.svg'; import figmaDashboardGoodPracticesAspectRatio from '!url-loader!./assets/dashboard-good-practices_aspect-ratio.svg'; import figmaDashboardGoodPracticesPickTheRightChartModel from '!url-loader!./assets/dashboard-good-practices_pick-the-right-chart-model.svg'; import figmaDashboardGoodPracticesRelevance from '!url-loader!./assets/dashboard-good-practices_relevance.svg'; import figmaDashboardGoodPracticesTitles from '!url-loader!./assets/dashboard-good-practices_titles.svg'; import figmaDashboardGoodPracticesMargins from '!url-loader!./assets/dashboard-good-practices_margins.svg';
Dashboard good practices
import VersionBadge from './version_badge';
Dashboards are the most common way to monitor our own data, to be alerted of important events and to gather insights. They typically work combining different charts that might be completely unrelated as they can share the same data source from different points of view.
To understand the role of dashboards it's important to frame what they are and what is their function, to avoid using them in the wrong way. We will use the definition that Stephen Few uses in his “Information design dashboard”, where he states:
A dashboard is a visual display of the most important information needed to achieve one or more objectives; consolidated and arranged on a single screen so the information can be monitored at a glance.
It's important to learn how to get the best out of this tool, to do so we put together a series of high level principles that could help you to create a dashboard that leverages your data and facilitates its understanding.
NOTE: if you are looking for a practical guide of the functionalities of dashboards and a basic tutorial on how to handle them, have a look at this page: Kibana Guide
Structure
A preliminary research conducted on dashboards made by Elastic customers showed a tendency to relate on one of the following templates when building a monitoring application. Unfortunately, we don't offer (yet) a simplified way to re-created on of these templates but we suggest to take inspiration from one of these four major categories when building your own dashboard, according to your needs and to the goal of your product.
Context
A wide assumption in design is that everything depends on the context and dashboards are not different. Similar charts could work differently in different environments and therefore we should always try to understand as much as possible what kind of context we are working with.
To help this process you can try asking yourself questions like:
1 / Who is going to use this dashboard?
Identifying your users is the most important thing that you have to do. This will help you understand how descriptive you have to be with title, charts typologies and explanation according to users skills and field knowledge.
2 / How much time will the users have?
Dashboards should always be conceived for an immediate, clear and fast fruition. Don't overcomplicate it, give the users the information they need in the best and clearest way possible.
3 / What is the main goal of this dashboard and if there are, what are the secondary ones?
This could help you identify your primary charts and how to build up a narrative that could go from primary to secondary charts, creating a comprehensive overview of the context.
4 / What kind of charts can help users identify insights in the most immediate and clear way?
We know this is not a simple task and we are currently working on a proper series of guidance on how to choose a chart, our plan is to have a dedicated page for each chart typology where you will be able to find all the information needed to use it properly. In the meanwhile we can recommend two great resources that will help you decide what to do with a set of data. First, the chart classification made by dr. Andrew Abela is a classic in the determination of the best charts based on function. You can find it here. Second, this great archive made by the Danish design studio Ferdio helps you learn about charts and filter by purpose. You can find the archive here.
Organization and hierarchy
Studies such as Gestalt show how the positioning of elements in space can define their belonging, with a certain degree this can be applied to dashboards.
Closure
Charts that are related to the same typology of data or that are somehow connected by meaning should stay close together. When two related charts are placed away from each other the effort required of the user to move from one to the other could be too much, it's better to try to reduce this effort by putting them close together.
Sometimes they can also work together. In the following example we show how two Metric Charts could live next to a Line Chart that share a more detailed version of the same data. As you can see the charts colors match, this way we can safely remove the legend from the line chart which is no longer necessary and we can have a wider understanding of the data.
Hierarchy
There are two main concepts that you have to keep in mind when organizing charts in a dashboard:
Reading Direction
Most people are used to reading from top to bottom, this concept can be applied on dashboards as well. Place at the top of your page the most important charts and the ones that could give a brief and immediate summary of the context. A good general suggestion would be to increase the level of detail while you reach the bottom of the dashboard, this way users that are interested in getting all the information can obtain them without requiring too much effort from user that only need a quick glance of the situation.
Central focal point
Placing a big chart, especially with big visual shapes such as rectangles, at the center of the dashboard would help reinforce a natural visual focal point that lies in the center of the interface. As you can see in the following example, the barchart with blue bears is clearly the predominant piece of information and where we want the users to focus primarily.
Keep in mind these two concepts and use them according to your needs.
Rows
A simple and common practice to structure your dashboard would consist in dividing it into rows. Placing charts in rows would make them belong to “sections” and therefore they will be considered related among each other. It's a good principle to follow when structuring a dashboard from top to bottom into different layers of detail and complexity.
Aspect ratio
Within the data visualization field there have been multiple attempts to create a solid and unique rule to guide people in designing well proportioned charts. When is it ok to have an horizontal layout, how much can we shrink it without losing readability?
There is no universal answer to these questions and the rules that currently are shared among the data community are far too complex to be understood and correctly used by anybody. We believe that a high level concept could do the work in our case.
We suggest avoiding charts that are too narrow or too short, since we mainly deal with time series data and keep in mind that an horizontal proportion might be the best in showing trends and small variations.
There are as usual exceptions, some charts (e.g. scatterplot) might benefit from squared proportions, look for specific instructions on each chart's page.
Pick the right chart model
When creating a new chart please consider its implied features. In the following example we can see how the area chart on the left is hard to read when we have really small numbers. Lines that move diagonally tend to hide small numbers due to the oblique direction that they follow, a stacked barchart instead is way better in showing details such as the one described.
This is just an example useful to explain the concept and can be applied to several typologies of visualization, if you are stuck and don't know which chart to pick please feel free to reach out to the datavis team.
Colors
Consistency
The most important thing to keep in mind is that the best option is always to stick with the guidelines provided by our EUI. Colors provided there for visualization have been tested for accessibility contrast and using them you would be sure to properly serve the biggest audience. You can find this documentation here
In addition to that, following these guidelines you'll be sure to get a consistent interface that will work perfectly with all other solutions.
Relevance
If your dashboard is made to identify specific behaviors it might be interesting to consider a color setting that could help pointing it out. Use a neutral color for generic elements and an accent one for the things that you are looking for.
Custom brands
Sometimes it happens that a dashboard needs to be customized according to a client brand identity. When so, be cautious and pick really few colors to work with, try to make the charts consistent among each other and don't forget about accessibility.
If you want to go deeper and study how to work with colors, consider reading this great summary by Lisa Charlotte Muth from Datawrapper.
Titles, labels and margins
Titles
Titles can have a strong visual impact on dashboards, especially when there are a lot of small charts. Two principles can generally be followed:
remove unnecessary or repetitive titles when the information is already
explained or written within the chart (e.g. Metric Charts)
when title is needed make it self explanatory and exhaustive, this way
you will be able to remove axis titles and other specifications leaving
more space for the charts itself.
Margins
Kibana dashboards offer the possibility to apply margins between chart panels, we would suggest to always do that. Margins create separation between charts which is an important visual feature, it helps identifying when two elements belong together or not while, at the same time, they provide more spacing and empty spaces that are always useful in making our interface more clean and elegant.
Using margins will also automatically apply little shadows to panels, this will make them stand out from the background.
Labels, numbers and formatting
Reduce the number of decimal places to the absolutely necessary ones.
Use tables whenever you need precise numbers.
If possible, remove redundant labels. If we are talking about Usage
metrics, then you can easily remove the Usage word used across 7 charts
out of 8 charts in the prev example.
Lens
Always use Lens, when possible. There are still a few functionalities that you might not find in Lens (within this document you can learn more about them) but everything is gradually moving into this application. It's the best choice to be consistent and up to date and it should always be your first choice when creating new visualizations.
import useChartBaseThemeSource from '!raw-loader!./use_chart_base_theme';
Sizing
import VersionBadge from './version_badge';
When placing charts into smaller containers or panels, you must re-evaluate your data to provide a more simplified version. This could be as simple as shifting legend positions from the right side to the bottom or adding annotations to give context and describe the key points in your data.
mdx-code-block
import { Sizes } from './sizes';
import BrowserOnly from '@docusaurus/BrowserOnly';
{() => }
Sparklines
Sparklines are quick visual summaries of data where actual values are not important. They should be only a single series and not contain more than 12 values. Be sure to remove all extraneous markings like ticks, labels, tooltips and grid. The surrounding content should give context to the sparkline.
EUI also provides a quick theme alteration object that you can merge with the correct dark or light theme to properly style your sparklines.
import useChartBaseThemeSource from '!raw-loader!./use_chart_base_theme';
Theming
import VersionBadge from './version_badge';
mdx-code-block
import { useChartBaseTheme } from './use_chart_base_theme';
import * as ElasticCharts from '@elastic/charts';
EUI provides both light and dark theme files to use in tandem with Elastic Charts. Simply import these objects from the themes folder and pass the correct one to the Settings.theme property.
EUI provides a plugin utility for ease of pulling in the correct theme object depending on the current Kibana theme. Learn more from this readme.
:::
EUI also provides some basic color palettes and functions if you would like to change the default color blind safe scheme to another palette. You can import these from the services folder. Create a new partial theme object with your chosen colors and prepend it to the list of themes supplied to Settings.
/* Detailed source-code truncated for AI context efficiency. */
Coloring charts
Use color to distinguish categories, represent quantity/density, and highlight data. When using color in this way, be aware that too many colors in a single chart can create noise and hinder quick comprehension.
When creating a multi-series chart where each series shows contrasting data, use the color blind safe palette of contrasting colors. This will also avoid implying levels of magnitude.
Think about the data you are delivering and if there is a way to highlight key indicators. If you can combine the series into logical groups, use contrasting shapes and styles, but keep the same color for within groups.
Quantity vs trends
When coloring for sequential series data (not categorical), rely on conventions. If the data signifies quantities, use a single color that spans from light colors for low amounts to dark colors for high amounts. If the data signifies trends, use a two-color divergent scheme, with the darkest colors at the extremes.
When signifying quantities, group values into intervals instead of a continuous gradient scale.
mdx-code-block
import { Categorical } from './types/categorical';
Packages Website Docs Getting Started Accessibility (`packages/website/docs/getting-started/accessibility.mdx`)
sidebar_label: Accessibility
sidebar_position: 4
Accessibility guidelines
mdx-code-block
import Link from '@docusaurus/Link';
import { EuiFlexGroup, EuiFlexItem, EuiText } from '@elastic/eui';
:::info Check component-level guidance
This page provides some general accessibility guidelines. For component specific recommendations, be sure to check out their individual documentation pages.
:::
EUI provides a strong start to building accessibility into your apps. The components provided strive to meet WCAG 2.1 guidelines on semantics, keyboard functionality, color contrast, and so on. How you stitch together these components in the overall page structure also plays a large role in meeting accessibility goals. Headings, landmarks, page titles, focus management, and accessible names all work together to create accessible apps.
Building accessibility into your app is as important as code quality, visual design, and performance, and it’s also important that you test as you go. You can approach accessibility testing from three dimensions: automated, manual, and empathetic thinking. Use automated tests to quickly cover as much ground as possible, manual tests to address more complicated scenarios, and empathy to fill in the gaps.
For a technical intro to accessibility and how EUI tackles it
import { EuiAspectRatio } from '@elastic/eui';
Headings and landmarks
You can aid navigation and make pages more accessible for screen reader users by using solid headings and landmarks. Headings are the simplest way for screen readers to navigate pages. A good heading hierarchy:
Uses only one <h1> on each page
Doesn't skip levels <h1> → <h6>
Doesn't duplicate content
Heading examples
```tsx
Discover your data
Some content
Drill into site metrics
An important site metric
Another important site metric
```
```tsx
Discover your data
Some content
Discover your data
text
<h4 className="myLargeTitle">
An important site metric
</h4>
</EuiText>
```
:::tip EuiTitle gives you a way to separate your presentation from your semantic markup. :::
```tsx
Discover your data
Drill into site metrics
An important site metric
Another important site metric
```
Landmarks are another way for screen readers to navigate pages. A benefit of landmarks is that they offer more context on the type of content to expect than a heading. This is useful for tech that offers reader modes (e.g., Firefox, Safari, and apps like Pocket) and new form factors (e.g., smartwatches). Many landmarks are mapped to HTML elements, such as <main>, <aside>, <article>; others are exposed through the role attribute.
You can implement named landmarks with aria-label or aria-labelledby. However, having a heading inside of the landmark (even if it is visually hidden) and referenced by aria-labelledby is preferred.
Up Your A11y: Heading Levels in Reusable Components
Page titles
Each page requires a unique, informative title that accurately reflects what the page does. The best page titles put the unique content first. Effectively, they're reverse-order breadcrumbs.
Use this format:{Unique page title} - {Site title}
**Discover - Kibana**
Rollup Jobs - Management - Kibana
<Guideline type="dont" panelPadding="l" text={ <> Though unique, this does not provide enough context.
Watchers - Management - Kibana
</> }
Watchers
<Guideline type="dont" panelPadding="l" text={ <> Although it provides all the context, putting the most important bit at the end is hard to find.
Spaces - Management - Kibana
</> }
Elastic Kibana - Spaces
<Guideline type="dont" panelPadding="l" text={ <> Although this provides all the context and in a good order, a title is not the place for any extra words.
Reporting - Management - Kibana
</> }
This is the Reporting page of the Management section of Kibana.
Further reading
WCAG 2.4.2 Page Titled - Level A
Focus management
Where is the focus state right now?
Focus states are an important part of design because they let keyboard users know where focus is currently at. All browsers ship with focus states for interactive elements, and most of the time you shouldn’t need to alter these. EUI goes further to customize focus states to match the Elastic brand and provide better visual states, including color contrast.
Where is the focus state going?
Given that a keyboard user primarily navigates pages in one direction (either forward or backward), it’s important to have an intuitive focus order. Focus order should follow the flow of the page to make it easy to follow. If you’ve made a normally non-interactive element like a <div> interactive via JavaScript, you can enable a tab stop using tabIndex=0. If you want something that is only focusable programmatically, you can use tabIndex=-1.
:::danger Using tabIndex values greater than 0 is problematic and should be avoided. :::
How do I get back to where I was?
Navigating complex sites sometimes means your focus state will jump around (e.g., skip links, modals, typeahead, and so on). If you remove an element that currently has focus without setting focus anywhere else, users start over at the beginning of the page. Unless there’s a strong reason to do otherwise, focus state should always return to where it was previously if the currently focused element disappears. For example, closing a modal might mean your focus is on a close button; when the modal closes, you should return focus to the button that opened the modal.
Further reading
W3C: Keyboard Compatibility
React docs: Programmatically managing focus
MDN: Keyboard-navigable JavaScript widgets
WebAIM: Keyboard Accessibility
Up Your A11y: Getting Started with Keyboard Navigation and Screen Readers
The difference between keyboard and screen reader navigation
Disabled elements
Browsers remove disabled elements from the tab order. This means keyboard users cannot tab to those elements or activate nested focus behaviors.
If you disable a button or form element, you need to provide clear instructions to users how to correct errors or remove the disabled state.
When you add the HTML disabled attribute to an element, you must remove tooltips or other focus interactions. You must also remove focus interactions if you pass the isDisabled prop to components like EuiAccordion.
Further reading
MDN: Tabindex
W3C: Disabled elements
Naming
An accessible name is the name of an HTML element as it’s exposed to assistive technology. An accessible name can then be read by a screen reader or can be targeted for an action.
Most elements
For most content, the accessible name comes from the element’s inner text, such as: <a href="https://elastic.co">Elastic.co</a>. A screen reader can now read it out something like “Elastic.co, link” or, using voice commands, it can be controlled with “Click Elastic.co link”.
Images and other elements
Some content might require special attributes to give an element an accessible name. For images, you can use alt attributes, such as:
text
Buttons without inner text
For buttons without descriptive text content, you can rely on ARIA to bring meaning back:
text
X
Forms and more complex patterns
Some HTML elements have associated elements that provide accessible names. Form elements are the most ubiquitous example: a checkbox doesn’t have a name by itself, but when it is associated with a label, assistive technologies can make the connection:
text
<label for="subscribe">Subscribe to Elastic news</label>
Of note: Repeated calls to action
Having only an accessible name, however, doesn’t always lead to the best UX. Take a list of available fields that someone might want to add to their filter (say, on the discovery page of a popular open source project):
Here, the 3 buttons have the same accessible name. There are a few different patterns you can use to differentiate between repeated items. For example, each button below shows a possible pattern you can use (in order of recommended best practice):
text
<h3 id="available">Available fields</h3>
<li>
_id
add
</li>
<li>
@timestamp
add
<EuiScreenReaderOnly>
@timestamp field to your current filter
</EuiScreenReaderOnly>
</li>
<li>
_index
add
</li>
:::tip Give lists an accessible label to improve their discoverability!
text
<h1 id="a1b2c3">My favorite fruit</h1>
...
:::
Further reading
W3c: Labeling Controls
The Paciello Group: What is an accessible name?
WebAIM: Creating Accessible Forms
Mozilla: How accessibility trees inform assistive tech
A List Apart: Semantics to Screen Readers
Testing considerations
There are a lot of aspects to accessibility, and covering all the bases can be a lot to keep in mind. By relying on standards, you can minimize the amount of special casing you have to do in code, but you should still be cognizant of the many modalities in which users might use your products.
Low-vision
While low-vision users may use many assistive technologies in tandem, this section focuses on zooming. Two ways that users can zoom the page are by increasing the base font-size with browser tools or by using a 3rd-party magnifier (sometimes, a physical magnifier) to better see the screen.
WCAG 1.4.4 defines 200% browser zoom should continue to work with no further action from the user as a Level AA criteria.ZoomText is the most popular 3rd-party magnifier that gives users a window they can drag over content to magnify and read it out loud. Testing for the best experiences here is exceptionally difficult because you must make visual judgement calls. Specifically, related pieces of information should be close enough together for a low-vision user to efficiently interact with the UI.
Low-vision/blind (screen readers)
Blind and low-vision users often rely on tools, such as screen readers and braille readers, to navigate the web. Screen and braille readers read the page from top to bottom. Building a page with a good structure, will make it quick and easy to navigate. Braille readers are a textual representation of what a screen reader would say so we can focus on screen reader compatibility.
The 3 most common, desktop, screen readers, and their most common browser pairings are:
JAWS with Chrome
NVDA with Firefox
VoiceOver with Safari
Mobile is a little simpler:
VoiceOver for iOS
TalkBack for Android
Apple docs: Learning VoiceOver Basics
WebAIM: Using VoiceOver to Evaluate Web Accessibility
WebAIM: Using NVDA to Evaluate Web Accessibility
WebAIM: Using JAWS to Evaluate Web Accessibility
An Intro To Screen Reader Testing for Sighted Developers
Learning resources
A wide-reaching guide on accessibility on MDN covering basics and best practices for a variety of subjects
The caniuse of ARIA attributes: Accessibility support
Accessibility web fundamentals by Google, similar in content to the MDN guide, but more guided
If you prefer videos to reading, a great “pick your subject” style series A11ycasts is available
Practical examples
For many things, there’s no need to reinvent the wheel. If your component is featured in one of these two sources, feel free to borrow heavily!
Inclusive Components
Accessible Components
Tooling
Axe plugin for Chrome and FF
Spec docs
Using ARIA
WCAG 2.1
How to Meet WCAG (Quick Reference)
WAI ARIA 1.1
Packages Website Docs Getting Started Setup (`packages/website/docs/getting-started/setup.mdx`)
sidebar_position: 1
Setup
import Link from '@docusaurus/Link';
Installation
EUI is published through NPM as a dependency. To install EUI into an existing project, use the yarn CLI (npm is not supported).
bash
yarn add @elastic/eui
Note that EUI has several peerDependencies requirements that will also need to be installed when starting with a blank project.
bash
yarn add @elastic/eui @elastic/eui-theme-borealis @elastic/datemath @emotion/react @emotion/css moment
You can read more about other ways to consume EUI in our wiki.
Supported environments
EUI is designed to work in the following environments:
mdx-code-block
import { EuiTable, EuiTableHeader, EuiTableHeaderCell, EuiTableBody, EuiTableRow, EuiTableRowCell } from '@elastic/eui';
import { css } from '@emotion/react';
EnvironmentSupported?Details
text
<EuiTableRow>
<EuiTableRowCell>React 16</EuiTableRowCell>
<EuiTableRowCell align="center">❌</EuiTableRowCell>
<EuiTableRowCell>
Support ended with EUI v106.0.0. Please upgrade to a modern React version.
</EuiTableRowCell>
</EuiTableRow>
<EuiTableRow>
<EuiTableRowCell>React 17</EuiTableRowCell>
<EuiTableRowCell align="center">✅</EuiTableRowCell>
<EuiTableRowCell>Fully supported</EuiTableRowCell>
</EuiTableRow>
<EuiTableRow>
<EuiTableRowCell>React 18</EuiTableRowCell>
<EuiTableRowCell align="center">✅</EuiTableRowCell>
<EuiTableRowCell>Fully supported (recommended)</EuiTableRowCell>
</EuiTableRow>
<EuiTableRow>
<EuiTableRowCell>React 19</EuiTableRowCell>
<EuiTableRowCell align="center">❌</EuiTableRowCell>
<EuiTableRowCell>
Not supported (plans to <Link href="https://github.com/elastic/eui/issues/8720">introduce support for React 19</Link>)
</EuiTableRowCell>
</EuiTableRow>
<EuiTableRow>
<EuiTableRowCell>Webpack</EuiTableRowCell>
<EuiTableRowCell align="center">✅</EuiTableRowCell>
<EuiTableRowCell>Fully supported</EuiTableRowCell>
</EuiTableRow>
<EuiTableRow>
<EuiTableRowCell>Vite/Rollup/Parcel/esbuild</EuiTableRowCell>
<EuiTableRowCell align="center">⚠️</EuiTableRowCell>
<EuiTableRowCell>
Supported with caveats (<Link href="https://github.com/elastic/eui/issues/7630">details</Link>)
</EuiTableRowCell>
</EuiTableRow>
<EuiTableRow>
<EuiTableRowCell>Strict Mode</EuiTableRowCell>
<EuiTableRowCell align="center">❌</EuiTableRowCell>
<EuiTableRowCell>
Not supported (<Link href="https://github.com/elastic/eui/issues/7774">details</Link>)
</EuiTableRowCell>
</EuiTableRow>
<EuiTableRow>
<EuiTableRowCell>Server-side Rendering</EuiTableRowCell>
<EuiTableRowCell align="center">⚠️</EuiTableRowCell>
<EuiTableRowCell>
Supported with caveats (<Link href="https://github.com/elastic/eui/issues/5419">details</Link>)
</EuiTableRowCell>
</EuiTableRow>
If you have questions about your environment or run into issues, please open a GitHub issue or start a discussion.
Setting up your application
EUI uses CSS-in-JS (specifically Emotion) for its styles and theming. As such, we require an <EuiProvider> wrapper around your application in order for various theme-related UI & UX (such as dark/light mode switching) to work as expected.
You can build custom components using EUI's theme tokens, consumed via useEuiTheme(). The below example uses Emotion's css prop, but any CSS-in-JS library should be able to interpolate the JS variables.
import React from 'react';
import { EuiIcon, EuiCode, EuiText, useEuiTheme } from '@elastic/eui';
import { css } from '@emotion/react';
export default () => {
const { euiTheme } = useEuiTheme();
return (
<EuiText>
<EuiIcon
type="stopFill"
size="xxl"
css={{ color: euiTheme.colors.primary }}
/>{' '}
This primary color will adjust based on the light or dark theme value
The padding of this box is created using <EuiCode>calc()</EuiCode>{' '}
because EUI's theme sizes are string pixel values that are
calculated off the theme's <EuiCode>base</EuiCode>
</EuiText>
);
};
Customizing with classes
For consumers using vanilla or preprocessed CSS, all components allow you to pass a custom className prop, which will be appended onto the component.
:::warning Avoid overwriting .eui class names
EUI's class names are not a guaranteed API and are prone to change, which will risk breaking your theme. Target your custom classNames instead.
EUI can be slightly customized to fit your branding needs by altering the base tokens like color and typography. You can pass a full or partial list of override tokens to the EuiProvider's modify prop.
:::warning Touch the least amount of variables possible
By nature, EUI is very rigid. You shouldn't need much to make drastic changes to color. Most themes are less then a dozen variable overwrites in total.
import React, { FunctionComponent, ReactNode } from 'react';
import { EuiCode, EuiText, EuiThemeProvider, useEuiTheme } from '@elastic/eui';
const Box: FunctionComponent<{ children: ReactNode }> = ({ children }) => {
const { euiTheme } = useEuiTheme();
return (
<EuiText
css={{
background: euiTheme.colors.lightShade,
padding: euiTheme.size.xl,
}}
>
{children}
</EuiText>
);
};
export default () => {
const overrides = {
colors: {
LIGHT: { lightShade: '#d3e6df' },
DARK: { lightShade: '#394c4b' },
},
};
return (
<EuiThemeProvider modify={overrides}>
The background of this box is using the locally overridden value of{' '}
<EuiCode>euiTheme.colors.lightShade</EuiCode>
</EuiThemeProvider>
);
};
Fonts
By default, EUI ships with a font stack that includes some outside, open source fonts. If your system is internet available you can include these by adding the following imports to your SCSS/CSS files, otherwise you'll need to bundle the physical fonts in your build. EUI will drop to System Fonts (which you may prefer) in their absence.
If your application doesn't allow grabbing the font assets from a CDN, you'll need to download and locally provide the font files. You can download the files directly from their source site rsms.me/inter/, then follow the instructions in the provided CSS file for importing. We recommend using the single variable font file and importing with the following settings:
You can import React components from the top-level EUI module.
tsx
import {
EuiButton,
EuiCallOut,
EuiPanel,
} from '@elastic/eui';
Most services are published from the lib/services directory. Some are published from their module directories in this directory.
tsx
import { keys } from '@elastic/eui/lib/services';
import { Timer } from '@elastic/eui/lib/services/time';
Test utilities are published from the lib/test directory.
tsx
import { findTestSubject } from '@elastic/eui/lib/test'; // Enzyme
import { findByTestSubject, render, screen } from '@elastic/eui/lib/test/rtl'; // React Testing Library
Customizing component defaults
While all props can be individually customized via props, some components can have their default props customized globally via EuiProvider'scomponentDefaults API. Read more in EuiProvider's documentation.
Packages Website Docs Getting Started Tree Shaking (`packages/website/docs/getting-started/tree-shaking.mdx`)
sidebar_position: 2
keywords: [tree-shaking, bundle size, optimization, dead code elimination]
Tree-shaking
mdx-code-block
import Link from '@docusaurus/Link';
Tree-shaking (dead code elimination) allows bundlers to remove unused code from your production builds, reducing the amount of JavaScript shipped to users.
How EUI supports tree-shaking
EUI publishes two build outputs:
es/ — ES modules (used by modern bundlers via the module field in package.json)
lib/ — CommonJS (used as fallback via the main field)
The es/ build preserves import/export syntax, which bundlers like Webpack, Rollup, and esbuild can statically analyze to determine which exports are actually used.
EUI's package.json also declares:
json
{
"sideEffects": ["*.css", "*.scss"]
}
This tells bundlers that all JavaScript modules are side-effect-free and safe to remove if unused. Only CSS/SCSS files are marked as having side effects.
What you need
For tree-shaking to work, your bundler must resolve @elastic/eui to the ES module build (es/). Most bundlers do this automatically via the module field. Verify that you're importing from the top-level package:
tsx
import { EuiButton, EuiPanel } from '@elastic/eui';
No special configuration is needed — a properly configured bundler will tree-shake unused components automatically.
Icons and code-splitting
EuiIcon uses dynamic imports to load icon assets only when actually used. Depending on your bundler's configuration, each icon may become a fully separate chunk loaded lazily at runtime, or be otherwise grouped together with other code. It's common for bundlers to decide that based on the minimum and maximum chunk sizes to optimize code delivery efficiency. Please check your bundler documentation to learn more.
ts
// Simplified example of how EUI maps icon names to dynamic imports
const typeToPathMap = {
calendar: () => import('./assets/calendar'),
check: () => import('./assets/check'),
// ...
};
This means icon SVGs are not included in your main bundle — they're fetched only when a specific icon renders on the page. However, the mapping object itself (which associates icon names with their chunk references) is always included when EuiIcon is used.
:::info There is an ongoing effort to refactor EuiIcon for improved performance and reduced mapping overhead. :::
Verifying tree-shaking in your project
To confirm tree-shaking is working:
Webpack: Use webpack-bundle-analyzer to visualize your bundle and check that unused EUI components are not present
Vite/Rollup: Use rollup-plugin-visualizer for a similar analysis
A minimal app importing only EuiButton should produce a production bundle significantly smaller than the full EUI library.
CommonJS consumers
If your project resolves to the lib/ (CommonJS) build, tree-shaking will not work because CommonJS modules are not statically analyzable. In this case, you can use direct subpath imports to limit what gets included:
tsx
import { EuiButton } from '@elastic/eui/lib/components/button';
import { keys } from '@elastic/eui/lib/services';
:::warning Subpath imports are not a guaranteed API and may change between versions. Prefer using the ES module build with top-level imports for reliable tree-shaking. :::
Packages Website Docs Patterns Help Content (`packages/website/docs/patterns/help-content.mdx`)
sidebar_position: 4
import figmaHelpContentShortDefinitions from '!url-loader!./assets/help-content_short-definitions.svg'; import figmaHelpContentForASpecificSettingOrGroup from '!url-loader!./assets/help-content_for-a-specific-setting-or-group.svg'; import figmaHelpContentForAnEntirePageOrApp from '!url-loader!./assets/help-content_for-an-entire-page-or-app.svg'; import figmaHelpContentFieldsAndSettings from '!url-loader!./assets/help-content_fields-and-settings.svg'; import figmaHelpContentAdditionalInformation from '!url-loader!./assets/help-content_additional-information.svg'; import figmaHelpContentSyntaxReferences from '!url-loader!./assets/help-content_syntax-references.svg'; import figmaHelpContentLegendsForGraphsAndTables from '!url-loader!./assets/help-content_legends-for-graphs-and-tables.svg';
Help content
Help content includes any copy added to the product experience to help users understand a concept or complete a task. It can be definitions, context, explanations, hints, references, and more.
Considering help content
Before thinking about how to display and write help content, it's important to consider the messaging, importance, scope and role of that content in the user experience.
The right content at the right time. Focus on what users need to reach their next step. They don't need to know everything from the start. If anything, too much information will confuse them. Consider what you're sure they know, and what they may not know that they need.
Be intentional. Don't push everything to the user's face. While some information is absolutely critical, some might be useful for specific cases or for a particular type of users only. Some might even just be additional context that would digress from the user's flow. Consider letting users choose to consume or not that non-critical, yet useful, content through a simple and recognizable interaction.
Anticipate errors and questions. Don't wait for errors or questions to provide guidance. We help users avoid them, and when they happen, we're here for them.
Short definitions
Defining terms is particularly useful in onboarding experiences or in the first few steps that users go through when using a feature.
Interaction
Question mark icon with the primary color directly following the term to explain.
Start the definition with the full term you are defining to leave no ambiguity.
Audience level: beginners.
Don’t include interactions inside of the tooltip (like links). If you need some or rich formatting, you're likely not just providing a simple definition and are instead explaining a concept.
No header, title, footer or call to action.
Concepts
Concepts are more than quick definitions. They help users understand how something works, why they should or shouldn't use something, and can include resources or links with further details.
For a specific setting or group
Interaction
Information icon with the primary color directly following the setting to explain.
On click, a popover appears. It is dismissed by a second click outside of the popover area.
Popover content
Header required
Be explicit about what users are about to read.
Sentence case.
No punctuation.
Body required
If necessary, structure the content into sections. It should be no more than 2 to 3 short paragraphs. If the content is longer than that, use a flyout to avoid disrupting the user from their main task.
Format keywords, code blocks, input examples, links, etc.
You can add images or gifs.
Sentence case.
Full punctuation.
Audience level: beginners.
Footer optional
For any call to action.
For an entire page or app
Note: the formatting of the flyout here is just an example.
Interaction
Use a Flyout, in push mode if possible, as overlays can be disruptive.
Locate the interaction to open the flyout somewhere visible and that matches with the context of the content:
If that is for an entire app, use the help button from the header
If that is for a significant section or area of the UI, use an explicit link near the top of the area or section or a button.
Add an explicit Close action to the footer as a way to dismiss the flyout.
Flyout content
Header
Action-oriented title.
Sentence case.
No punctuation.
Body
Structure content to help users scan.
Keep it short, it doesn't have to be the fully-detailed documentation. Keep the most important to keep it readable.
Format keywords, code blocks, input examples, etc.
You can add images or gifs.
Sentence case.
Full punctuation.
Audience: all levels.
Footer
Call to action to Close the flyout.
If necessary, add another call to action to read the full documentation.
Fields and settings
Several mechanisms exist to provide additional help around a specific field. All of these mechanisms are optional.
Hint text (shows below a field)
Use it for explaining:
the accepted syntax, the format, range or number of accepted values, or how to enter multiple values if needed.
the consequences of changing the value or enabling the setting if they're critical to know.
Hint text should be short. If the text is more than 2 lines, there might be an underlying design issue, or you need a different help mechanism like a syntax reference.
Placeholder text
Important help should show outside of a field, not as placeholder, as the information will disappear as soon as users start typing.
Use it for input examples.
Exception: in search and filter bars, use it to guide users on what they can search for or filter.
Defaults are different from placeholders and shouldn't look like placeholders. They should pre-populate the field.
Additional information
Add a tooltip or popover next to the label of a field to provide additional context on the concept or to provide usage guidance. For example, "When should I use this?".
Syntax references
Providing reference docs directly in the product can help users stay focused on filling any field requiring functions or advanced syntax.
Interaction
Use the icon with the primary color to indicate a reference. When possible, add an explicit link text.
Locate the interaction for opening the reference right next to or under the field, depending on the UI layout.
Use the same word as the link text used to open the popover.
Body
Format the content of the popover in a way that helps users scan. It can be a simple definition list, or a table with one icon and definition per row, etc.
For acronyms, make sure to repeat them as they appear on the visualization, accompanied by their full form.
Other forms of in-product help
More forms of indirect ways to help users exist in the product experience and have their own dedicated patterns. Related patterns:
Packages Website Docs Patterns Save Buttons (`packages/website/docs/patterns/save-buttons.mdx`)
sidebar_position: 5
import figmaSaveButtonsWhenShouldIUseASaveButton from '!url-loader!./assets/save-buttons_when-should-i-use-a-save-button.svg'; import figmaSaveButtonsHowDoIStyleIt from '!url-loader!./assets/save-buttons_how-do-i-style-it.svg'; import figmaSaveButtonsAvoidUsingTheDefaultFillForTheButton from '!url-loader!./assets/save-buttons_avoid-using-the-default-fill-for-the-button.svg'; import figmaSaveButtonsAvoidUsingTheSuccessColorVariant from '!url-loader!./assets/save-buttons_avoid-using-the-success-color-variant.svg'; import figmaSaveButtonsAvoidSizingTheButtonDifferentlyThanSurroundingButtonsOrFormFields from '!url-loader!./assets/save-buttons_avoid-sizing-the-button-differently-than-surrounding-buttons-or-form-fields.svg'; import figmaSaveButtonsConsiderIfAnIconIsNecessary from '!url-loader!./assets/save-buttons_consider-if-an-icon-is-necessary.svg';
Save buttons
Guidance for how to select button type, what props to use (color and size), whether to include an icon or not, what labels to use (e.g. Create vs Save), and when
When should I use a 'Save button'?
Save buttons are specific to CRUD operations. As there are obviously many places in Kibana that require creating, editing/updating something, the following are recommendations to help drive consistency.
Creating an object that can be edited / deleted
Updating or modifying settings
Consider using "Save" rather than "Update", "Create" or "Apply". Try to reserve the use of “Apply" for actions that do not permanently save data but rather temporarily apply changes.
How do I style it?
The save button should be the most dominant action and should be consistent in any context. It is recommended to follow the recommended styling to maintain a recognizable save action.
Use the fill prop
Match the sizing of form fields, but use the medium size where possible.
Use the primary color variant
Include the noun in the label.
Avoid using icons where possible
Avoid using the default fill for the button
Saving should be the primary action in the user's context, so this button should be the most dominant. If multiple buttons or options, make sure the 'Save' button uses the fill prop.
Avoid using the `success` color variant
The save button should be the most dominant action in the view. By using our primary color and fill prop, it insures we keep these consistent wherever it is placed.
Avoid sizing the button differently than surrounding buttons or form fields
We should use the medium size button by default. However, use the same size button as other buttons in the group and the associated form inputs (m for default size form inputs, s for compressed forms).
Consider if an icon is necessary
While icons can provide context and additional visual cues to buttons, a Save button by principle should be styled so it is visually dominant. Therefore, it is likely less necessary to use an icon for these buttons compared to other actions within the page that can have similar weight to nearby buttons/actions.
The choice of the icon can also be difficult here. We have used a disk icon and a check icon with Save buttons within our UIs, but the meaning of these icons can be confusing. So our recommendation is to avoid the inconsistency here and rely solely on the label.
Exceptions to the rule: As with anything, there will be exceptions to these recommendations. One example is the EuiInlineEdit component which provides a save action without a label.
Packages Website Docs Utilities Auto Sizer (`packages/website/docs/utilities/auto-sizer.mdx`)
keywords: [EuiAutoSizer]
Auto sizer
EuiAutoSizer helps components that use virtualized rendering and/or require explicit dimensions to fill all available space in the parent container. See the react-virtualized AutoSizer documentation as EuiAutoSizer is a passthrough component for AutoSizer.
```tsx
import React from 'react';
import { css } from '@emotion/react';
import { EuiAutoSizer, EuiAutoSize, EuiCode, EuiPanel, logicalSizeCSS, } from '@elastic/eui';
</Demo>
## Props
import docgen from '@elastic/eui-docgen/dist/components/auto_sizer';
<PropTable definition={docgen.EuiAutoSizer} />
---
### Packages Website Docs Utilities Container Queries (`packages/website/docs/utilities/container-queries.mdx`)
---
title: Container queries
---
import { EuiBetaBadge } from '@elastic/eui';
# Container queries <EuiBetaBadge label="Beta" color="accent" />
Container queries allow you to create styles that adapt based on the available
space within element's parent container, rather than the entire browser
viewport. This unlocks a new level of modularity, enabling designs that are
intrinsically responsive regardless of where they are placed in a layout,
and reactive to important layout changes, like expanding the page sidebar.
```tsx interactive
import React from 'react';
import { EuiText, EuiResizableContainer } from '@elastic/eui';
import { faker } from '@faker-js/faker';
const text = faker.lorem.paragraphs();
export default () => (
<EuiResizableContainer style={{ height: '200px' }}>
{(EuiResizablePanel, EuiResizableButton) => (
<>
<EuiResizablePanel initialSize={50} minSize="30%" tabIndex={0} css={euiContainerCSS('inline-size')}>
<EuiText>{text}</EuiText>
</EuiResizablePanel>
<EuiResizableButton accountForScrollbars="before" />
<EuiResizablePanel initialSize={50} minSize="200px" tabIndex={0} css={euiContainerCSS('inline-size')}>
<EuiText css={css`${euiContainerQuery('(width < 300px)')} { ${useEuiFontSize('xs')} }`}>
**This text will become small when the panel width is lower than 300px**
{text}
</EuiText>
</EuiResizablePanel>
</>
)}
</EuiResizableContainer>
);
Overview
For years, responsive web design relied heavily on media queries. These allow us to adjust styles based on the global viewport size (e.g., "if the screen is wider than 768px, do this"). While effective for page-level layouts, media queries often fall short when building reusable components, or building component organisms. An EuiCard or EuiBasicTable, for example, might need to look different when placed in a narrow flyout versus a wide main content area, even if the viewport size remains the same.
Container queries solve this by moving the context from the screen to the element:
Media Queries: "How wide is the user's screen?"
Container Queries: "How much space do I have in this specific container?"
By querying the container, you can build self-contained components that "own" their responsiveness. This reduces the need for complex grid classes or layout-specific overrides.
Usage
EUI ships with container query utilities that make the process of defining containers and creating container queries seamless.
1. Define the container
To use container queries, you must first establish a containment context on a parent element to tell the browser which element should act as the reference point for size calculations.
We provide two utility functions to help with that:
euiContainer(type: ContainerType, name?: string, scrollState?: boolean) - to be used within Emotion template tags
euiContainerCSS(type: ContainerType, name?: string, scrollState?: boolean) - to be passed directly to the css prop
2. Query the container
Once a container is established, you can use the euiContainerQuery utility to define container queries and apply styles to its children. The approach is very similar to media queries and the breakpoint EUI utilities like euiMinBreakpoint():
conditions: string: one or many conditions to query the container with. Similarly to media queries, you can use size queries (e.g., (width > 300px)), scroll state queries (e.g., (scroll-state(scrollable: top))) or even style queries. You can use the and, or and not logical keywords to define container conditions. Note that all conditions must be wrapped in parentheses.
containerName?: string: When provided, it will be used to target the containment context and run queries against it. Otherwise, the nearest ancestor with containment will be queried instead.
Querying containers using JS
:::warning For performance reasons, using container queries in JS is not recommended. ::: All layout transforms related to container query changes should be handled directly in CSS (or CSS-in-JS) whenever possible. This ensures good performance and omits React tree rerenders caused by viewport or container resizes, which are the major cause of the application feeling sluggish.
That being said, we're aware that sometimes it is necessary to access container queries from JS land, and we provide a (relatively) performant way to do so - the useEuiContainerQuery() hook.
What's important to point out is that using the hook requires passing a special ref prop provided by it to the element you want to query, so that the calculation can be done on the right element.
```tsx
import React from 'react';
import { useEuiContainerQuery, euiContainerCSS, EuiCodeBlock } from '@elastic/eui';
return (
<EuiResizableContainer>
{(EuiResizablePanel, EuiResizableButton) => (
<>
<EuiResizablePanel initialSize="50" css={euiContainerCSS('inline-size')}>
<EuiCodeBlock language="jsx" fontSize="m" paddingSize="m" whiteSpace="pre">
{`/* true when width > 300px */\nmatches = ${matches ? 'true' : 'false'}`}
</EuiCodeBlock>
</EuiResizablePanel>
<EuiResizableButton accountForScrollbars="before" />
<EuiResizablePanel initialSize="50">
Resize the resizable panel to see the left side content changing.
</EuiResizablePanel>
</>
)}
</EuiResizableContainer>
);
};
text
</Demo>
---
### Packages Website Docs Utilities Copy (`packages/website/docs/utilities/copy.mdx`)
---
keywords: [EuiCopy]
---
# Copy
The **EuiCopy** component is a utility for copying text to clipboard. Wrap a function that returns a component. The first argument will be a `copy` function.
<Demo>
```tsx interactive
import React, { useState } from 'react';
import {
EuiCopy,
EuiButton,
EuiFieldText,
EuiSpacer,
EuiFormRow,
} from '@elastic/eui';
export default () => {
const [copyText, setCopyText] = useState('I am the text that will be copied');
const onChange = (e) => {
setCopyText(e.target.value);
};
return (
<EuiFormRow label="Enter text that will be copied to clipboard">
<EuiFieldText value={copyText} onChange={onChange} />
</EuiFormRow>
<EuiSpacer size="m" />
<EuiCopy textToCopy={copyText}>
{(copy) => (
<EuiButton onClick={copy}>Click to copy input text</EuiButton>
)}
</EuiCopy>
);
};
Copy to clipboard function
The function copyToClipboard allows you to copy text to the clipboard. It receives an argument of type string.
```
/* Detailed source-code truncated for AI context efficiency. */
```
Props
import docgen from '@elastic/eui-docgen/dist/components/copy';
EuiDelayRender is a component for conditionally toggling the visibility of a child component. It will ensure that the child is hidden for at least 500ms (default). This allows delay UI rendering. That is helpful when you need to update aria live region(s) repeatedly.
```tsx interactive
import React, { useState, Fragment } from 'react';
</Demo>
## Delay hide
**EuiDelayHide** is a component for conditionally toggling the visibility of a child component. It will ensure that the child is visible for at least 1000ms (default). This avoids UI glitches that are common with loading spinners and other elements that are rendered conditionally and potentially for a short amount of time.
<Demo>
```tsx interactive
import React, { useState, Fragment } from 'react';
import {
EuiDelayHide,
EuiFlexItem,
EuiCheckbox,
EuiFormRow,
EuiFieldNumber,
EuiLoadingSpinner,
} from '@elastic/eui';
export default () => {
const [minimumDuration, setDuration] = useState(3000);
const [hide, setHide] = useState(false);
const onChangeMinimumDuration = (event) => {
setDuration(parseInt(event.target.value, 10));
};
const onChangeHide = (event) => {
setHide(event.target.checked);
};
return (
<Fragment>
<EuiFlexItem>
<EuiFormRow>
<EuiCheckbox
id="dummy-id"
checked={hide}
onChange={onChangeHide}
label="Hide child"
/>
</EuiFormRow>
<EuiFormRow label="Minimum duration">
<EuiFieldNumber
value={minimumDuration}
onChange={onChangeMinimumDuration}
/>
</EuiFormRow>
<EuiFormRow label="Child to render">
<EuiDelayHide
hide={hide}
minimumDuration={minimumDuration}
render={() => <EuiLoadingSpinner size="m" />}
/>
</EuiFormRow>
</EuiFlexItem>
</Fragment>
);
};
Props
import delayRenderDocgen from '@elastic/eui-docgen/dist/components/delay_render'; import delayHideDocgen from '@elastic/eui-docgen/dist/components/delay_hide';
Use EuiErrorBoundary to prevent errors from taking down the entire app.
Ideally end-users should never see an actual error boundary rendered. If a React component can throw errors during rendering, those should be caught internally and the component should render specific error messages that help the user understand what happened and how to correct it. See the Error messages pattern for more guidance.
EuiErrorBoundary should only be used to surround components at a high level, where we don't know how lower-level code works and we don't trust it to catch all of its own errors. Wrapping lazy-loaded components in boundaries is a good practice.
In Kibana, we try to preserve the integrity of server-side data by not allowing the user to make state changes using a UI with an active error state; for this reason, the error boundary should cover up UI controls that trigger updates to server-side data. Or the controls should be hidden some other way, like having the component be aware of when it's in an error state.
:::note Kibana engineers
All error boundaries should have the same look and feel in Kibana, and for that reason it's not recommended to use EuiErrorBoundary at all. Use KibanaErrorBoundary instead
:::
```tsx interactive
import React from 'react';
import { EuiErrorBoundary } from '@elastic/eui';
const BadComponent = () => { throw new Error( "I'm here to kick butt and chew bubblegum. And I'm all out of gum." ); };
export default () => (
);
```
Props
import docgen from '@elastic/eui-docgen/dist/components/error_boundary';
Packages Website Docs Utilities Highlight And Mark (`packages/website/docs/utilities/highlight-and-mark.mdx`)
keywords: [EuiHighlight, EuiMark]
Highlight and mark
Highlight
Use EuiHighlight to highlight substrings within a string, typically in response to user input.
```
/* Detailed source-code truncated for AI context efficiency. */
```
Mark
Use EuiMark to wrap a string in a mark element.
```tsx interactive
import React from 'react';
import { EuiMark } from '@elastic/eui';
export default () => { return ( <> The quick brown fox jumped over the lazy dog </> ); };
text
</Demo>
## Props
import highlightDocgen from '@elastic/eui-docgen/dist/components/highlight';
import markDocgen from '@elastic/eui-docgen/dist/components/mark';
<PropTable definition={highlightDocgen.EuiHighlight} />
<PropTable definition={markDocgen.EuiMark} />
---
### Packages Website Docs Utilities Html Id Generator (`packages/website/docs/utilities/html-id-generator.mdx`)
---
keywords: [htmlIdGenerator]
---
# HTML ID generator
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.
<Demo>
```tsx interactive
import React, { useState, Fragment } from 'react';
import {
EuiFlexGroup,
EuiFlexItem,
EuiButton,
EuiCode,
htmlIdGenerator,
} from '@elastic/eui';
export default () => {
const [value, setValue] = useState(htmlIdGenerator()());
const reGenerate = () => {
setValue(htmlIdGenerator()());
};
return (
<Fragment>
<EuiFlexGroup
justifyContent="flexStart"
gutterSize="m"
alignItems="center"
>
<EuiFlexItem grow={false}>
<EuiCode>{value}</EuiCode>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiButton onClick={reGenerate}>Regenerate</EuiButton>
</EuiFlexItem>
</EuiFlexGroup>
</Fragment>
);
};
ID generator with prefix
Provide a prefix to the generator to get an ID that starts with the specified prefix.
```tsx interactive
import React, { useState, Fragment } from 'react';
</Demo>
## Reusing the generator for multiple IDs
As you may have noticed, `htmlIdGenerator` is a curried function. This means you can reuse the original `htmlIdGenerator()` call to generate multiple IDs. Additionally, if you pass in suffixes to your second call, the generated ID(s) will share the same unique ID.
<Demo>
```tsx interactive
import React from 'react';
import { EuiCode, htmlIdGenerator } from '@elastic/eui';
const generateId = htmlIdGenerator('test');
export default () => {
return (
<>
<EuiCode>{generateId()}</EuiCode>
<EuiCode>{generateId()}</EuiCode>
<EuiCode>{generateId()}</EuiCode>
<EuiCode>{generateId('hello')}</EuiCode>
<EuiCode>{generateId('world')}</EuiCode>
</>
);
};
Memoized hook for component use
useGeneratedHtmlId is a custom React hook that automatically memoizes a randomly generated ID, preventing the ID from regenerating on every component rerender. The ID will only change if the component fully unmounts/mounts, or if you dynamically pass in new hook arguments.
Please note that unlike htmlIdGenerator, useGeneratedHtmlId is a single function and does not support generating multiple IDs that share the same unique ID. It is instead best used for simple one-off IDs, rather than groups of them.
```tsx interactive
import React, { useState, Fragment } from 'react';
import { EuiSwitch, EuiSpacer, EuiCode, useGeneratedHtmlId } from '@elastic/eui';