数据分析 · 新资源

Elastic UI 设计系统.

构建一个 Elastic UI 日志浏览器,采用经过验证的 Borealis 尺度、原生表格和搜索控件、严重程度筛选、本地保存的事件,以及完整的 React 源代码。

OpenDesign 指南8 个章节43 个参考条目原始应用指南
01

Elastic UI 设计系统界面预览

按消息或严重级别筛选事件流,保存单条事件,清空筛选结果,并切换浅色或深色外观。

搜索并保存示例日志事件打开完整示例
原生 Elastic UI 123.0.0 / Borealis 8.1.0,搭配 React 18.3.1。日志数据和应用状态均为本地原创。· 实现参考
02

Elastic UI 设计系统设计参考

Elastic UI 123.0.0,搭配 Borealis 主题 8.1.0 和 React 18.3.1。表格使用已发布主题 JSON 中的名称;这些是导出键,并非 useEuiTheme 对象中的实际路径。实际预览使用原生的 React 主题 API。

Borealis 语义颜色

来源
名称 / 规则值 / 配置用途
euiColorPrimarylight: #0B64DD; dark: #61A2FF已发布的主题导出名称。在 React 中,应通过 useEuiTheme 使用对应的语义角色,而不是硬编码这些值。
euiColorSuccesslight: #008A5E; dark: #24C292已发布的主题导出名称。在 React 中,应通过 useEuiTheme 使用对应的语义角色,而不是硬编码这些值。
euiColorWarninglight: #FACB3D; dark: #FACB3D已发布的主题导出名称。在 React 中,应通过 useEuiTheme 使用对应的语义角色,而不是硬编码这些值。
euiColorDangerlight: #C61E25; dark: #F6726A已发布的主题导出名称。在 React 中,应通过 useEuiTheme 使用对应的语义角色,而不是硬编码这些值。
euiColorEmptyShadelight: #FFFFFF; dark: #111C2C已发布的主题导出名称。在 React 中,应通过 useEuiTheme 使用对应的语义角色,而不是硬编码这些值。
euiColorFullShadelight: #07101F; dark: #FFFFFF已发布的主题导出名称。在 React 中,应通过 useEuiTheme 使用对应的语义角色,而不是硬编码这些值。
euiPageBackgroundColorlight: #ECF1F9; dark: #07101F已发布的主题导出名称。在 React 中,应通过 useEuiTheme 使用对应的语义角色,而不是硬编码这些值。
euiTextColorlight: #384861; dark: #B4C1D5已发布的主题导出名称。在 React 中,应通过 useEuiTheme 使用对应的语义角色,而不是硬编码这些值。
euiTextSubduedColorlight: #516381; dark: #98A8C3已发布的主题导出名称。在 React 中,应通过 useEuiTheme 使用对应的语义角色,而不是硬编码这些值。

间距比例

来源
名称 / 规则值 / 配置用途
euiSizeXS4px使用尺寸相匹配的 EuiSpacer/FlexGroup 营造布局节奏;在此主题导出中,基准值为 16px。
euiSizeS8px使用尺寸相匹配的 EuiSpacer/FlexGroup 营造布局节奏;在此主题导出中,基准值为 16px。
euiSizeM12px使用尺寸相匹配的 EuiSpacer/FlexGroup 营造布局节奏;在此主题导出中,基准值为 16px。
euiSize16px使用尺寸相匹配的 EuiSpacer/FlexGroup 营造布局节奏;在此主题导出中,基准值为 16px。
euiSizeL24px使用尺寸相匹配的 EuiSpacer/FlexGroup 营造布局节奏;在此主题导出中,基准值为 16px。
euiSizeXL32px使用尺寸相匹配的 EuiSpacer/FlexGroup 营造布局节奏;在此主题导出中,基准值为 16px。
euiSizeXXL40px使用尺寸相匹配的 EuiSpacer/FlexGroup 营造布局节奏;在此主题导出中,基准值为 16px。

排版

来源
名称 / 规则值 / 配置用途
euiFontSizeXS12px已发布的字体参考。请使用 EuiText/EuiTitle 变体构建组件层级。
euiFontSizeS14px已发布的字体参考。请使用 EuiText/EuiTitle 变体构建组件层级。
euiFontSize14px已发布的字体参考。请使用 EuiText/EuiTitle 变体构建组件层级。
euiFontSizeM16px已发布的字体参考。请使用 EuiText/EuiTitle 变体构建组件层级。
euiFontSizeL20px已发布的字体参考。请使用 EuiText/EuiTitle 变体构建组件层级。
euiFontSizeXL24px已发布的字体参考。请使用 EuiText/EuiTitle 变体构建组件层级。
euiFontSizeXXL30px已发布的字体参考。请使用 EuiText/EuiTitle 变体构建组件层级。
euiLineHeight1.5已发布的字体参考。请使用 EuiText/EuiTitle 变体构建组件层级。

边框与形状

来源
名称 / 规则值 / 配置用途
euiBorderWidthThin1px区分行内元素、控件、面板和外框的形状;若所有元素都使用同一个圆角半径,就会抹去这种层级差异。
euiBorderWidthThick2px区分行内元素、控件、面板和外框的形状;若所有元素都使用同一个圆角半径,就会抹去这种层级差异。
euiBorderColor#E3E8F2 区分行内元素、控件、面板和外框的形状;若所有元素都使用同一个圆角半径,就会抹去这种层级差异。
euiBorderRadiusInline4px区分行内元素、控件、面板和外框的形状;若所有元素都使用同一个圆角半径,就会抹去这种层级差异。
euiBorderRadiusControl8px区分行内元素、控件、面板和外框的形状;若所有元素都使用同一个圆角半径,就会抹去这种层级差异。
euiBorderRadiusPanel12px区分行内元素、控件、面板和外框的形状;若所有元素都使用同一个圆角半径,就会抹去这种层级差异。
euiBorderRadiusFrame16px区分行内元素、控件、面板和外框的形状;若所有元素都使用同一个圆角半径,就会抹去这种层级差异。

焦点与动效

来源
名称 / 规则值 / 配置用途
euiFocusRingSize2px保留原生焦点和动效行为,不要覆盖内部的 .eui 选择器。
euiFocusBackgroundColor#ECF1F9 保留原生焦点和动效行为,不要覆盖内部的 .eui 选择器。
euiAnimSpeedExtraFast90ms保留原生焦点和动效行为,不要覆盖内部的 .eui 选择器。
euiAnimSpeedFast150ms保留原生焦点和动效行为,不要覆盖内部的 .eui 选择器。
euiAnimSpeedNormal250ms保留原生焦点和动效行为,不要覆盖内部的 .eui 选择器。
euiAnimSpeedSlow350ms保留原生焦点和动效行为,不要覆盖内部的 .eui 选择器。

此浏览器中原生 API 的约定

来源
名称 / 规则值 / 配置用途
EuiProvidercolorMode="light" | "dark"提供主题上下文和 Emotion 样式。
useEuiTheme{ euiTheme }为自定义元素和 EuiHealth 解析语义颜色。
EuiBasicTableitems · itemId · columns使用稳定 ID 渲染受控的可见集合。
EuiFieldSearchvalue · onChange · incremental应用程序会筛选包含六个事件的本地集合。
EuiButtonGroupoptions · idSelected · onChange受控的严重性筛选器,并显示图例。
EuiHealthcolor + readable child label状态圆点用于补充严重性文字,而非取而代之。
03

Elastic UI 设计系统完整指南

实用指南,介绍如何为真实界面选择、应用和评审此系统。

DESIGN-elastic-ui.md

完整指导、实现起点和源代码链接。

下载指南

Elastic UI 设计系统浏览器结构

调查工作区,而非通用仪表板

此示例包含三项精简摘要统计数据和一个事件流。搜索框和严重性筛选组可缩小事件流范围;每行都提供独立的保存操作。将操作放在对应事件旁边,读者便能在不离开筛选上下文的情况下收集证据。摘要明确统计的是示例事件,而非实时流量。

摘自本 OpenDesign 指南

使用当前主题边界

此示例搭配 EUI 123.0.0 和 Borealis 8.1.0。参考值取自已发布的主题,而非旧版 Amsterdam 截图。EuiProvider 管理视觉模式和样式上下文。应用状态独立于主题计算,因此更改外观不会重置已保存的事件或当前查询。

Elastic UI 设计系统语义颜色角色

让严重性与颜色角色建立有意义的对应关系

在此应用中,Info 使用 primary 角色,Warning 使用 warning 角色,Error 使用 danger 角色。EuiHealth 会在圆点旁显示严重性文字。此映射属于应用策略,并不意味着每个主色元素都表示信息严重性。错误数量和事件标签仍需使用易读的文字。

根据当前主题解析颜色

Screen 会调用 useEuiTheme,并使用解析出的颜色设置画布、正文文字和健康状态指示器。导出表格列出了成对的浅色和深色模式值,以便清楚地呈现差异。不要在两种模式中复制使用同一个十六进制值,也不要通过定位生成的 Emotion 类名来强行匹配。

Elastic UI 设计系统中的字体与数值层级

先为统计数据说明背景,再突出数字

EuiStat 将 6 个示例事件、1 个错误事件和已保存数量与相应描述配对。单独的数字可能含义不明。EuiTitle 用于引出调查内容,EuiText 则提供时间范围并说明数据为示例数据。事件流标题使用较小级别的标题,而不是第二个页面标题。

在紧凑布局中保持日志文字易读

Borealis 的基础导出字号为 14px,行高参考值为 1.5,并提供 12/16/20/24/30px 的字号阶梯。时间戳和服务名称使用固定列宽,事件消息则灵活占用剩余空间。此示例通过原生字体栈使用系统可用字体,不声称加载了远程 Inter 字体资源。

Elastic UI 设计系统基础表格与受控筛选器

将展示与筛选分开处理

EuiBasicTable 接收已筛选的项目。列定义指定字段或自定义渲染器,itemId 则使用稳定的事件 ID。EuiFieldSearch 用于更新查询状态;EuiButtonGroup 用于更新所选严重性 ID。应用程序会先组合两个筛选条件,再将可见集合传给表格。

  1. 输入服务名称或消息片段。
  2. 选择 Error 或 Warning 以组合严重性筛选条件。
  3. 使用对应行的操作保存匹配事件。
  4. 清除筛选器,确认已保存的事件仍有标记。

根据事件 ID 保存,而非根据行位置

Elastic UI 设计系统间距与响应式数据

在各个分组中使用原生间距比例

已发布的间距尺寸为 4、8、12、16、24、32 和 40px。EuiSpacer 和 EuiFlexGroup 用于设置摘要统计、筛选器与事件表格之间的间距。EuiPanel 提供容器形状;主题将面板的 12px 圆角与控件的 8px 圆角及框架的 16px 圆角区分开来。

让页面适配移动设备视口

摘要和筛选器分组会通过原生 FlexGroup 行为重新排列。表格位于有界的水平滚动区域中,应用最小宽度为 640px;这样既能保持时间戳、消息与操作之间的对应关系,也能避免整个页面溢出。生产环境中的表格可根据实际列设置,选择文档中说明的响应式表格选项。

Elastic UI 设计系统中的搜索、空状态与已保存状态

无结果搜索应提供直接的恢复操作

当查询不可能匹配任何结果时,显示“没有事件符合当前筛选条件”以及“清除筛选器”操作。重置查询和严重性筛选器后,六个示例事件会全部恢复,已保存的 ID 不受影响。表格上方的数量会通过礼貌型实时区域播报匹配事件总数。

本地保存不等同于事件工作流

保存事件会更改可见行的标签和已保存统计数据,但不会联系 Elasticsearch、创建事件或在刷新后保留数据。真实的调查产品应添加有文档说明的持久化端点,并在声称已保存集合能够持久保留之前,报告待处理或失败的响应。

Elastic UI 设计系统中的无障碍表格操作

为筛选器设置独立名称,不要只依赖占位符

搜索框具有无障碍名称,按钮组则提供严重性图例。表格列具有描述性标题;每行操作都是原生的 EuiButtonEmpty。EuiHealth 包含文字,因此状态颜色绝不会成为唯一信息。替换示例数据时,请保留这些约定。

  • 使用可见文字标明严重性。
  • 保留表格说明和列名称。
  • 让已保存状态与稳定的事件 ID 关联。

确保键盘操作状态与视觉状态一致

用户可以将焦点移至搜索框、选择严重性筛选器、访问行操作,并切换外观设置。2px 焦点环属于主题参考的一部分。保存一行后以及清除筛选器后,都应测试筛选结果;操作标签和已保存统计数据应始终保持一致。

Elastic UI 设计系统设置与主题集成

安装文档中列出的对等依赖

官方安装指南使用 Yarn,并将 Borealis、Emotion、datemath 和 moment 与 EUI 一同列出。本示例运行于 React 18,且不会将组件树包裹在 StrictMode 中。以下设置明确保留这些约束,而不是不加说明地继承新生成应用中的 React 19。

将完整入口文件作为实现

Elastic UI 设计系统组件示例

main.jsx — 完整的原生日志浏览器

EuiProvider 和 Borealis 支持受控事件表格,并提供严重级别/搜索筛选以及本地已保存事件状态。

jsx
import React, { useState } from "react";
import { createRoot } from "react-dom/client";
import {
  EuiProvider,
  EuiPanel,
  EuiTitle,
  EuiText,
  EuiSpacer,
  EuiFlexGroup,
  EuiFlexItem,
  EuiFieldSearch,
  EuiButtonGroup,
  EuiButtonEmpty,
  EuiBasicTable,
  EuiHealth,
  EuiStat,
  EuiSwitch,
  EuiCallOut,
  useEuiTheme,
} from "@elastic/eui";
const logs = [
  {
    id: 1,
    time: "09:42:18",
    service: "gateway",
    message: "Request completed",
    level: "Info",
    duration: 42,
  },
  {
    id: 2,
    time: "09:42:21",
    service: "worker",
    message: "Retry queued after timeout",
    level: "Warning",
    duration: 1200,
  },
  {
    id: 3,
    time: "09:42:24",
    service: "gateway",
    message: "Upstream connection refused",
    level: "Error",
    duration: 800,
  },
  {
    id: 4,
    time: "09:42:29",
    service: "indexer",
    message: "Batch indexed",
    level: "Info",
    duration: 180,
  },
  {
    id: 5,
    time: "09:42:32",
    service: "worker",
    message: "Job completed",
    level: "Info",
    duration: 94,
  },
  {
    id: 6,
    time: "09:42:36",
    service: "gateway",
    message: "Upstream connection restored",
    level: "Info",
    duration: 51,
  },
];
function Screen({ dark, setDark }) {
  const { euiTheme } = useEuiTheme();
  const [query, setQuery] = useState(""),
    [level, setLevel] = useState("all"),
    [saved, setSaved] = useState([]);
  const visible = logs.filter(
    (x) =>
      (level === "all" || x.level === level) &&
      (x.service + " " + x.message).toLowerCase().includes(query.toLowerCase()),
  );
  const colors = {
    Info: euiTheme.colors.primary,
    Warning: euiTheme.colors.warning,
    Error: euiTheme.colors.danger,
  };
  const columns = [
    { field: "time", name: "Time", width: "105px" },
    { field: "service", name: "Service", width: "105px" },
    {
      field: "message",
      name: "Event",
      render: (value, item) => (
        <span>
          {value}
          {saved.includes(item.id) ? " · Saved locally" : ""}
        </span>
      ),
    },
    {
      field: "level",
      name: "Level",
      width: "110px",
      render: (value) => <EuiHealth color={colors[value]}>{value}</EuiHealth>,
    },
    {
      name: "Action",
      width: "110px",
      render: (item) => (
        <EuiButtonEmpty
          size="xs"
          onClick={() =>
            setSaved((v) =>
              v.includes(item.id)
                ? v.filter((id) => id !== item.id)
                : [...v, item.id],
            )
          }
        >
          {saved.includes(item.id) ? "Unsave" : "Save event"}
        </EuiButtonEmpty>
      ),
    },
  ];
  return (
    <main
      style={{
        padding: 24,
        background: euiTheme.colors.backgroundBasePlain,
        color: euiTheme.colors.textParagraph,
      }}
    >
      <EuiFlexGroup justifyContent="spaceBetween" alignItems="center">
        <EuiFlexItem>
          <EuiText size="xs">
            <p>ELASTIC UI · LOG EXPLORER</p>
          </EuiText>
          <EuiTitle size="m">
            <h1>Investigate gateway events</h1>
          </EuiTitle>
        </EuiFlexItem>
        <EuiFlexItem grow={false}>
          <EuiSwitch
            label="Dark mode"
            checked={dark}
            onChange={(e) => setDark(e.target.checked)}
          />
        </EuiFlexItem>
      </EuiFlexGroup>
      <EuiSpacer size="l" />
      <EuiFlexGroup>
        <EuiFlexItem>
          <EuiPanel>
            <EuiStat title="6" description="Sample events" titleSize="m" />
          </EuiPanel>
        </EuiFlexItem>
        <EuiFlexItem>
          <EuiPanel>
            <EuiStat
              title="1"
              description="Error event"
              titleSize="m"
              titleColor="danger"
            />
          </EuiPanel>
        </EuiFlexItem>
        <EuiFlexItem>
          <EuiPanel>
            <EuiStat
              title={String(saved.length)}
              description="Saved in this preview"
              titleSize="m"
            />
          </EuiPanel>
        </EuiFlexItem>
      </EuiFlexGroup>
      <EuiSpacer size="l" />
      <EuiPanel>
        <EuiTitle size="xs">
          <h2>Event stream</h2>
        </EuiTitle>
        <EuiText size="s">
          <p>09:42:18–09:42:36 · fictional data · no live telemetry</p>
        </EuiText>
        <EuiSpacer size="m" />
        <EuiFlexGroup alignItems="center">
          <EuiFlexItem>
            <EuiFieldSearch
              fullWidth
              aria-label="Search log events"
              placeholder="Search service or message"
              value={query}
              onChange={(e) => setQuery(e.target.value)}
              incremental
            />
          </EuiFlexItem>
          <EuiFlexItem grow={false}>
            <EuiButtonGroup
              legend="Filter severity"
              options={[
                { id: "all", label: "All" },
                { id: "Warning", label: "Warning" },
                { id: "Error", label: "Error" },
              ]}
              idSelected={level}
              onChange={(id) => setLevel(id)}
            />
          </EuiFlexItem>
        </EuiFlexGroup>
        <EuiSpacer size="m" />
        <EuiText size="xs">
          <p aria-live="polite">{visible.length} matching events</p>
        </EuiText>
        <div style={{ overflowX: "auto", maxWidth: "100%" }}>
          <div style={{ minWidth: 640 }}>
            <EuiBasicTable
                  responsiveBreakpoint={false}
              items={visible}
              itemId="id"
              columns={columns}
              tableCaption="Fictional application events"
              noItemsMessage="No events match the current filters."
            />
          </div>
        </div>
        {!visible.length && (
          <EuiButtonEmpty
            onClick={() => {
              setLevel("all");
              setQuery("");
            }}
          >
            Clear filters
          </EuiButtonEmpty>
        )}
      </EuiPanel>
      <EuiSpacer size="l" />
      <EuiCallOut size="s" title="Local investigation workspace">
        Saving an event only changes this preview. No query is sent to
        Elasticsearch and no incident is created.
      </EuiCallOut>
      <EuiSpacer size="m" />
      <EuiText size="xs">
        <p>
          Native Elastic UI 123.0.0 · Borealis 8.1.0 theme · controlled filters
          and table actions.
        </p>
      </EuiText>
    </main>
  );
}
function App() {
  const [dark, setDark] = useState(false);
  return (
    <EuiProvider colorMode={dark ? "dark" : "light"}>
      <Screen dark={dark} setDark={setDark} />
    </EuiProvider>
  );
}
createRoot(document.getElementById("root")).render(<App />);

代码参考

  • 在 React 脚手架中保存为 src/main.jsx。此入口不会启用 StrictMode。
  • 640px 的表格限定宽度属于应用层 CSS;所有组件视觉样式均由 EUI 提供。

与主题联动的自定义说明

在 provider 内使用当前主题,避免自定义标记依赖生成的组件类名。

jsx
import React from 'react';
import { useEuiTheme } from '@elastic/eui';
export function InvestigationNote({ children }) {
  const { euiTheme } = useEuiTheme();
  return <aside style={{ padding: euiTheme.size.m, color: euiTheme.colors.textParagraph, borderLeft: `2px solid ${euiTheme.colors.primary}` }}>{children}</aside>;
}

代码参考

  • 在 EuiProvider 下渲染 InvestigationNote。说明会继承所选的视觉模式。
04

Elastic UI 设计系统 Agent 设置

实现起点

EUI 官方指南说明了 Yarn 安装方式及对 React 18 的支持。非 Webpack 打包器存在文档所述的注意事项;此原生示例使用 esbuild 打包并经过浏览器测试。请使用脚手架支持的 Node.js 版本。

sh
yarn create vite elastic-investigation --template react
cd elastic-investigation
yarn add [email protected] [email protected] @elastic/[email protected] @elastic/[email protected] @elastic/datemath @emotion/react @emotion/css moment
# Replace src/main.jsx with the complete example above.
yarn dev

适用于你项目的提示词

Agent 提示词
使用 Elastic UI 123.0.0、Borealis 8.1.0 和 React 18 构建日志调查界面。阅读提供的主题导出文件和原生源代码。使用 EuiProvider、受控的 EuiBasicTable、无障碍搜索和严重级别筛选。按稳定的事件 ID 保存,在筛选时保留书签,并明确区分本地 UI 状态与真实的 Elasticsearch 查询或持久化响应。确保表格在移动设备上保持在容器内。
05

Elastic UI 设计系统来源与范围

由 OpenDesign 编写的指南,以及使用 Borealis 8.1.0 和原生 Elastic UI 123.0.0 构建的虚构本地日志浏览器。示例事件并非实时遥测数据,保存操作也仅保存在内存中。EUI 有其自身的 Elastic/SSPL 许可选择;请根据你的发行方式查阅官方软件包许可证。