From 6553ebbdd58022740d7719145c7ba9ea194e1dd8 Mon Sep 17 00:00:00 2001
From: Drew Tate <drew.tate@elastic.co>
Date: Tue, 13 Jun 2023 20:09:01 -0500
Subject: [PATCH] [Lens][Visualizations] library annotation groups listing page
 (#157988)

---
 .github/CODEOWNERS                            |   4 +-
 .../public/examples/msearch/msearch_app.tsx   |   2 +-
 .../public/examples/msearch/msearch_table.tsx |   4 +-
 .../content_management_examples/tsconfig.json |   5 +-
 package.json                                  |   4 +-
 .../tabbed_table_list_view/README.mdx         |  20 +
 .../tabbed_table_list_view/index.ts           |  11 +
 .../tabbed_table_list_view/jest.config.js     |  13 +
 .../tabbed_table_list_view/kibana.jsonc       |   5 +
 .../tabbed_table_list_view/package.json       |   6 +
 .../tabbed_table_list_view/src/index.ts       |  13 +
 .../src/tabbed_table_list_view.test.tsx       | 128 ++++
 .../src/tabbed_table_list_view.tsx            |  88 +++
 .../tabbed_table_list_view/tsconfig.json      |  27 +
 .../README.mdx                                |   0
 .../table_list_view/index.ts                  |  12 +
 .../jest.config.js                            |   2 +-
 .../kibana.jsonc                              |   2 +-
 .../package.json                              |   4 +-
 .../src/table_list_view.stories.tsx           |  14 +-
 .../table_list_view/src/table_list_view.tsx   | 131 ++++
 .../table_list_view/tsconfig.json             |  25 +
 .../table_list_view_table/README.mdx          |  20 +
 .../index.ts                                  |   5 +-
 .../table_list_view_table/jest.config.js      |  13 +
 .../table_list_view_table/kibana.jsonc        |   5 +
 .../table_list_view_table/package.json        |   6 +
 .../src/__jest__/index.ts                     |   0
 .../src/__jest__/tests.helpers.tsx            |   0
 .../src/actions.ts                            |   2 +-
 .../src/components/confirm_delete_modal.tsx   |   0
 .../src/components/index.ts                   |   0
 .../src/components/item_details.tsx           |   4 +-
 .../src/components/listing_limit_warning.tsx  |   0
 .../src/components/table.tsx                  |  14 +-
 .../src/components/table_sort_select.tsx      |   2 +-
 .../src/components/tag_badge.tsx              |   0
 .../src/components/tag_filter_panel.tsx       |   0
 .../src/components/updated_at_field.tsx       |   0
 .../src/components/use_tag_filter_panel.tsx   |   0
 .../src/constants.ts                          |   0
 .../src/index.ts                              |   6 +-
 .../src/mocks.tsx                             |   2 +-
 .../src/reducer.tsx                           |   2 +-
 .../src/services.tsx                          |   0
 .../src/table_list_view.test.tsx              | 140 +++-
 .../src/table_list_view_table.tsx}            | 171 +++--
 .../src/types.ts                              |   0
 .../src/use_tags.ts                           |   2 +-
 .../src/use_url_state.ts                      |   0
 .../tsconfig.json                             |   0
 packages/kbn-dom-drag-drop/README.md          |  13 +-
 packages/kbn-optimizer/limits.yml             |   2 +-
 .../dashboard_listing.test.tsx                |  15 +-
 .../dashboard_listing/dashboard_listing.tsx   |   8 +-
 src/plugins/dashboard/tsconfig.json           |   5 +-
 src/plugins/event_annotation/.i18nrc.json     |   6 +
 .../event_annotation/common/constants.ts      |   4 +
 .../common/create_copied_annotation.ts        |  24 +
 .../common/fetch_event_annotations/utils.ts   |   2 +-
 src/plugins/event_annotation/common/index.ts  |  14 +-
 .../common/manual_event_annotation/index.ts   |  22 +
 .../query_point_event_annotation/index.ts     |  20 +
 src/plugins/event_annotation/common/types.ts  |  10 +-
 src/plugins/event_annotation/jest.config.js   |   7 +-
 src/plugins/event_annotation/kibana.jsonc     |  15 +-
 .../group_editor_flyout.test.tsx.snap         |  38 +
 .../annotation_editor_controls.tsx            | 390 ++++++++++
 .../annotation_editor_controls/helpers.ts     |  93 +++
 .../annotation_editor_controls/icon_set.ts    | 111 +++
 .../annotation_editor_controls}/index.scss    |   0
 .../annotation_editor_controls/index.test.tsx | 435 ++++++++++++
 .../annotation_editor_controls/index.tsx      |  20 +
 .../manual_annotation_panel.tsx               |  37 +-
 .../query_annotation_panel.tsx                |  55 +-
 .../range_annotation_panel.tsx                |  46 +-
 .../tooltip_annotation_panel.tsx              |  33 +-
 .../annotation_editor_controls/types.ts       |  13 +
 .../components/get_annotation_accessor.ts     |  32 +
 .../group_editor_controls/annotation_list.tsx | 132 ++++
 .../get_annotation_accessor.ts                |  32 +
 .../group_editor_controls.test.tsx            | 235 ++++++
 .../group_editor_controls.tsx                 | 212 ++++++
 .../components/group_editor_controls/index.ts |   9 +
 .../components/group_editor_flyout.test.tsx   | 134 ++++
 .../public/components/group_editor_flyout.tsx | 146 ++++
 .../public/components/index.ts                |  13 +
 .../public/components/table_list.test.tsx     | 222 ++++++
 .../public/components/table_list.tsx          | 205 ++++++
 .../__snapshots__/service.test.ts.snap        |  77 +-
 .../event_annotation_service/helpers.ts       |   8 -
 .../event_annotation_service/service.test.ts  | 160 ++---
 .../event_annotation_service/service.tsx      | 143 +++-
 .../public/event_annotation_service/types.ts  |   9 +
 .../public/get_table_list.tsx                 |  61 ++
 src/plugins/event_annotation/public/index.ts  |   5 +
 src/plugins/event_annotation/public/plugin.ts |  62 +-
 src/plugins/event_annotation/server/plugin.ts |   1 -
 .../event_annotation/server/saved_objects.ts  |   8 +-
 src/plugins/event_annotation/tsconfig.json    |  23 +-
 src/plugins/files_management/public/app.tsx   |   6 +-
 .../public/mount_management_section.tsx       |   2 +-
 src/plugins/files_management/tsconfig.json    |   3 +-
 .../common/index.ts                           |   9 +
 .../common/types.ts                           |   9 +
 .../visualization_ui_components/kibana.jsonc  |   6 +-
 .../dimension_buttons/dimension_button.tsx    |  41 +-
 .../dimension_button_icon.tsx                 |   6 +-
 .../dimension_buttons/empty_button.tsx        |  64 ++
 .../components/dimension_buttons/index.ts     |   4 +
 .../dimension_buttons/palette_indicator.tsx   |  20 +-
 .../components/dimension_buttons/trigger.tsx  |  70 ++
 .../public/components/index.ts                |   6 +
 .../components}/line_style_settings.tsx       |  25 +-
 .../public/components/name_input.tsx          |   2 +-
 .../query_input/filter_query_input.scss       |  11 +
 .../query_input/filter_query_input.tsx        |   5 +-
 .../components/text_decoration_setting.tsx    | 122 ++++
 .../public/index.ts                           |   9 +
 .../public/types.ts                           |  11 +
 .../public/util.ts                            |  12 +
 .../visualization_ui_components/tsconfig.json |   3 +-
 .../visualizations/common/constants.ts        |   1 +
 src/plugins/visualizations/public/mocks.ts    |   1 +
 src/plugins/visualizations/public/plugin.ts   |  10 +-
 src/plugins/visualizations/public/types.ts    |   3 +
 .../public/visualize_app/app.tsx              |   6 +-
 .../components/visualize_listing.tsx          | 319 +++++----
 .../public/visualize_app/index.tsx            |   2 +-
 .../public/visualize_app/types.ts             |   3 +-
 src/plugins/visualizations/tsconfig.json      |   8 +-
 .../functional/page_objects/visualize_page.ts |   8 +
 test/functional/services/listing_table.ts     |  16 +-
 tsconfig.base.json                            |   8 +-
 x-pack/plugins/graph/public/application.tsx   |   2 +-
 .../graph/public/apps/listing_route.tsx       |   6 +-
 x-pack/plugins/graph/tsconfig.json            |   3 +-
 x-pack/plugins/lens/common/constants.ts       |   6 +-
 x-pack/plugins/lens/common/types.ts           |   4 +-
 .../lens/public/app_plugin/lens_top_nav.tsx   |   4 +-
 .../lens/public/data_views_service/loader.ts  |   4 +-
 .../dimension_panel/dimension_panel.test.tsx  |   4 +-
 .../datasources/form_based/form_based.tsx     |   2 +-
 .../text_based/text_based_languages.tsx       |   2 +-
 .../buttons/empty_dimension_button.tsx        |  65 +-
 .../config_panel/layer_panel.scss             | 110 +--
 .../config_panel/layer_panel.test.tsx         |   7 +-
 .../editor_frame/config_panel/layer_panel.tsx |  26 +-
 .../dimension_trigger/index.tsx               |  50 --
 .../xy/annotations/actions/index.ts           |   6 +
 .../annotations/actions/save_action.test.tsx  |   3 +-
 .../xy/annotations/actions/save_action.tsx    |  25 +-
 .../visualizations/xy/annotations/helpers.tsx |  76 +-
 .../lens/public/visualizations/xy/index.ts    |  11 +-
 .../visualizations/xy/to_expression.test.ts   |   2 +
 .../visualizations/xy/visualization.test.tsx  |   2 +
 .../visualizations/xy/visualization.tsx       |  20 +-
 .../annotations_panel.tsx                     | 446 +++---------
 .../annotations_config_panel/helpers.ts       | 116 ---
 .../annotations_config_panel/icon_set.ts      | 106 ---
 .../annotations_config_panel/index.test.tsx   | 668 ------------------
 .../annotations_config_panel/types.ts         |  15 -
 .../xy/xy_config_panel/dimension_editor.tsx   |  25 +-
 .../reference_line_panel.tsx                  |  20 +-
 .../shared/marker_decoration_settings.tsx     | 106 +--
 .../xy_config_panel/xy_config_panel.test.tsx  |  20 +-
 .../visualizations/xy/xy_suggestions.test.ts  |   2 +
 x-pack/plugins/maps/public/render_app.tsx     |   2 +-
 .../routes/list_page/maps_list_view.tsx       |   6 +-
 x-pack/plugins/maps/tsconfig.json             |   3 +-
 .../translations/translations/fr-FR.json      |  36 -
 .../translations/translations/ja-JP.json      |  36 -
 .../translations/translations/zh-CN.json      |  36 -
 .../apps/lens/group6/annotations.ts           |  14 +-
 .../test/functional/page_objects/lens_page.ts |   4 +-
 yarn.lock                                     |  10 +-
 176 files changed, 4809 insertions(+), 2414 deletions(-)
 create mode 100644 packages/content-management/tabbed_table_list_view/README.mdx
 create mode 100644 packages/content-management/tabbed_table_list_view/index.ts
 create mode 100644 packages/content-management/tabbed_table_list_view/jest.config.js
 create mode 100644 packages/content-management/tabbed_table_list_view/kibana.jsonc
 create mode 100644 packages/content-management/tabbed_table_list_view/package.json
 create mode 100644 packages/content-management/tabbed_table_list_view/src/index.ts
 create mode 100644 packages/content-management/tabbed_table_list_view/src/tabbed_table_list_view.test.tsx
 create mode 100644 packages/content-management/tabbed_table_list_view/src/tabbed_table_list_view.tsx
 create mode 100644 packages/content-management/tabbed_table_list_view/tsconfig.json
 rename packages/content-management/{table_list => table_list_view}/README.mdx (100%)
 create mode 100644 packages/content-management/table_list_view/index.ts
 rename packages/content-management/{table_list => table_list_view}/jest.config.js (86%)
 rename packages/content-management/{table_list => table_list_view}/kibana.jsonc (57%)
 rename packages/content-management/{table_list => table_list_view}/package.json (62%)
 rename packages/content-management/{table_list => table_list_view}/src/table_list_view.stories.tsx (90%)
 create mode 100644 packages/content-management/table_list_view/src/table_list_view.tsx
 create mode 100644 packages/content-management/table_list_view/tsconfig.json
 create mode 100644 packages/content-management/table_list_view_table/README.mdx
 rename packages/content-management/{table_list => table_list_view_table}/index.ts (69%)
 create mode 100644 packages/content-management/table_list_view_table/jest.config.js
 create mode 100644 packages/content-management/table_list_view_table/kibana.jsonc
 create mode 100644 packages/content-management/table_list_view_table/package.json
 rename packages/content-management/{table_list => table_list_view_table}/src/__jest__/index.ts (100%)
 rename packages/content-management/{table_list => table_list_view_table}/src/__jest__/tests.helpers.tsx (100%)
 rename packages/content-management/{table_list => table_list_view_table}/src/actions.ts (99%)
 rename packages/content-management/{table_list => table_list_view_table}/src/components/confirm_delete_modal.tsx (100%)
 rename packages/content-management/{table_list => table_list_view_table}/src/components/index.ts (100%)
 rename packages/content-management/{table_list => table_list_view_table}/src/components/item_details.tsx (96%)
 rename packages/content-management/{table_list => table_list_view_table}/src/components/listing_limit_warning.tsx (100%)
 rename packages/content-management/{table_list => table_list_view_table}/src/components/table.tsx (94%)
 rename packages/content-management/{table_list => table_list_view_table}/src/components/table_sort_select.tsx (98%)
 rename packages/content-management/{table_list => table_list_view_table}/src/components/tag_badge.tsx (100%)
 rename packages/content-management/{table_list => table_list_view_table}/src/components/tag_filter_panel.tsx (100%)
 rename packages/content-management/{table_list => table_list_view_table}/src/components/updated_at_field.tsx (100%)
 rename packages/content-management/{table_list => table_list_view_table}/src/components/use_tag_filter_panel.tsx (100%)
 rename packages/content-management/{table_list => table_list_view_table}/src/constants.ts (100%)
 rename packages/content-management/{table_list => table_list_view_table}/src/index.ts (81%)
 rename packages/content-management/{table_list => table_list_view_table}/src/mocks.tsx (99%)
 rename packages/content-management/{table_list => table_list_view_table}/src/reducer.tsx (99%)
 rename packages/content-management/{table_list => table_list_view_table}/src/services.tsx (100%)
 rename packages/content-management/{table_list => table_list_view_table}/src/table_list_view.test.tsx (91%)
 rename packages/content-management/{table_list/src/table_list_view.tsx => table_list_view_table/src/table_list_view_table.tsx} (88%)
 rename packages/content-management/{table_list => table_list_view_table}/src/types.ts (100%)
 rename packages/content-management/{table_list => table_list_view_table}/src/use_tags.ts (98%)
 rename packages/content-management/{table_list => table_list_view_table}/src/use_url_state.ts (100%)
 rename packages/content-management/{table_list => table_list_view_table}/tsconfig.json (100%)
 create mode 100755 src/plugins/event_annotation/.i18nrc.json
 create mode 100644 src/plugins/event_annotation/common/create_copied_annotation.ts
 create mode 100644 src/plugins/event_annotation/public/components/__snapshots__/group_editor_flyout.test.tsx.snap
 create mode 100644 src/plugins/event_annotation/public/components/annotation_editor_controls/annotation_editor_controls.tsx
 create mode 100644 src/plugins/event_annotation/public/components/annotation_editor_controls/helpers.ts
 create mode 100644 src/plugins/event_annotation/public/components/annotation_editor_controls/icon_set.ts
 rename {x-pack/plugins/lens/public/visualizations/xy/xy_config_panel/annotations_config_panel => src/plugins/event_annotation/public/components/annotation_editor_controls}/index.scss (100%)
 create mode 100644 src/plugins/event_annotation/public/components/annotation_editor_controls/index.test.tsx
 create mode 100644 src/plugins/event_annotation/public/components/annotation_editor_controls/index.tsx
 rename {x-pack/plugins/lens/public/visualizations/xy/xy_config_panel/annotations_config_panel => src/plugins/event_annotation/public/components/annotation_editor_controls}/manual_annotation_panel.tsx (80%)
 rename {x-pack/plugins/lens/public/visualizations/xy/xy_config_panel/annotations_config_panel => src/plugins/event_annotation/public/components/annotation_editor_controls}/query_annotation_panel.tsx (64%)
 rename {x-pack/plugins/lens/public/visualizations/xy/xy_config_panel/annotations_config_panel => src/plugins/event_annotation/public/components/annotation_editor_controls}/range_annotation_panel.tsx (72%)
 rename {x-pack/plugins/lens/public/visualizations/xy/xy_config_panel/annotations_config_panel => src/plugins/event_annotation/public/components/annotation_editor_controls}/tooltip_annotation_panel.tsx (84%)
 create mode 100644 src/plugins/event_annotation/public/components/annotation_editor_controls/types.ts
 create mode 100644 src/plugins/event_annotation/public/components/get_annotation_accessor.ts
 create mode 100644 src/plugins/event_annotation/public/components/group_editor_controls/annotation_list.tsx
 create mode 100644 src/plugins/event_annotation/public/components/group_editor_controls/get_annotation_accessor.ts
 create mode 100644 src/plugins/event_annotation/public/components/group_editor_controls/group_editor_controls.test.tsx
 create mode 100644 src/plugins/event_annotation/public/components/group_editor_controls/group_editor_controls.tsx
 create mode 100644 src/plugins/event_annotation/public/components/group_editor_controls/index.ts
 create mode 100644 src/plugins/event_annotation/public/components/group_editor_flyout.test.tsx
 create mode 100644 src/plugins/event_annotation/public/components/group_editor_flyout.tsx
 create mode 100644 src/plugins/event_annotation/public/components/index.ts
 create mode 100644 src/plugins/event_annotation/public/components/table_list.test.tsx
 create mode 100644 src/plugins/event_annotation/public/components/table_list.tsx
 create mode 100644 src/plugins/event_annotation/public/get_table_list.tsx
 create mode 100644 src/plugins/visualization_ui_components/common/index.ts
 create mode 100644 src/plugins/visualization_ui_components/common/types.ts
 create mode 100644 src/plugins/visualization_ui_components/public/components/dimension_buttons/empty_button.tsx
 create mode 100644 src/plugins/visualization_ui_components/public/components/dimension_buttons/trigger.tsx
 rename {x-pack/plugins/lens/public/visualizations/xy/xy_config_panel/shared => src/plugins/visualization_ui_components/public/components}/line_style_settings.tsx (83%)
 create mode 100644 src/plugins/visualization_ui_components/public/components/query_input/filter_query_input.scss
 create mode 100644 src/plugins/visualization_ui_components/public/components/text_decoration_setting.tsx
 create mode 100644 src/plugins/visualization_ui_components/public/types.ts
 create mode 100644 src/plugins/visualization_ui_components/public/util.ts
 delete mode 100644 x-pack/plugins/lens/public/shared_components/dimension_trigger/index.tsx
 delete mode 100644 x-pack/plugins/lens/public/visualizations/xy/xy_config_panel/annotations_config_panel/helpers.ts
 delete mode 100644 x-pack/plugins/lens/public/visualizations/xy/xy_config_panel/annotations_config_panel/icon_set.ts
 delete mode 100644 x-pack/plugins/lens/public/visualizations/xy/xy_config_panel/annotations_config_panel/index.test.tsx
 delete mode 100644 x-pack/plugins/lens/public/visualizations/xy/xy_config_panel/annotations_config_panel/types.ts

diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS
index 667b43e3a73da..7d57bd2924ba0 100644
--- a/.github/CODEOWNERS
+++ b/.github/CODEOWNERS
@@ -88,7 +88,9 @@ src/plugins/console @elastic/platform-deployment-management
 packages/content-management/content_editor @elastic/appex-sharedux
 examples/content_management_examples @elastic/appex-sharedux
 src/plugins/content_management @elastic/appex-sharedux
-packages/content-management/table_list @elastic/appex-sharedux
+packages/content-management/tabbed_table_list_view @elastic/appex-sharedux
+packages/content-management/table_list_view @elastic/appex-sharedux
+packages/content-management/table_list_view_table @elastic/appex-sharedux
 packages/kbn-content-management-utils @elastic/kibana-data-discovery
 examples/controls_example @elastic/kibana-presentation
 src/plugins/controls @elastic/kibana-presentation
diff --git a/examples/content_management_examples/public/examples/msearch/msearch_app.tsx b/examples/content_management_examples/public/examples/msearch/msearch_app.tsx
index aa2bc6c1a8b7b..ae05e15db91d0 100644
--- a/examples/content_management_examples/public/examples/msearch/msearch_app.tsx
+++ b/examples/content_management_examples/public/examples/msearch/msearch_app.tsx
@@ -8,7 +8,7 @@
 
 import React from 'react';
 import { ContentClientProvider, type ContentClient } from '@kbn/content-management-plugin/public';
-import { TableListViewKibanaProvider } from '@kbn/content-management-table-list';
+import { TableListViewKibanaProvider } from '@kbn/content-management-table-list-view-table';
 import type { CoreStart } from '@kbn/core/public';
 import { toMountPoint } from '@kbn/kibana-react-plugin/public';
 import { FormattedRelative, I18nProvider } from '@kbn/i18n-react';
diff --git a/examples/content_management_examples/public/examples/msearch/msearch_table.tsx b/examples/content_management_examples/public/examples/msearch/msearch_table.tsx
index 47eaab4ba602b..a30c652cf0f79 100644
--- a/examples/content_management_examples/public/examples/msearch/msearch_table.tsx
+++ b/examples/content_management_examples/public/examples/msearch/msearch_table.tsx
@@ -6,7 +6,7 @@
  * Side Public License, v 1.
  */
 
-import { TableListView, UserContentCommonSchema } from '@kbn/content-management-table-list';
+import { TableListView, UserContentCommonSchema } from '@kbn/content-management-table-list-view';
 import { useContentClient } from '@kbn/content-management-plugin/public';
 import React from 'react';
 import { SavedObjectsFindOptionsReference } from '@kbn/core-saved-objects-api-browser';
@@ -51,7 +51,7 @@ export const MSearchTable = () => {
       initialPageSize={50}
       entityName={`ContentItem`}
       entityNamePlural={`ContentItems`}
-      tableListTitle={`MSearch Demo`}
+      title={`MSearch Demo`}
       urlStateEnabled={false}
       emptyPrompt={<>No data found. Try to install some sample data first.</>}
       onClickTitle={(item) => {
diff --git a/examples/content_management_examples/tsconfig.json b/examples/content_management_examples/tsconfig.json
index 7f07213ce82b6..ba1c3f19850c6 100644
--- a/examples/content_management_examples/tsconfig.json
+++ b/examples/content_management_examples/tsconfig.json
@@ -20,10 +20,13 @@
     "@kbn/content-management-plugin",
     "@kbn/core-application-browser",
     "@kbn/shared-ux-link-redirect-app",
-    "@kbn/content-management-table-list",
+    "@kbn/content-management-table-list-view",
+    "@kbn/content-management-table-list-view-table",
     "@kbn/kibana-react-plugin",
     "@kbn/i18n-react",
     "@kbn/saved-objects-tagging-oss-plugin",
     "@kbn/core-saved-objects-api-browser",
+    "@kbn/content-management-table-list-view-table",
+    "@kbn/content-management-table-list-view",
   ]
 }
diff --git a/package.json b/package.json
index c00cffaf22cb4..f65e02b6dabec 100644
--- a/package.json
+++ b/package.json
@@ -189,7 +189,9 @@
     "@kbn/content-management-content-editor": "link:packages/content-management/content_editor",
     "@kbn/content-management-examples-plugin": "link:examples/content_management_examples",
     "@kbn/content-management-plugin": "link:src/plugins/content_management",
-    "@kbn/content-management-table-list": "link:packages/content-management/table_list",
+    "@kbn/content-management-tabbed-table-list-view": "link:packages/content-management/tabbed_table_list_view",
+    "@kbn/content-management-table-list-view": "link:packages/content-management/table_list_view",
+    "@kbn/content-management-table-list-view-table": "link:packages/content-management/table_list_view_table",
     "@kbn/content-management-utils": "link:packages/kbn-content-management-utils",
     "@kbn/controls-example-plugin": "link:examples/controls_example",
     "@kbn/controls-plugin": "link:src/plugins/controls",
diff --git a/packages/content-management/tabbed_table_list_view/README.mdx b/packages/content-management/tabbed_table_list_view/README.mdx
new file mode 100644
index 0000000000000..357a07f8fc1c3
--- /dev/null
+++ b/packages/content-management/tabbed_table_list_view/README.mdx
@@ -0,0 +1,20 @@
+---
+id: sharedUX/contentManagement/TabbedTableListView
+slug: /shared-ux/content-management/tabbed-table-list-view
+title: Tabbed table list view
+summary: A table to render user generated saved objects.
+tags: ['shared-ux', 'content-management']
+date: 2022-08-09
+---
+
+The `<TabbedTableListView />` renders an eui page to display a list of user content saved object.
+
+**Uncomplete documentation**. Will be updated.
+
+## API
+
+TODO: https://github.com/elastic/kibana/issues/144402
+
+## EUI Promotion Status
+
+This component is not currently considered for promotion to EUI.
diff --git a/packages/content-management/tabbed_table_list_view/index.ts b/packages/content-management/tabbed_table_list_view/index.ts
new file mode 100644
index 0000000000000..cf228d45a10e3
--- /dev/null
+++ b/packages/content-management/tabbed_table_list_view/index.ts
@@ -0,0 +1,11 @@
+/*
+ * 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.
+ */
+
+export { TabbedTableListView, type TableListTab, type TableListTabParentProps } from './src';
+
+export type { UserContentCommonSchema } from '@kbn/content-management-table-list-view-table';
diff --git a/packages/content-management/tabbed_table_list_view/jest.config.js b/packages/content-management/tabbed_table_list_view/jest.config.js
new file mode 100644
index 0000000000000..dcb469837dcbc
--- /dev/null
+++ b/packages/content-management/tabbed_table_list_view/jest.config.js
@@ -0,0 +1,13 @@
+/*
+ * 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.
+ */
+
+module.exports = {
+  preset: '@kbn/test',
+  rootDir: '../../..',
+  roots: ['<rootDir>/packages/content-management/tabbed_table_list_view'],
+};
diff --git a/packages/content-management/tabbed_table_list_view/kibana.jsonc b/packages/content-management/tabbed_table_list_view/kibana.jsonc
new file mode 100644
index 0000000000000..335fdd602037a
--- /dev/null
+++ b/packages/content-management/tabbed_table_list_view/kibana.jsonc
@@ -0,0 +1,5 @@
+{
+  "type": "shared-common",
+  "id": "@kbn/content-management-tabbed-table-list-view",
+  "owner": "@elastic/appex-sharedux"
+}
diff --git a/packages/content-management/tabbed_table_list_view/package.json b/packages/content-management/tabbed_table_list_view/package.json
new file mode 100644
index 0000000000000..42c4ca6b19c0d
--- /dev/null
+++ b/packages/content-management/tabbed_table_list_view/package.json
@@ -0,0 +1,6 @@
+{
+  "name": "@kbn/content-management-tabbed-table-list-view",
+  "private": true,
+  "version": "1.0.0",
+  "license": "SSPL-1.0 OR Elastic License 2.0"
+}
diff --git a/packages/content-management/tabbed_table_list_view/src/index.ts b/packages/content-management/tabbed_table_list_view/src/index.ts
new file mode 100644
index 0000000000000..515598b765bcd
--- /dev/null
+++ b/packages/content-management/tabbed_table_list_view/src/index.ts
@@ -0,0 +1,13 @@
+/*
+ * 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.
+ */
+
+export {
+  TabbedTableListView,
+  type TableListTab,
+  type TableListTabParentProps,
+} from './tabbed_table_list_view';
diff --git a/packages/content-management/tabbed_table_list_view/src/tabbed_table_list_view.test.tsx b/packages/content-management/tabbed_table_list_view/src/tabbed_table_list_view.test.tsx
new file mode 100644
index 0000000000000..cc7ced86951a4
--- /dev/null
+++ b/packages/content-management/tabbed_table_list_view/src/tabbed_table_list_view.test.tsx
@@ -0,0 +1,128 @@
+/*
+ * 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 React from 'react';
+import { ReactWrapper, mount, shallow } from 'enzyme';
+import {
+  TabbedTableListView,
+  TableListTabParentProps,
+  TableListTab,
+} from './tabbed_table_list_view';
+import { KibanaPageTemplate } from '@kbn/shared-ux-page-kibana-template';
+import { EuiPageTemplate } from '@elastic/eui';
+import { act } from 'react-dom/test-utils';
+
+// Mock the necessary props for the component
+const title = 'Test Title';
+const description = 'Test Description';
+const headingId = 'test-heading-id';
+const children = <div>Test Children</div>;
+
+const tableList1 = 'Test Table List 1';
+const tableList2 = 'Test Table List 2';
+
+const tabs: TableListTab[] = [
+  {
+    title: 'Tab 1',
+    id: 'tab-1',
+    getTableList: async (props: TableListTabParentProps) => <div>{tableList1}</div>,
+  },
+  {
+    title: 'Tab 2',
+    id: 'tab-2',
+    getTableList: async (props: TableListTabParentProps) => <div>{tableList2}</div>,
+  },
+];
+
+describe('TabbedTableListView', () => {
+  it('should render without errors', () => {
+    const wrapper = shallow(
+      <TabbedTableListView
+        title={title}
+        description={description}
+        headingId={headingId}
+        children={children}
+        tabs={tabs}
+        activeTabId={'tab-1'}
+        changeActiveTab={() => {}}
+      />
+    );
+    expect(wrapper.exists()).toBe(true);
+  });
+
+  it('should render the correct title and description', () => {
+    const wrapper = shallow(
+      <TabbedTableListView
+        title={title}
+        description={description}
+        headingId={headingId}
+        children={children}
+        tabs={tabs}
+        activeTabId={'tab-1'}
+        changeActiveTab={() => {}}
+      />
+    );
+    expect(wrapper.find(KibanaPageTemplate.Header).prop('pageTitle')).toMatchInlineSnapshot(`
+      <span
+        id="test-heading-id"
+      >
+        Test Title
+      </span>
+    `);
+    expect(wrapper.find(KibanaPageTemplate.Header).prop('description')).toContain(description);
+  });
+
+  it('should render the correct number of tabs', () => {
+    const wrapper = shallow(
+      <TabbedTableListView
+        title={title}
+        description={description}
+        headingId={headingId}
+        children={children}
+        tabs={tabs}
+        activeTabId={'tab-1'}
+        changeActiveTab={() => {}}
+      />
+    );
+
+    expect(wrapper.find(EuiPageTemplate.Header).prop('tabs')).toHaveLength(2);
+  });
+
+  it('should switch tabs when props change', async () => {
+    const changeActiveTab = jest.fn();
+
+    let wrapper: ReactWrapper | undefined;
+    await act(async () => {
+      wrapper = mount(
+        <TabbedTableListView
+          title={title}
+          description={description}
+          headingId={headingId}
+          children={children}
+          tabs={tabs}
+          activeTabId={'tab-1'}
+          changeActiveTab={changeActiveTab}
+        />
+      );
+    });
+
+    if (!wrapper) {
+      throw new Error("enzyme wrapper didn't initialize");
+    }
+
+    expect(wrapper.find(EuiPageTemplate.Section).text()).toContain(tableList1);
+
+    await act(async () => {
+      wrapper?.setProps({
+        activeTabId: 'tab-2',
+      });
+    });
+
+    expect(wrapper.find(EuiPageTemplate.Section).text()).toContain(tableList2);
+  });
+});
diff --git a/packages/content-management/tabbed_table_list_view/src/tabbed_table_list_view.tsx b/packages/content-management/tabbed_table_list_view/src/tabbed_table_list_view.tsx
new file mode 100644
index 0000000000000..9872e35d90c88
--- /dev/null
+++ b/packages/content-management/tabbed_table_list_view/src/tabbed_table_list_view.tsx
@@ -0,0 +1,88 @@
+/*
+ * 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 { KibanaPageTemplate } from '@kbn/shared-ux-page-kibana-template';
+import React, { useCallback, useEffect, useState } from 'react';
+import type {
+  TableListViewTableProps,
+  UserContentCommonSchema,
+} from '@kbn/content-management-table-list-view-table';
+import type { TableListViewProps } from '@kbn/content-management-table-list-view';
+
+export type TableListTabParentProps<T extends UserContentCommonSchema = UserContentCommonSchema> =
+  Pick<TableListViewTableProps<T>, 'onFetchSuccess' | 'setPageDataTestSubject'>;
+
+export interface TableListTab<T extends UserContentCommonSchema = UserContentCommonSchema> {
+  title: string;
+  id: string;
+  getTableList: (
+    propsFromParent: TableListTabParentProps<T>
+  ) => Promise<React.ReactNode> | React.ReactNode;
+}
+
+type TabbedTableListViewProps = Pick<
+  TableListViewProps<UserContentCommonSchema>,
+  'title' | 'description' | 'headingId' | 'children'
+> & { tabs: TableListTab[]; activeTabId: string; changeActiveTab: (id: string) => void };
+
+export const TabbedTableListView = ({
+  title,
+  description,
+  headingId,
+  children,
+  tabs,
+  activeTabId,
+  changeActiveTab,
+}: TabbedTableListViewProps) => {
+  const [hasInitialFetchReturned, setHasInitialFetchReturned] = useState(false);
+  const [pageDataTestSubject, setPageDataTestSubject] = useState<string>();
+
+  const getActiveTab = useCallback(
+    () => tabs.find((tab) => tab.id === activeTabId) ?? tabs[0],
+    [activeTabId, tabs]
+  );
+
+  const [tableList, setTableList] = useState<React.ReactNode>(null);
+
+  useEffect(() => {
+    async function loadTableList() {
+      const newTableList = await getActiveTab().getTableList({
+        onFetchSuccess: () => {
+          if (!hasInitialFetchReturned) {
+            setHasInitialFetchReturned(true);
+          }
+        },
+        setPageDataTestSubject,
+      });
+      setTableList(newTableList);
+    }
+
+    loadTableList();
+  }, [hasInitialFetchReturned, activeTabId, tabs, getActiveTab]);
+
+  return (
+    <KibanaPageTemplate panelled data-test-subj={pageDataTestSubject}>
+      <KibanaPageTemplate.Header
+        pageTitle={<span id={headingId}>{title}</span>}
+        description={description}
+        data-test-subj="top-nav"
+        tabs={tabs.map((tab) => ({
+          onClick: () => changeActiveTab(tab.id),
+          isSelected: tab.id === getActiveTab().id,
+          label: tab.title,
+        }))}
+      />
+      <KibanaPageTemplate.Section aria-labelledby={hasInitialFetchReturned ? headingId : undefined}>
+        {/* Any children passed to the component */}
+        {children}
+
+        {tableList}
+      </KibanaPageTemplate.Section>
+    </KibanaPageTemplate>
+  );
+};
diff --git a/packages/content-management/tabbed_table_list_view/tsconfig.json b/packages/content-management/tabbed_table_list_view/tsconfig.json
new file mode 100644
index 0000000000000..7d0e24d7aff69
--- /dev/null
+++ b/packages/content-management/tabbed_table_list_view/tsconfig.json
@@ -0,0 +1,27 @@
+{
+  "extends": "../../../tsconfig.base.json",
+  "compilerOptions": {
+    "outDir": "target/types",
+    "types": [
+      "jest",
+      "node",
+      "react",
+      "@kbn/ambient-ui-types",
+      "@kbn/ambient-storybook-types",
+      "@emotion/react/types/css-prop"
+    ]
+  },
+  "include": [
+    "**/*.ts",
+    "**/*.tsx",
+  ],
+  "kbn_references": [
+    "@kbn/content-management-table-list-view",
+    "@kbn/shared-ux-page-kibana-template",
+    "@kbn/content-management-table-list-view-table",
+    "@kbn/content-management-table-list-view",
+  ],
+  "exclude": [
+    "target/**/*",
+  ]
+}
diff --git a/packages/content-management/table_list/README.mdx b/packages/content-management/table_list_view/README.mdx
similarity index 100%
rename from packages/content-management/table_list/README.mdx
rename to packages/content-management/table_list_view/README.mdx
diff --git a/packages/content-management/table_list_view/index.ts b/packages/content-management/table_list_view/index.ts
new file mode 100644
index 0000000000000..cb075152947ed
--- /dev/null
+++ b/packages/content-management/table_list_view/index.ts
@@ -0,0 +1,12 @@
+/*
+ * 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.
+ */
+
+export { TableListView } from './src/table_list_view';
+export type { TableListViewProps } from './src/table_list_view';
+
+export type { UserContentCommonSchema } from '@kbn/content-management-table-list-view-table';
diff --git a/packages/content-management/table_list/jest.config.js b/packages/content-management/table_list_view/jest.config.js
similarity index 86%
rename from packages/content-management/table_list/jest.config.js
rename to packages/content-management/table_list_view/jest.config.js
index 546d16dd86cf0..aab014b44dcb8 100644
--- a/packages/content-management/table_list/jest.config.js
+++ b/packages/content-management/table_list_view/jest.config.js
@@ -9,5 +9,5 @@
 module.exports = {
   preset: '@kbn/test',
   rootDir: '../../..',
-  roots: ['<rootDir>/packages/content-management/table_list'],
+  roots: ['<rootDir>/packages/content-management/table_list_view'],
 };
diff --git a/packages/content-management/table_list/kibana.jsonc b/packages/content-management/table_list_view/kibana.jsonc
similarity index 57%
rename from packages/content-management/table_list/kibana.jsonc
rename to packages/content-management/table_list_view/kibana.jsonc
index 2e4dd9548f604..a9c3a12553ff4 100644
--- a/packages/content-management/table_list/kibana.jsonc
+++ b/packages/content-management/table_list_view/kibana.jsonc
@@ -1,5 +1,5 @@
 {
   "type": "shared-common",
-  "id": "@kbn/content-management-table-list",
+  "id": "@kbn/content-management-table-list-view",
   "owner": "@elastic/appex-sharedux"
 }
diff --git a/packages/content-management/table_list/package.json b/packages/content-management/table_list_view/package.json
similarity index 62%
rename from packages/content-management/table_list/package.json
rename to packages/content-management/table_list_view/package.json
index b387c8a466b5e..1a3dbce9e195c 100644
--- a/packages/content-management/table_list/package.json
+++ b/packages/content-management/table_list_view/package.json
@@ -1,6 +1,6 @@
 {
-  "name": "@kbn/content-management-table-list",
+  "name": "@kbn/content-management-table-list-view",
   "private": true,
   "version": "1.0.0",
   "license": "SSPL-1.0 OR Elastic License 2.0"
-}
\ No newline at end of file
+}
diff --git a/packages/content-management/table_list/src/table_list_view.stories.tsx b/packages/content-management/table_list_view/src/table_list_view.stories.tsx
similarity index 90%
rename from packages/content-management/table_list/src/table_list_view.stories.tsx
rename to packages/content-management/table_list_view/src/table_list_view.stories.tsx
index 4943c9d0be657..878e5413b6774 100644
--- a/packages/content-management/table_list/src/table_list_view.stories.tsx
+++ b/packages/content-management/table_list_view/src/table_list_view.stories.tsx
@@ -11,10 +11,16 @@ import Chance from 'chance';
 import moment from 'moment';
 import { action } from '@storybook/addon-actions';
 
-import { Params, getStoryArgTypes, getStoryServices } from './mocks';
-
-import { TableListView as Component, UserContentCommonSchema } from './table_list_view';
-import { TableListViewProvider } from './services';
+import {
+  TableListViewProvider,
+  UserContentCommonSchema,
+} from '@kbn/content-management-table-list-view-table';
+import {
+  Params,
+  getStoryArgTypes,
+  getStoryServices,
+} from '@kbn/content-management-table-list-view-table/src/mocks';
+import { TableListView as Component } from './table_list_view';
 
 import mdx from '../README.mdx';
 
diff --git a/packages/content-management/table_list_view/src/table_list_view.tsx b/packages/content-management/table_list_view/src/table_list_view.tsx
new file mode 100644
index 0000000000000..59fbe67a4a4c5
--- /dev/null
+++ b/packages/content-management/table_list_view/src/table_list_view.tsx
@@ -0,0 +1,131 @@
+/*
+ * 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 { KibanaPageTemplate } from '@kbn/shared-ux-page-kibana-template';
+import React, { ReactNode, useState } from 'react';
+import {
+  TableListViewTable,
+  type TableListViewTableProps,
+  type UserContentCommonSchema,
+} from '@kbn/content-management-table-list-view-table';
+
+export type TableListViewProps<T extends UserContentCommonSchema = UserContentCommonSchema> = Pick<
+  TableListViewTableProps<T>,
+  | 'entityName'
+  | 'entityNamePlural'
+  | 'initialFilter'
+  | 'headingId'
+  | 'initialPageSize'
+  | 'listingLimit'
+  | 'urlStateEnabled'
+  | 'customTableColumn'
+  | 'emptyPrompt'
+  | 'findItems'
+  | 'createItem'
+  | 'editItem'
+  | 'deleteItems'
+  | 'getDetailViewLink'
+  | 'onClickTitle'
+  | 'id'
+  | 'rowItemActions'
+  | 'contentEditor'
+  | 'titleColumnName'
+  | 'withoutPageTemplateWrapper'
+  | 'showEditActionForItem'
+> & {
+  title: string;
+  description?: string;
+  /**
+   * Additional actions (buttons) to be placed in the page header.
+   * @note only the first two values will be used.
+   */
+  additionalRightSideActions?: ReactNode[];
+  children?: ReactNode | undefined;
+};
+
+export const TableListView = <T extends UserContentCommonSchema>({
+  title,
+  description,
+  entityName,
+  entityNamePlural,
+  initialFilter,
+  headingId,
+  initialPageSize,
+  listingLimit,
+  urlStateEnabled = true,
+  customTableColumn,
+  emptyPrompt,
+  findItems,
+  createItem,
+  editItem,
+  deleteItems,
+  getDetailViewLink,
+  onClickTitle,
+  rowItemActions,
+  id: listingId,
+  contentEditor,
+  children,
+  titleColumnName,
+  additionalRightSideActions,
+  withoutPageTemplateWrapper,
+}: TableListViewProps<T>) => {
+  const PageTemplate = withoutPageTemplateWrapper
+    ? (React.Fragment as unknown as typeof KibanaPageTemplate)
+    : KibanaPageTemplate;
+
+  const [hasInitialFetchReturned, setHasInitialFetchReturned] = useState(false);
+  const [pageDataTestSubject, setPageDataTestSubject] = useState<string>();
+
+  return (
+    <PageTemplate panelled data-test-subj={pageDataTestSubject}>
+      <KibanaPageTemplate.Header
+        pageTitle={<span id={headingId}>{title}</span>}
+        description={description}
+        rightSideItems={additionalRightSideActions?.slice(0, 2)}
+        data-test-subj="top-nav"
+      />
+      <KibanaPageTemplate.Section aria-labelledby={hasInitialFetchReturned ? headingId : undefined}>
+        {/* Any children passed to the component */}
+        {children}
+
+        <TableListViewTable
+          tableCaption={title}
+          entityName={entityName}
+          entityNamePlural={entityNamePlural}
+          initialFilter={initialFilter}
+          headingId={headingId}
+          initialPageSize={initialPageSize}
+          listingLimit={listingLimit}
+          urlStateEnabled={urlStateEnabled}
+          customTableColumn={customTableColumn}
+          emptyPrompt={emptyPrompt}
+          findItems={findItems}
+          createItem={createItem}
+          editItem={editItem}
+          deleteItems={deleteItems}
+          rowItemActions={rowItemActions}
+          getDetailViewLink={getDetailViewLink}
+          onClickTitle={onClickTitle}
+          id={listingId}
+          contentEditor={contentEditor}
+          titleColumnName={titleColumnName}
+          withoutPageTemplateWrapper={withoutPageTemplateWrapper}
+          onFetchSuccess={() => {
+            if (!hasInitialFetchReturned) {
+              setHasInitialFetchReturned(true);
+            }
+          }}
+          setPageDataTestSubject={setPageDataTestSubject}
+        />
+      </KibanaPageTemplate.Section>
+    </PageTemplate>
+  );
+};
+
+// eslint-disable-next-line import/no-default-export
+export default TableListView;
diff --git a/packages/content-management/table_list_view/tsconfig.json b/packages/content-management/table_list_view/tsconfig.json
new file mode 100644
index 0000000000000..8b09db8f78c5a
--- /dev/null
+++ b/packages/content-management/table_list_view/tsconfig.json
@@ -0,0 +1,25 @@
+{
+  "extends": "../../../tsconfig.base.json",
+  "compilerOptions": {
+    "outDir": "target/types",
+    "types": [
+      "jest",
+      "node",
+      "react",
+      "@kbn/ambient-ui-types",
+      "@kbn/ambient-storybook-types",
+      "@emotion/react/types/css-prop"
+    ]
+  },
+  "include": [
+    "**/*.ts",
+    "**/*.tsx",
+  ],
+  "kbn_references": [
+    "@kbn/shared-ux-page-kibana-template",
+    "@kbn/content-management-table-list-view-table"
+  ],
+  "exclude": [
+    "target/**/*",
+  ]
+}
diff --git a/packages/content-management/table_list_view_table/README.mdx b/packages/content-management/table_list_view_table/README.mdx
new file mode 100644
index 0000000000000..df3fdc9db53e9
--- /dev/null
+++ b/packages/content-management/table_list_view_table/README.mdx
@@ -0,0 +1,20 @@
+---
+id: sharedUX/contentManagement/TableListViewTable
+slug: /shared-ux/content-management/table-list-view-table
+title: Table list view
+summary: A table to render user generated saved objects.
+tags: ['shared-ux', 'content-management']
+date: 2022-08-09
+---
+
+The `<TableListViewTable />` renders a list of user content saved object.
+
+**Uncomplete documentation**. Will be updated.
+
+## API
+
+TODO: https://github.com/elastic/kibana/issues/144402
+
+## EUI Promotion Status
+
+This component is not currently considered for promotion to EUI.
diff --git a/packages/content-management/table_list/index.ts b/packages/content-management/table_list_view_table/index.ts
similarity index 69%
rename from packages/content-management/table_list/index.ts
rename to packages/content-management/table_list_view_table/index.ts
index 9a608b2d6dda3..28acf44221f89 100644
--- a/packages/content-management/table_list/index.ts
+++ b/packages/content-management/table_list_view_table/index.ts
@@ -6,7 +6,8 @@
  * Side Public License, v 1.
  */
 
-export { TableListView, TableListViewProvider, TableListViewKibanaProvider } from './src';
+export { TableListViewTable, TableListViewProvider, TableListViewKibanaProvider } from './src';
+
+export type { UserContentCommonSchema, TableListViewTableProps, RowActions } from './src';
 
-export type { UserContentCommonSchema, RowActions } from './src';
 export type { TableListViewKibanaDependencies } from './src/services';
diff --git a/packages/content-management/table_list_view_table/jest.config.js b/packages/content-management/table_list_view_table/jest.config.js
new file mode 100644
index 0000000000000..6f81c7abbaf3c
--- /dev/null
+++ b/packages/content-management/table_list_view_table/jest.config.js
@@ -0,0 +1,13 @@
+/*
+ * 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.
+ */
+
+module.exports = {
+  preset: '@kbn/test',
+  rootDir: '../../..',
+  roots: ['<rootDir>/packages/content-management/table_list_view_table'],
+};
diff --git a/packages/content-management/table_list_view_table/kibana.jsonc b/packages/content-management/table_list_view_table/kibana.jsonc
new file mode 100644
index 0000000000000..85861c453bffb
--- /dev/null
+++ b/packages/content-management/table_list_view_table/kibana.jsonc
@@ -0,0 +1,5 @@
+{
+  "type": "shared-common",
+  "id": "@kbn/content-management-table-list-view-table",
+  "owner": "@elastic/appex-sharedux"
+}
diff --git a/packages/content-management/table_list_view_table/package.json b/packages/content-management/table_list_view_table/package.json
new file mode 100644
index 0000000000000..b9d5dcf7a03f2
--- /dev/null
+++ b/packages/content-management/table_list_view_table/package.json
@@ -0,0 +1,6 @@
+{
+  "name": "@kbn/content-management-table-list-view-table",
+  "private": true,
+  "version": "1.0.0",
+  "license": "SSPL-1.0 OR Elastic License 2.0"
+}
diff --git a/packages/content-management/table_list/src/__jest__/index.ts b/packages/content-management/table_list_view_table/src/__jest__/index.ts
similarity index 100%
rename from packages/content-management/table_list/src/__jest__/index.ts
rename to packages/content-management/table_list_view_table/src/__jest__/index.ts
diff --git a/packages/content-management/table_list/src/__jest__/tests.helpers.tsx b/packages/content-management/table_list_view_table/src/__jest__/tests.helpers.tsx
similarity index 100%
rename from packages/content-management/table_list/src/__jest__/tests.helpers.tsx
rename to packages/content-management/table_list_view_table/src/__jest__/tests.helpers.tsx
diff --git a/packages/content-management/table_list/src/actions.ts b/packages/content-management/table_list_view_table/src/actions.ts
similarity index 99%
rename from packages/content-management/table_list/src/actions.ts
rename to packages/content-management/table_list_view_table/src/actions.ts
index 6ae2740381fcc..0a18c74a571aa 100644
--- a/packages/content-management/table_list/src/actions.ts
+++ b/packages/content-management/table_list_view_table/src/actions.ts
@@ -8,7 +8,7 @@
 import type { IHttpFetchError } from '@kbn/core-http-browser';
 import type { Query } from '@elastic/eui';
 
-import type { State, UserContentCommonSchema } from './table_list_view';
+import type { State, UserContentCommonSchema } from './table_list_view_table';
 
 /** Action to trigger a fetch of the table items */
 export interface OnFetchItemsAction {
diff --git a/packages/content-management/table_list/src/components/confirm_delete_modal.tsx b/packages/content-management/table_list_view_table/src/components/confirm_delete_modal.tsx
similarity index 100%
rename from packages/content-management/table_list/src/components/confirm_delete_modal.tsx
rename to packages/content-management/table_list_view_table/src/components/confirm_delete_modal.tsx
diff --git a/packages/content-management/table_list/src/components/index.ts b/packages/content-management/table_list_view_table/src/components/index.ts
similarity index 100%
rename from packages/content-management/table_list/src/components/index.ts
rename to packages/content-management/table_list_view_table/src/components/index.ts
diff --git a/packages/content-management/table_list/src/components/item_details.tsx b/packages/content-management/table_list_view_table/src/components/item_details.tsx
similarity index 96%
rename from packages/content-management/table_list/src/components/item_details.tsx
rename to packages/content-management/table_list_view_table/src/components/item_details.tsx
index b7f4186438b66..0b2f52b216905 100644
--- a/packages/content-management/table_list/src/components/item_details.tsx
+++ b/packages/content-management/table_list_view_table/src/components/item_details.tsx
@@ -12,11 +12,11 @@ import { RedirectAppLinks } from '@kbn/shared-ux-link-redirect-app';
 
 import type { Tag } from '../types';
 import { useServices } from '../services';
-import type { UserContentCommonSchema, Props as TableListViewProps } from '../table_list_view';
+import type { UserContentCommonSchema, TableListViewTableProps } from '../table_list_view_table';
 import { TagBadge } from './tag_badge';
 
 type InheritedProps<T extends UserContentCommonSchema> = Pick<
-  TableListViewProps<T>,
+  TableListViewTableProps<T>,
   'onClickTitle' | 'getDetailViewLink' | 'id'
 >;
 interface Props<T extends UserContentCommonSchema> extends InheritedProps<T> {
diff --git a/packages/content-management/table_list/src/components/listing_limit_warning.tsx b/packages/content-management/table_list_view_table/src/components/listing_limit_warning.tsx
similarity index 100%
rename from packages/content-management/table_list/src/components/listing_limit_warning.tsx
rename to packages/content-management/table_list_view_table/src/components/listing_limit_warning.tsx
diff --git a/packages/content-management/table_list/src/components/table.tsx b/packages/content-management/table_list_view_table/src/components/table.tsx
similarity index 94%
rename from packages/content-management/table_list/src/components/table.tsx
rename to packages/content-management/table_list_view_table/src/components/table.tsx
index 3214e7bf00a72..875d1ddedfa41 100644
--- a/packages/content-management/table_list/src/components/table.tsx
+++ b/packages/content-management/table_list_view_table/src/components/table.tsx
@@ -17,6 +17,7 @@ import {
   SearchFilterConfig,
   Direction,
   Query,
+  Search,
   type EuiTableSelectionType,
 } from '@elastic/eui';
 import { i18n } from '@kbn/i18n';
@@ -25,9 +26,9 @@ import { useServices } from '../services';
 import type { Action } from '../actions';
 import type {
   State as TableListViewState,
-  Props as TableListViewProps,
+  TableListViewTableProps,
   UserContentCommonSchema,
-} from '../table_list_view';
+} from '../table_list_view_table';
 import type { TableItemsRowActions } from '../types';
 import { TableSortSelect } from './table_sort_select';
 import { TagFilterPanel } from './tag_filter_panel';
@@ -53,8 +54,9 @@ interface Props<T extends UserContentCommonSchema> extends State<T>, TagManageme
   tableCaption: string;
   tableColumns: Array<EuiBasicTableColumn<T>>;
   hasUpdatedAtMetadata: boolean;
-  deleteItems: TableListViewProps<T>['deleteItems'];
+  deleteItems: TableListViewTableProps<T>['deleteItems'];
   tableItemsRowActions: TableItemsRowActions;
+  renderCreateButton: () => React.ReactElement | undefined;
   onSortChange: (column: SortColumnField, direction: Direction) => void;
   onTableChange: (criteria: CriteriaWithPagination<T>) => void;
   onTableSearchChange: (arg: { query: Query | null; queryText: string }) => void;
@@ -76,6 +78,7 @@ export function Table<T extends UserContentCommonSchema>({
   tagsToTableItemMap,
   tableItemsRowActions,
   deleteItems,
+  renderCreateButton,
   tableCaption,
   onTableChange,
   onTableSearchChange,
@@ -201,10 +204,11 @@ export function Table<T extends UserContentCommonSchema>({
     return [tableSortSelectFilter, tagFilterPanel];
   }, [tableSortSelectFilter, tagFilterPanel]);
 
-  const search = useMemo(() => {
+  const search = useMemo((): Search => {
     return {
       onChange: onTableSearchChange,
       toolsLeft: renderToolsLeft(),
+      toolsRight: renderCreateButton(),
       query: searchQuery.query ?? undefined,
       box: {
         incremental: true,
@@ -212,7 +216,7 @@ export function Table<T extends UserContentCommonSchema>({
       },
       filters: searchFilters,
     };
-  }, [onTableSearchChange, renderToolsLeft, searchFilters, searchQuery.query]);
+  }, [onTableSearchChange, renderCreateButton, renderToolsLeft, searchFilters, searchQuery.query]);
 
   const noItemsMessage = (
     <FormattedMessage
diff --git a/packages/content-management/table_list/src/components/table_sort_select.tsx b/packages/content-management/table_list_view_table/src/components/table_sort_select.tsx
similarity index 98%
rename from packages/content-management/table_list/src/components/table_sort_select.tsx
rename to packages/content-management/table_list_view_table/src/components/table_sort_select.tsx
index ec2c5c5db1f6d..37cc6fc9eea80 100644
--- a/packages/content-management/table_list/src/components/table_sort_select.tsx
+++ b/packages/content-management/table_list_view_table/src/components/table_sort_select.tsx
@@ -19,7 +19,7 @@ import {
 } from '@elastic/eui';
 import { css } from '@emotion/react';
 
-import { State } from '../table_list_view';
+import { State } from '../table_list_view_table';
 
 type SortItem = EuiSelectableOption & {
   column: SortColumnField;
diff --git a/packages/content-management/table_list/src/components/tag_badge.tsx b/packages/content-management/table_list_view_table/src/components/tag_badge.tsx
similarity index 100%
rename from packages/content-management/table_list/src/components/tag_badge.tsx
rename to packages/content-management/table_list_view_table/src/components/tag_badge.tsx
diff --git a/packages/content-management/table_list/src/components/tag_filter_panel.tsx b/packages/content-management/table_list_view_table/src/components/tag_filter_panel.tsx
similarity index 100%
rename from packages/content-management/table_list/src/components/tag_filter_panel.tsx
rename to packages/content-management/table_list_view_table/src/components/tag_filter_panel.tsx
diff --git a/packages/content-management/table_list/src/components/updated_at_field.tsx b/packages/content-management/table_list_view_table/src/components/updated_at_field.tsx
similarity index 100%
rename from packages/content-management/table_list/src/components/updated_at_field.tsx
rename to packages/content-management/table_list_view_table/src/components/updated_at_field.tsx
diff --git a/packages/content-management/table_list/src/components/use_tag_filter_panel.tsx b/packages/content-management/table_list_view_table/src/components/use_tag_filter_panel.tsx
similarity index 100%
rename from packages/content-management/table_list/src/components/use_tag_filter_panel.tsx
rename to packages/content-management/table_list_view_table/src/components/use_tag_filter_panel.tsx
diff --git a/packages/content-management/table_list/src/constants.ts b/packages/content-management/table_list_view_table/src/constants.ts
similarity index 100%
rename from packages/content-management/table_list/src/constants.ts
rename to packages/content-management/table_list_view_table/src/constants.ts
diff --git a/packages/content-management/table_list/src/index.ts b/packages/content-management/table_list_view_table/src/index.ts
similarity index 81%
rename from packages/content-management/table_list/src/index.ts
rename to packages/content-management/table_list_view_table/src/index.ts
index d1e83d7dd2e93..4f060ea25b9f1 100644
--- a/packages/content-management/table_list/src/index.ts
+++ b/packages/content-management/table_list_view_table/src/index.ts
@@ -6,13 +6,13 @@
  * Side Public License, v 1.
  */
 
-export { TableListView } from './table_list_view';
+export { TableListViewTable } from './table_list_view_table';
 
 export type {
-  Props as TableListViewProps,
+  TableListViewTableProps,
   State as TableListViewState,
   UserContentCommonSchema,
-} from './table_list_view';
+} from './table_list_view_table';
 
 export { TableListViewProvider, TableListViewKibanaProvider } from './services';
 
diff --git a/packages/content-management/table_list/src/mocks.tsx b/packages/content-management/table_list_view_table/src/mocks.tsx
similarity index 99%
rename from packages/content-management/table_list/src/mocks.tsx
rename to packages/content-management/table_list_view_table/src/mocks.tsx
index 3fcf27100e22b..2597e890aff6d 100644
--- a/packages/content-management/table_list/src/mocks.tsx
+++ b/packages/content-management/table_list_view_table/src/mocks.tsx
@@ -82,7 +82,7 @@ export const getStoryServices = (params: Params, action: ActionFn = () => {}) =>
  * consuming component stories.
  */
 export const getStoryArgTypes = () => ({
-  tableListTitle: {
+  title: {
     control: {
       type: 'text',
     },
diff --git a/packages/content-management/table_list/src/reducer.tsx b/packages/content-management/table_list_view_table/src/reducer.tsx
similarity index 99%
rename from packages/content-management/table_list/src/reducer.tsx
rename to packages/content-management/table_list_view_table/src/reducer.tsx
index 84ff8c0308f96..c8486d92caced 100644
--- a/packages/content-management/table_list/src/reducer.tsx
+++ b/packages/content-management/table_list_view_table/src/reducer.tsx
@@ -5,7 +5,7 @@
  * in compliance with, at your election, the Elastic License 2.0 or the Server
  * Side Public License, v 1.
  */
-import type { State, UserContentCommonSchema } from './table_list_view';
+import type { State, UserContentCommonSchema } from './table_list_view_table';
 import type { Action } from './actions';
 
 export function getReducer<T extends UserContentCommonSchema>() {
diff --git a/packages/content-management/table_list/src/services.tsx b/packages/content-management/table_list_view_table/src/services.tsx
similarity index 100%
rename from packages/content-management/table_list/src/services.tsx
rename to packages/content-management/table_list_view_table/src/services.tsx
diff --git a/packages/content-management/table_list/src/table_list_view.test.tsx b/packages/content-management/table_list_view_table/src/table_list_view.test.tsx
similarity index 91%
rename from packages/content-management/table_list/src/table_list_view.test.tsx
rename to packages/content-management/table_list_view_table/src/table_list_view.test.tsx
index 6b0b850cddcd4..f03ab50c5234b 100644
--- a/packages/content-management/table_list/src/table_list_view.test.tsx
+++ b/packages/content-management/table_list_view_table/src/table_list_view.test.tsx
@@ -18,10 +18,10 @@ import type { LocationDescriptor, History } from 'history';
 import { WithServices } from './__jest__';
 import { getTagList } from './mocks';
 import {
-  TableListView,
-  Props as TableListViewProps,
-  UserContentCommonSchema,
-} from './table_list_view';
+  TableListViewTable,
+  type TableListViewTableProps,
+  type UserContentCommonSchema,
+} from './table_list_view_table';
 
 const mockUseEffect = useEffect;
 
@@ -49,18 +49,6 @@ interface Router {
   };
 }
 
-const requiredProps: TableListViewProps = {
-  entityName: 'test',
-  entityNamePlural: 'tests',
-  listingLimit: 500,
-  initialFilter: '',
-  initialPageSize: 20,
-  tableListTitle: 'test title',
-  findItems: jest.fn().mockResolvedValue({ total: 0, hits: [] }),
-  getDetailViewLink: () => 'http://elastic.co',
-  urlStateEnabled: false,
-};
-
 const twoDaysAgo = new Date(new Date().setDate(new Date().getDate() - 2));
 const twoDaysAgoToString = new Date(twoDaysAgo.getTime()).toDateString();
 const yesterday = new Date(new Date().setDate(new Date().getDate() - 1));
@@ -73,6 +61,20 @@ const getActions = (testBed: TestBed) => ({
 });
 
 describe('TableListView', () => {
+  const requiredProps: TableListViewTableProps = {
+    entityName: 'test',
+    entityNamePlural: 'tests',
+    listingLimit: 500,
+    initialFilter: '',
+    initialPageSize: 20,
+    findItems: jest.fn().mockResolvedValue({ total: 0, hits: [] }),
+    getDetailViewLink: () => 'http://elastic.co',
+    urlStateEnabled: false,
+    onFetchSuccess: () => {},
+    tableCaption: 'my caption',
+    setPageDataTestSubject: () => {},
+  };
+
   beforeAll(() => {
     jest.useFakeTimers({ legacyFakeTimers: true });
   });
@@ -81,8 +83,8 @@ describe('TableListView', () => {
     jest.useRealTimers();
   });
 
-  const setup = registerTestBed<string, TableListViewProps>(
-    WithServices<TableListViewProps>(TableListView),
+  const setup = registerTestBed<string, TableListViewTableProps>(
+    WithServices<TableListViewTableProps>(TableListViewTable),
     {
       defaultProps: { ...requiredProps },
       memoryRouter: { wrapComponent: true },
@@ -376,8 +378,8 @@ describe('TableListView', () => {
   });
 
   describe('column sorting', () => {
-    const setupColumnSorting = registerTestBed<string, TableListViewProps>(
-      WithServices<TableListViewProps>(TableListView, {
+    const setupColumnSorting = registerTestBed<string, TableListViewTableProps>(
+      WithServices<TableListViewTableProps>(TableListViewTable, {
         TagList: getTagList({ references: [] }),
       }),
       {
@@ -579,8 +581,8 @@ describe('TableListView', () => {
   });
 
   describe('content editor', () => {
-    const setupInspector = registerTestBed<string, TableListViewProps>(
-      WithServices<TableListViewProps>(TableListView),
+    const setupInspector = registerTestBed<string, TableListViewTableProps>(
+      WithServices<TableListViewTableProps>(TableListViewTable),
       {
         defaultProps: { ...requiredProps },
         memoryRouter: { wrapComponent: true },
@@ -630,8 +632,8 @@ describe('TableListView', () => {
   });
 
   describe('tag filtering', () => {
-    const setupTagFiltering = registerTestBed<string, TableListViewProps>(
-      WithServices<TableListViewProps>(TableListView, {
+    const setupTagFiltering = registerTestBed<string, TableListViewTableProps>(
+      WithServices<TableListViewTableProps>(TableListViewTable, {
         getTagList: () => [
           { id: 'id-tag-1', name: 'tag-1', type: 'tag', description: '', color: '' },
           { id: 'id-tag-2', name: 'tag-2', type: 'tag', description: '', color: '' },
@@ -782,8 +784,8 @@ describe('TableListView', () => {
   describe('url state', () => {
     let router: Router | undefined;
 
-    const setupTagFiltering = registerTestBed<string, TableListViewProps>(
-      WithServices<TableListViewProps>(TableListView, {
+    const setupTagFiltering = registerTestBed<string, TableListViewTableProps>(
+      WithServices<TableListViewTableProps>(TableListViewTable, {
         getTagList: () => [
           { id: 'id-tag-1', name: 'tag-1', type: 'tag', description: '', color: '' },
           { id: 'id-tag-2', name: 'tag-2', type: 'tag', description: '', color: '' },
@@ -1092,7 +1094,7 @@ describe('TableListView', () => {
       },
     ];
 
-    const setupTest = async (props?: Partial<TableListViewProps>) => {
+    const setupTest = async (props?: Partial<TableListViewTableProps>) => {
       let testBed: TestBed | undefined;
       const deleteItems = jest.fn();
       await act(async () => {
@@ -1173,3 +1175,87 @@ describe('TableListView', () => {
     });
   });
 });
+
+describe('TableList', () => {
+  const requiredProps: TableListViewTableProps = {
+    entityName: 'test',
+    entityNamePlural: 'tests',
+    initialPageSize: 20,
+    listingLimit: 500,
+    findItems: jest.fn().mockResolvedValue({ total: 0, hits: [] }),
+    onFetchSuccess: jest.fn(),
+    tableCaption: 'test title',
+    getDetailViewLink: () => '',
+    setPageDataTestSubject: () => {},
+  };
+
+  const setup = registerTestBed<string, TableListViewTableProps>(
+    WithServices<TableListViewTableProps>(TableListViewTable),
+    {
+      defaultProps: { ...requiredProps, refreshListBouncer: false },
+      memoryRouter: { wrapComponent: true },
+    }
+  );
+
+  it('refreshes the list when the bouncer changes', async () => {
+    let testBed: TestBed;
+
+    const findItems = jest.fn().mockResolvedValue({ total: 0, hits: [] });
+
+    await act(async () => {
+      testBed = setup({ findItems });
+    });
+
+    const { component, table } = testBed!;
+
+    findItems.mockClear();
+    expect(findItems).not.toHaveBeenCalled();
+
+    const hits: UserContentCommonSchema[] = [
+      {
+        id: `item`,
+        type: 'dashboard',
+        updatedAt: 'some date',
+        attributes: {
+          title: `Updated title`,
+        },
+        references: [],
+      },
+    ];
+    findItems.mockResolvedValue({ total: hits.length, hits });
+
+    await act(async () => {
+      component.setProps({
+        refreshListBouncer: true,
+      });
+    });
+
+    component.update();
+
+    expect(findItems).toHaveBeenCalledTimes(1);
+
+    const metadata = table.getMetaData('itemsInMemTable');
+
+    expect(metadata.tableCellsValues[0][0]).toBe('Updated title');
+  });
+
+  it('reports successful fetches', async () => {
+    const onFetchSuccess = jest.fn();
+
+    await act(async () => {
+      setup({ onFetchSuccess });
+    });
+
+    expect(onFetchSuccess).toHaveBeenCalled();
+  });
+
+  it('reports the page data test subject', async () => {
+    const setPageDataTestSubject = jest.fn();
+
+    act(() => {
+      setup({ setPageDataTestSubject });
+    });
+
+    expect(setPageDataTestSubject).toHaveBeenCalledWith('testLandingPage');
+  });
+});
diff --git a/packages/content-management/table_list/src/table_list_view.tsx b/packages/content-management/table_list_view_table/src/table_list_view_table.tsx
similarity index 88%
rename from packages/content-management/table_list/src/table_list_view.tsx
rename to packages/content-management/table_list_view_table/src/table_list_view_table.tsx
index 030aaad0527a9..a0d544de5687e 100644
--- a/packages/content-management/table_list/src/table_list_view.tsx
+++ b/packages/content-management/table_list_view_table/src/table_list_view_table.tsx
@@ -6,7 +6,7 @@
  * Side Public License, v 1.
  */
 
-import React, { useReducer, useCallback, useEffect, useRef, useMemo, ReactNode } from 'react';
+import React, { useReducer, useCallback, useEffect, useRef, useMemo } from 'react';
 import useDebounce from 'react-use/lib/useDebounce';
 import {
   EuiBasicTableColumn,
@@ -49,11 +49,11 @@ interface ContentEditorConfig
   enabled?: boolean;
 }
 
-export interface Props<T extends UserContentCommonSchema = UserContentCommonSchema> {
+export interface TableListViewTableProps<
+  T extends UserContentCommonSchema = UserContentCommonSchema
+> {
   entityName: string;
   entityNamePlural: string;
-  tableListTitle: string;
-  tableListDescription?: string;
   listingLimit: number;
   initialFilter?: string;
   initialPageSize: number;
@@ -73,7 +73,6 @@ export interface Props<T extends UserContentCommonSchema = UserContentCommonSche
    * Currently only the "delete" ite action can be disabled.
    */
   rowItemActions?: (obj: T) => RowActions | undefined;
-  children?: ReactNode | undefined;
   findItems(
     searchQuery: string,
     refs?: {
@@ -99,11 +98,7 @@ export interface Props<T extends UserContentCommonSchema = UserContentCommonSche
    * Name for the column containing the "title" value.
    */
   titleColumnName?: string;
-  /**
-   * Additional actions (buttons) to be placed in the page header.
-   * @note only the first two values will be used.
-   */
-  additionalRightSideActions?: ReactNode[];
+
   /**
    * This assumes the content is already wrapped in an outer PageTemplate component.
    * @note Hack! This is being used as a workaround so that this page can be rendered in the Kibana management UI
@@ -111,6 +106,11 @@ export interface Props<T extends UserContentCommonSchema = UserContentCommonSche
    */
   withoutPageTemplateWrapper?: boolean;
   contentEditor?: ContentEditorConfig;
+
+  tableCaption: string;
+  refreshListBouncer?: boolean;
+  onFetchSuccess: () => void;
+  setPageDataTestSubject: (subject: string) => void;
 }
 
 export interface State<T extends UserContentCommonSchema = UserContentCommonSchema> {
@@ -242,9 +242,8 @@ const tableColumnMetadata = {
   },
 } as const;
 
-function TableListViewComp<T extends UserContentCommonSchema>({
-  tableListTitle,
-  tableListDescription,
+function TableListViewTableComp<T extends UserContentCommonSchema>({
+  tableCaption,
   entityName,
   entityNamePlural,
   initialFilter: initialQuery,
@@ -264,11 +263,16 @@ function TableListViewComp<T extends UserContentCommonSchema>({
   onClickTitle,
   id: listingId = 'userContent',
   contentEditor = { enabled: false },
-  children,
   titleColumnName,
-  additionalRightSideActions = [],
   withoutPageTemplateWrapper,
-}: Props<T>) {
+  onFetchSuccess,
+  refreshListBouncer,
+  setPageDataTestSubject,
+}: TableListViewTableProps<T>) {
+  useEffect(() => {
+    setPageDataTestSubject(`${entityName}LandingPage`);
+  }, [entityName, setPageDataTestSubject]);
+
   if (!getDetailViewLink && !onClickTitle) {
     throw new Error(
       `[TableListView] One o["getDetailViewLink" or "onClickTitle"] prop must be provided.`
@@ -366,7 +370,6 @@ function TableListViewComp<T extends UserContentCommonSchema>({
 
   const hasQuery = searchQuery.text !== '';
   const hasNoItems = !isFetchingItems && items.length === 0 && !hasQuery;
-  const pageDataTestSubject = `${entityName}LandingPage`;
   const showFetchError = Boolean(fetchError);
   const showLimitError = !showFetchError && totalItems > listingLimit;
 
@@ -397,6 +400,8 @@ function TableListViewComp<T extends UserContentCommonSchema>({
             response,
           },
         });
+
+        onFetchSuccess();
       }
     } catch (err) {
       dispatch({
@@ -404,7 +409,11 @@ function TableListViewComp<T extends UserContentCommonSchema>({
         data: err,
       });
     }
-  }, [searchQueryParser, findItems, searchQuery.text]);
+  }, [searchQueryParser, searchQuery.text, findItems, onFetchSuccess]);
+
+  useEffect(() => {
+    fetchItems();
+  }, [fetchItems, refreshListBouncer]);
 
   const updateQuery = useCallback(
     (query: Query) => {
@@ -903,7 +912,7 @@ function TableListViewComp<T extends UserContentCommonSchema>({
 
   if (!showFetchError && hasNoItems) {
     return (
-      <PageTemplate panelled isEmptyState={true} data-test-subj={pageDataTestSubject}>
+      <PageTemplate panelled isEmptyState={true}>
         <KibanaPageTemplate.Section
           aria-labelledby={hasInitialFetchReturned ? headingId : undefined}
         >
@@ -920,80 +929,64 @@ function TableListViewComp<T extends UserContentCommonSchema>({
     : 'table-is-loading';
 
   return (
-    <PageTemplate panelled data-test-subj={pageDataTestSubject}>
-      <KibanaPageTemplate.Header
-        pageTitle={<span id={headingId}>{tableListTitle}</span>}
-        description={tableListDescription}
-        rightSideItems={[
-          renderCreateButton() ?? <span />,
-          ...additionalRightSideActions?.slice(0, 2),
-        ]}
-        data-test-subj="top-nav"
-      />
-      <KibanaPageTemplate.Section aria-labelledby={hasInitialFetchReturned ? headingId : undefined}>
-        {/* Any children passed to the component */}
-        {children}
-
-        {/* Too many items error */}
-        {showLimitError && (
-          <ListingLimitWarning
-            canEditAdvancedSettings={canEditAdvancedSettings}
-            advancedSettingsLink={getListingLimitSettingsUrl()}
-            entityNamePlural={entityNamePlural}
-            totalItems={totalItems}
-            listingLimit={listingLimit}
-          />
-        )}
+    <>
+      {/* Too many items error */}
+      {showLimitError && (
+        <ListingLimitWarning
+          canEditAdvancedSettings={canEditAdvancedSettings}
+          advancedSettingsLink={getListingLimitSettingsUrl()}
+          entityNamePlural={entityNamePlural}
+          totalItems={totalItems}
+          listingLimit={listingLimit}
+        />
+      )}
+
+      {/* Error while fetching items */}
+      {showFetchError && renderFetchError()}
+
+      {/* Table of items */}
+      <div data-test-subj={testSubjectState}>
+        <Table<T>
+          dispatch={dispatch}
+          items={items}
+          renderCreateButton={renderCreateButton}
+          isFetchingItems={isFetchingItems}
+          searchQuery={searchQuery}
+          tableColumns={tableColumns}
+          hasUpdatedAtMetadata={hasUpdatedAtMetadata}
+          tableSort={tableSort}
+          tableItemsRowActions={tableItemsRowActions}
+          pagination={pagination}
+          selectedIds={selectedIds}
+          entityName={entityName}
+          entityNamePlural={entityNamePlural}
+          tagsToTableItemMap={tagsToTableItemMap}
+          deleteItems={deleteItems}
+          tableCaption={tableCaption}
+          onTableChange={onTableChange}
+          onTableSearchChange={onTableSearchChange}
+          onSortChange={onSortChange}
+          addOrRemoveIncludeTagFilter={addOrRemoveIncludeTagFilter}
+          addOrRemoveExcludeTagFilter={addOrRemoveExcludeTagFilter}
+          clearTagSelection={clearTagSelection}
+        />
 
-        {/* Error while fetching items */}
-        {showFetchError && renderFetchError()}
-
-        {/* Table of items */}
-        <div data-test-subj={testSubjectState}>
-          <Table<T>
-            dispatch={dispatch}
-            items={items}
-            isFetchingItems={isFetchingItems}
-            searchQuery={searchQuery}
-            tableColumns={tableColumns}
-            hasUpdatedAtMetadata={hasUpdatedAtMetadata}
-            tableSort={tableSort}
-            pagination={pagination}
-            selectedIds={selectedIds}
+        {/* Delete modal */}
+        {showDeleteModal && (
+          <ConfirmDeleteModal<T>
+            isDeletingItems={isDeletingItems}
             entityName={entityName}
             entityNamePlural={entityNamePlural}
-            tagsToTableItemMap={tagsToTableItemMap}
-            deleteItems={deleteItems}
-            tableCaption={tableListTitle}
-            tableItemsRowActions={tableItemsRowActions}
-            onTableChange={onTableChange}
-            onTableSearchChange={onTableSearchChange}
-            onSortChange={onSortChange}
-            addOrRemoveIncludeTagFilter={addOrRemoveIncludeTagFilter}
-            addOrRemoveExcludeTagFilter={addOrRemoveExcludeTagFilter}
-            clearTagSelection={clearTagSelection}
+            items={selectedItems}
+            onConfirm={deleteSelectedItems}
+            onCancel={() => dispatch({ type: 'onCancelDeleteItems' })}
           />
-
-          {/* Delete modal */}
-          {showDeleteModal && (
-            <ConfirmDeleteModal<T>
-              isDeletingItems={isDeletingItems}
-              entityName={entityName}
-              entityNamePlural={entityNamePlural}
-              items={selectedItems}
-              onConfirm={deleteSelectedItems}
-              onCancel={() => dispatch({ type: 'onCancelDeleteItems' })}
-            />
-          )}
-        </div>
-      </KibanaPageTemplate.Section>
-    </PageTemplate>
+        )}
+      </div>
+    </>
   );
 }
 
-const TableListView = React.memo(TableListViewComp) as typeof TableListViewComp;
-
-export { TableListView };
-
-// eslint-disable-next-line import/no-default-export
-export default TableListView;
+export const TableListViewTable = React.memo(
+  TableListViewTableComp
+) as typeof TableListViewTableComp;
diff --git a/packages/content-management/table_list/src/types.ts b/packages/content-management/table_list_view_table/src/types.ts
similarity index 100%
rename from packages/content-management/table_list/src/types.ts
rename to packages/content-management/table_list_view_table/src/types.ts
diff --git a/packages/content-management/table_list/src/use_tags.ts b/packages/content-management/table_list_view_table/src/use_tags.ts
similarity index 98%
rename from packages/content-management/table_list/src/use_tags.ts
rename to packages/content-management/table_list_view_table/src/use_tags.ts
index 345a3484306ff..207304564a829 100644
--- a/packages/content-management/table_list/src/use_tags.ts
+++ b/packages/content-management/table_list_view_table/src/use_tags.ts
@@ -9,7 +9,7 @@ import { useCallback, useMemo } from 'react';
 import { Query } from '@elastic/eui';
 
 import type { Tag } from './types';
-import type { UserContentCommonSchema } from './table_list_view';
+import type { UserContentCommonSchema } from './table_list_view_table';
 
 type QueryUpdater = (query: Query, tag: Tag) => Query;
 
diff --git a/packages/content-management/table_list/src/use_url_state.ts b/packages/content-management/table_list_view_table/src/use_url_state.ts
similarity index 100%
rename from packages/content-management/table_list/src/use_url_state.ts
rename to packages/content-management/table_list_view_table/src/use_url_state.ts
diff --git a/packages/content-management/table_list/tsconfig.json b/packages/content-management/table_list_view_table/tsconfig.json
similarity index 100%
rename from packages/content-management/table_list/tsconfig.json
rename to packages/content-management/table_list_view_table/tsconfig.json
diff --git a/packages/kbn-dom-drag-drop/README.md b/packages/kbn-dom-drag-drop/README.md
index c0145516a811e..892bb60675d92 100644
--- a/packages/kbn-dom-drag-drop/README.md
+++ b/packages/kbn-dom-drag-drop/README.md
@@ -23,9 +23,7 @@ const context = useContext(DragContext);
 In your child application, place a `ChildDragDropProvider` at the root of that, and spread the context into it:
 
 ```js
-<ChildDragDropProvider {...context}>
-  ... your child app here ...
-</ChildDragDropProvider>
+<ChildDragDropProvider {...context}>... your child app here ...</ChildDragDropProvider>
 ```
 
 This enables your child application to share the same drag / drop context as the root application.
@@ -71,9 +69,7 @@ return (
 To create a reordering group, surround the elements from the same group with a `ReorderProvider`:
 
 ```js
-<ReorderProvider id="groupId">
-  ... elements from one group here ...
-</ReorderProvider>
+<ReorderProvider id="groupId">... elements from one group here ...</ReorderProvider>
 ```
 
 The children `DragDrop` components must have props defined as in the example:
@@ -85,8 +81,8 @@ The children `DragDrop` components must have props defined as in the example:
       <DragDrop
         key={f.id}
         draggable
-        dragTypes={["move"]}
-        dropType="reorder"
+        dragType="move"
+        dropTypes={["reorder"]} // generally shouldn't be set until a drag operation has started
         reorderableGroup={fields} // consists all reorderable elements in the group, eg. [{id:'3'}, {id:'5'}, {id:'1'}]
         value={{
           id: f.id,
@@ -102,4 +98,3 @@ The children `DragDrop` components must have props defined as in the example:
   </div>
 </ReorderProvider>
 ```
-
diff --git a/packages/kbn-optimizer/limits.yml b/packages/kbn-optimizer/limits.yml
index 0ea5cfba97e61..db5f45fb4447f 100644
--- a/packages/kbn-optimizer/limits.yml
+++ b/packages/kbn-optimizer/limits.yml
@@ -41,7 +41,7 @@ pageLoadAssetSize:
   enterpriseSearch: 35741
   essSecurity: 16573
   esUiShared: 326654
-  eventAnnotation: 22000
+  eventAnnotation: 48565
   exploratoryView: 74673
   expressionError: 22127
   expressionGauge: 25000
diff --git a/src/plugins/dashboard/public/dashboard_listing/dashboard_listing.test.tsx b/src/plugins/dashboard/public/dashboard_listing/dashboard_listing.test.tsx
index 383b51fbd65fa..99b164433fe41 100644
--- a/src/plugins/dashboard/public/dashboard_listing/dashboard_listing.test.tsx
+++ b/src/plugins/dashboard/public/dashboard_listing/dashboard_listing.test.tsx
@@ -19,16 +19,23 @@ import { DashboardListing, DashboardListingProps } from './dashboard_listing';
  * need to ensure we're passing down the correct props, but the table list view itself doesn't need to be rendered
  * in our tests because it is covered in its package.
  */
-import { TableListView } from '@kbn/content-management-table-list';
-// import { TableListViewKibanaProvider } from '@kbn/content-management-table-list';
-jest.mock('@kbn/content-management-table-list', () => {
-  const originalModule = jest.requireActual('@kbn/content-management-table-list');
+import { TableListView } from '@kbn/content-management-table-list-view';
+// import { TableListViewKibanaProvider } from '@kbn/content-management-table-list-view';
+jest.mock('@kbn/content-management-table-list-view-table', () => {
+  const originalModule = jest.requireActual('@kbn/content-management-table-list-view-table');
   return {
     __esModule: true,
     ...originalModule,
     TableListViewKibanaProvider: jest.fn().mockImplementation(({ children }) => {
       return <>{children}</>;
     }),
+  };
+});
+jest.mock('@kbn/content-management-table-list-view', () => {
+  const originalModule = jest.requireActual('@kbn/content-management-table-list-view-table');
+  return {
+    __esModule: true,
+    ...originalModule,
     TableListView: jest.fn().mockReturnValue(null),
   };
 });
diff --git a/src/plugins/dashboard/public/dashboard_listing/dashboard_listing.tsx b/src/plugins/dashboard/public/dashboard_listing/dashboard_listing.tsx
index 4d867be5c1fd1..57716ee8b525e 100644
--- a/src/plugins/dashboard/public/dashboard_listing/dashboard_listing.tsx
+++ b/src/plugins/dashboard/public/dashboard_listing/dashboard_listing.tsx
@@ -10,11 +10,11 @@ import { FormattedRelative, I18nProvider } from '@kbn/i18n-react';
 import React, { PropsWithChildren, useCallback, useState } from 'react';
 
 import {
-  TableListView,
-  TableListViewKibanaDependencies,
+  type TableListViewKibanaDependencies,
   TableListViewKibanaProvider,
   type UserContentCommonSchema,
-} from '@kbn/content-management-table-list';
+} from '@kbn/content-management-table-list-view-table';
+import { TableListView } from '@kbn/content-management-table-list-view';
 import { ViewMode } from '@kbn/embeddable-plugin/public';
 import { reportPerformanceMetricEvent } from '@kbn/ebt-tools';
 import type { SavedObjectsFindOptionsReference } from '@kbn/core/public';
@@ -233,7 +233,7 @@ export const DashboardListing = ({
           createItem={!showWriteControls ? undefined : createItem}
           editItem={!showWriteControls ? undefined : editItem}
           entityNamePlural={getEntityNamePlural()}
-          tableListTitle={getTableListTitle()}
+          title={getTableListTitle()}
           headingId="dashboardListingHeading"
           initialPageSize={initialPageSize}
           initialFilter={initialFilter}
diff --git a/src/plugins/dashboard/tsconfig.json b/src/plugins/dashboard/tsconfig.json
index 78c7ba0dccd52..a4ca23893c48c 100644
--- a/src/plugins/dashboard/tsconfig.json
+++ b/src/plugins/dashboard/tsconfig.json
@@ -32,7 +32,6 @@
     "@kbn/data-view-editor-plugin",
     "@kbn/unified-search-plugin",
     "@kbn/shared-ux-page-analytics-no-data",
-    "@kbn/content-management-table-list",
     "@kbn/content-management-plugin",
     "@kbn/content-management-utils",
     "@kbn/i18n-react",
@@ -60,7 +59,9 @@
     "@kbn/core-saved-objects-server",
     "@kbn/core-saved-objects-utils-server",
     "@kbn/object-versioning",
-    "@kbn/core-saved-objects-api-server"
+    "@kbn/core-saved-objects-api-server",
+    "@kbn/content-management-table-list-view",
+    "@kbn/content-management-table-list-view-table"
   ],
   "exclude": ["target/**/*"]
 }
diff --git a/src/plugins/event_annotation/.i18nrc.json b/src/plugins/event_annotation/.i18nrc.json
new file mode 100755
index 0000000000000..0be009b9d4364
--- /dev/null
+++ b/src/plugins/event_annotation/.i18nrc.json
@@ -0,0 +1,6 @@
+{
+  "prefix": "eventAnnotations",
+  "paths": {
+    "eventAnnotations": "."
+  }
+}
diff --git a/src/plugins/event_annotation/common/constants.ts b/src/plugins/event_annotation/common/constants.ts
index 04255cee00c22..08398d53119cc 100644
--- a/src/plugins/event_annotation/common/constants.ts
+++ b/src/plugins/event_annotation/common/constants.ts
@@ -25,3 +25,7 @@ export const AvailableAnnotationIcons = {
 } as const;
 
 export const EVENT_ANNOTATION_GROUP_TYPE = 'event-annotation-group';
+
+export const ANNOTATIONS_LISTING_VIEW_ID = 'annotations';
+
+export const EVENT_ANNOTATION_APP_NAME = 'event-annotations';
diff --git a/src/plugins/event_annotation/common/create_copied_annotation.ts b/src/plugins/event_annotation/common/create_copied_annotation.ts
new file mode 100644
index 0000000000000..caab8923c855e
--- /dev/null
+++ b/src/plugins/event_annotation/common/create_copied_annotation.ts
@@ -0,0 +1,24 @@
+/*
+ * 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 { getDefaultManualAnnotation } from './manual_event_annotation';
+import { EventAnnotationConfig } from './types';
+
+export const createCopiedAnnotation = (
+  newId: string,
+  timestamp: string,
+  source?: EventAnnotationConfig
+): EventAnnotationConfig => {
+  if (!source) {
+    return getDefaultManualAnnotation(newId, timestamp);
+  }
+  return {
+    ...source,
+    id: newId,
+  };
+};
diff --git a/src/plugins/event_annotation/common/fetch_event_annotations/utils.ts b/src/plugins/event_annotation/common/fetch_event_annotations/utils.ts
index b8658b8b81565..88979fce32002 100644
--- a/src/plugins/event_annotation/common/fetch_event_annotations/utils.ts
+++ b/src/plugins/event_annotation/common/fetch_event_annotations/utils.ts
@@ -12,6 +12,7 @@ import { omit, pick } from 'lodash';
 import dateMath from '@kbn/datemath';
 import moment from 'moment';
 import { IUiSettingsClient } from '@kbn/core-ui-settings-browser';
+import { LineStyle } from '@kbn/visualization-ui-components/common/types';
 import {
   ManualEventAnnotationOutput,
   ManualPointEventAnnotationOutput,
@@ -22,7 +23,6 @@ import {
   annotationColumns,
   AvailableAnnotationIcon,
   EventAnnotationOutput,
-  LineStyle,
   PointStyleProps,
 } from '../types';
 
diff --git a/src/plugins/event_annotation/common/index.ts b/src/plugins/event_annotation/common/index.ts
index f7a62d4f3918a..0341a9e5ed4a2 100644
--- a/src/plugins/event_annotation/common/index.ts
+++ b/src/plugins/event_annotation/common/index.ts
@@ -18,8 +18,16 @@ export type {
   QueryPointEventAnnotationArgs,
   QueryPointEventAnnotationOutput,
 } from './query_point_event_annotation/types';
-export { manualPointEventAnnotation, manualRangeEventAnnotation } from './manual_event_annotation';
-export { queryPointEventAnnotation } from './query_point_event_annotation';
+export {
+  manualPointEventAnnotation,
+  manualRangeEventAnnotation,
+  getDefaultManualAnnotation,
+} from './manual_event_annotation';
+export {
+  queryPointEventAnnotation,
+  getDefaultQueryAnnotation,
+} from './query_point_event_annotation';
+export { createCopiedAnnotation } from './create_copied_annotation';
 export { eventAnnotationGroup } from './event_annotation_group';
 export type { EventAnnotationGroupArgs } from './event_annotation_group';
 
@@ -36,4 +44,4 @@ export type {
   EventAnnotationGroupAttributes,
 } from './types';
 
-export { EVENT_ANNOTATION_GROUP_TYPE } from './constants';
+export { EVENT_ANNOTATION_GROUP_TYPE, ANNOTATIONS_LISTING_VIEW_ID } from './constants';
diff --git a/src/plugins/event_annotation/common/manual_event_annotation/index.ts b/src/plugins/event_annotation/common/manual_event_annotation/index.ts
index a178211164822..12d00cefc75b4 100644
--- a/src/plugins/event_annotation/common/manual_event_annotation/index.ts
+++ b/src/plugins/event_annotation/common/manual_event_annotation/index.ts
@@ -9,6 +9,7 @@
 import type { ExpressionFunctionDefinition } from '@kbn/expressions-plugin/common';
 import { i18n } from '@kbn/i18n';
 import { AvailableAnnotationIcons } from '../constants';
+import { EventAnnotationConfig } from '../types';
 
 import type {
   ManualRangeEventAnnotationArgs,
@@ -163,3 +164,24 @@ export const manualRangeEventAnnotation: ExpressionFunctionDefinition<
     };
   },
 };
+
+export const defaultAnnotationLabel = i18n.translate(
+  'eventAnnotation.manualAnnotation.defaultAnnotationLabel',
+  {
+    defaultMessage: 'Event',
+  }
+);
+
+export const getDefaultManualAnnotation = (
+  id: string,
+  timestamp: string
+): EventAnnotationConfig => ({
+  label: defaultAnnotationLabel,
+  type: 'manual',
+  key: {
+    type: 'point_in_time',
+    timestamp,
+  },
+  icon: 'triangle',
+  id,
+});
diff --git a/src/plugins/event_annotation/common/query_point_event_annotation/index.ts b/src/plugins/event_annotation/common/query_point_event_annotation/index.ts
index cb9ba882a9f89..6c0ee0bd62eb7 100644
--- a/src/plugins/event_annotation/common/query_point_event_annotation/index.ts
+++ b/src/plugins/event_annotation/common/query_point_event_annotation/index.ts
@@ -9,6 +9,7 @@
 import type { ExpressionFunctionDefinition } from '@kbn/expressions-plugin/common';
 import { i18n } from '@kbn/i18n';
 import { AvailableAnnotationIcons } from '../constants';
+import { EventAnnotationConfig } from '../types';
 
 import type { QueryPointEventAnnotationArgs, QueryPointEventAnnotationOutput } from './types';
 
@@ -111,3 +112,22 @@ export const queryPointEventAnnotation: ExpressionFunctionDefinition<
     };
   },
 };
+
+export const getDefaultQueryAnnotation = (
+  id: string,
+  fieldName: string,
+  timeField: string
+): EventAnnotationConfig => ({
+  filter: {
+    type: 'kibana_query',
+    query: `${fieldName}: *`,
+    language: 'kuery',
+  },
+  timeField,
+  type: 'query',
+  key: {
+    type: 'point_in_time',
+  },
+  id,
+  label: `${fieldName}: *`,
+});
diff --git a/src/plugins/event_annotation/common/types.ts b/src/plugins/event_annotation/common/types.ts
index b3e704ef647e5..9bdd4cd283523 100644
--- a/src/plugins/event_annotation/common/types.ts
+++ b/src/plugins/event_annotation/common/types.ts
@@ -6,9 +6,11 @@
  * Side Public License, v 1.
  */
 
+import type { UserContentCommonSchema } from '@kbn/content-management-table-list-view';
 import { DataViewSpec, KibanaQueryOutput } from '@kbn/data-plugin/common';
 import { DatatableColumn } from '@kbn/expressions-plugin/common';
 import { $Values } from '@kbn/utility-types';
+import { LineStyle } from '@kbn/visualization-ui-components/common/types';
 import { AvailableAnnotationIcons } from './constants';
 import {
   ManualEventAnnotationOutput,
@@ -20,7 +22,6 @@ import {
   QueryPointEventAnnotationOutput,
 } from './query_point_event_annotation/types';
 
-export type LineStyle = 'solid' | 'dashed' | 'dotted';
 export type Fill = 'inside' | 'outside' | 'none';
 export type ManualAnnotationType = 'manual';
 export type QueryAnnotationType = 'query';
@@ -85,10 +86,9 @@ export type EventAnnotationConfig =
 export interface EventAnnotationGroupAttributes {
   title: string;
   description: string;
-  tags: string[];
   ignoreGlobalFilters: boolean;
   annotations: EventAnnotationConfig[];
-  dataViewSpec?: DataViewSpec;
+  dataViewSpec?: DataViewSpec | null;
 }
 
 export interface EventAnnotationGroupConfig {
@@ -101,6 +101,10 @@ export interface EventAnnotationGroupConfig {
   dataViewSpec?: DataViewSpec;
 }
 
+export type EventAnnotationGroupContent = UserContentCommonSchema & {
+  attributes: { indexPatternId: string; dataViewSpec?: DataViewSpec };
+};
+
 export type EventAnnotationArgs =
   | ManualPointEventAnnotationArgs
   | ManualRangeEventAnnotationArgs
diff --git a/src/plugins/event_annotation/jest.config.js b/src/plugins/event_annotation/jest.config.js
index 61269c91a8bfd..e7e58e57eb37d 100644
--- a/src/plugins/event_annotation/jest.config.js
+++ b/src/plugins/event_annotation/jest.config.js
@@ -10,7 +10,10 @@ module.exports = {
   preset: '@kbn/test',
   rootDir: '../../..',
   roots: ['<rootDir>/src/plugins/event_annotation'],
-  coverageDirectory: '<rootDir>/target/kibana-coverage/jest/src/plugins/event_ann',
+  coverageDirectory: '<rootDir>/target/kibana-coverage/jest/src/plugins/event_annotation',
   coverageReporters: ['text', 'html'],
-  collectCoverageFrom: ['<rootDir>/src/plugins/event_ann/{common,public,server}/**/*.{ts,tsx}'],
+  collectCoverageFrom: [
+    '<rootDir>/src/plugins/event_annotation/{common,public,server}/**/*.{ts,tsx}',
+  ],
+  setupFiles: ['jest-canvas-mock'],
 };
diff --git a/src/plugins/event_annotation/kibana.jsonc b/src/plugins/event_annotation/kibana.jsonc
index b4df5edf135af..1099c467d502f 100644
--- a/src/plugins/event_annotation/kibana.jsonc
+++ b/src/plugins/event_annotation/kibana.jsonc
@@ -11,10 +11,23 @@
       "expressions",
       "savedObjectsManagement",
       "data",
+      "presentationUtil",
+      "visualizations",
+      "dataViews",
+      "unifiedSearch",
+      "kibanaUtils",
+      "visualizationUiComponents"
+    ],
+    "optionalPlugins": [
+      "savedObjectsTagging",
     ],
     "requiredBundles": [
+      "data",
       "savedObjectsFinder",
-      "dataViews"
+      "dataViews",
+      "kibanaReact",
+      "visualizationUiComponents",
+      "unifiedFieldList"
     ],
     "extraPublicDirs": [
       "common"
diff --git a/src/plugins/event_annotation/public/components/__snapshots__/group_editor_flyout.test.tsx.snap b/src/plugins/event_annotation/public/components/__snapshots__/group_editor_flyout.test.tsx.snap
new file mode 100644
index 0000000000000..49def88aecb70
--- /dev/null
+++ b/src/plugins/event_annotation/public/components/__snapshots__/group_editor_flyout.test.tsx.snap
@@ -0,0 +1,38 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`group editor flyout renders controls 1`] = `
+Object {
+  "TagSelector": [MockFunction],
+  "createDataView": [MockFunction],
+  "dataViews": Array [
+    Object {
+      "id": "some-id",
+      "title": "My Data View",
+    },
+  ],
+  "group": Object {
+    "annotations": Array [
+      Object {
+        "icon": "triangle",
+        "id": "my-id",
+        "key": Object {
+          "timestamp": "some-timestamp",
+          "type": "point_in_time",
+        },
+        "label": "Event",
+        "type": "manual",
+      },
+    ],
+    "description": "",
+    "ignoreGlobalFilters": false,
+    "indexPatternId": "some-id",
+    "tags": Array [],
+    "title": "My group",
+  },
+  "queryInputServices": Object {},
+  "selectedAnnotation": undefined,
+  "setSelectedAnnotation": [Function],
+  "showValidation": false,
+  "update": [MockFunction],
+}
+`;
diff --git a/src/plugins/event_annotation/public/components/annotation_editor_controls/annotation_editor_controls.tsx b/src/plugins/event_annotation/public/components/annotation_editor_controls/annotation_editor_controls.tsx
new file mode 100644
index 0000000000000..e88aa0d72558a
--- /dev/null
+++ b/src/plugins/event_annotation/public/components/annotation_editor_controls/annotation_editor_controls.tsx
@@ -0,0 +1,390 @@
+/*
+ * 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 './index.scss';
+import { isFieldLensCompatible } from '@kbn/visualization-ui-components/public';
+import React, { useCallback, useEffect } from 'react';
+import { i18n } from '@kbn/i18n';
+import { EuiFormRow, EuiSwitch, EuiSwitchEvent, EuiButtonGroup, EuiSpacer } from '@elastic/eui';
+import {
+  IconSelectSetting,
+  DimensionEditorSection,
+  NameInput,
+  ColorPicker,
+  LineStyleSettings,
+  TextDecorationSetting,
+  FieldPicker,
+  FieldOption,
+  type QueryInputServices,
+} from '@kbn/visualization-ui-components/public';
+import type { FieldOptionValue } from '@kbn/visualization-ui-components/public';
+import { DataView } from '@kbn/data-views-plugin/common';
+import { useExistingFieldsReader } from '@kbn/unified-field-list-plugin/public';
+import moment from 'moment';
+import { htmlIdGenerator } from '@elastic/eui';
+import { isQueryAnnotationConfig, isRangeAnnotationConfig } from '../..';
+import {
+  AvailableAnnotationIcon,
+  EventAnnotationConfig,
+  PointInTimeEventAnnotationConfig,
+  QueryPointEventAnnotationConfig,
+} from '../../../common';
+import {
+  defaultAnnotationColor,
+  defaultAnnotationLabel,
+  defaultAnnotationRangeColor,
+  defaultRangeAnnotationLabel,
+  toLineAnnotationColor,
+} from './helpers';
+import { annotationsIconSet } from './icon_set';
+import { sanitizeProperties } from './helpers';
+import { TooltipSection } from './tooltip_annotation_panel';
+import { ConfigPanelManualAnnotation } from './manual_annotation_panel';
+import { ConfigPanelQueryAnnotation } from './query_annotation_panel';
+
+export interface Props {
+  annotation: EventAnnotationConfig;
+  onAnnotationChange: (annotation: EventAnnotationConfig) => void;
+  dataView: DataView;
+  getDefaultRangeEnd: (rangeStart: string) => string;
+  calendarClassName?: string;
+  queryInputServices: QueryInputServices;
+  appName: string;
+}
+
+export const idPrefix = htmlIdGenerator()();
+
+const AnnotationEditorControls = ({
+  annotation: currentAnnotation,
+  onAnnotationChange,
+  dataView,
+  getDefaultRangeEnd,
+  calendarClassName,
+  queryInputServices,
+  appName,
+}: Props) => {
+  const { hasFieldData } = useExistingFieldsReader();
+
+  const isQueryBased = isQueryAnnotationConfig(currentAnnotation);
+  const isRange = isRangeAnnotationConfig(currentAnnotation);
+
+  const [queryInputShouldOpen, setQueryInputShouldOpen] = React.useState(false);
+  useEffect(() => {
+    setQueryInputShouldOpen(!isQueryBased);
+  }, [isQueryBased]);
+
+  const update = useCallback(
+    <T extends EventAnnotationConfig>(newAnnotation: Partial<T> | undefined) =>
+      newAnnotation &&
+      onAnnotationChange(sanitizeProperties({ ...currentAnnotation, ...newAnnotation })),
+    [currentAnnotation, onAnnotationChange]
+  );
+
+  return (
+    <>
+      <DimensionEditorSection
+        title={i18n.translate('eventAnnotation.xyChart.placement', {
+          defaultMessage: 'Placement',
+        })}
+      >
+        <EuiFormRow
+          label={i18n.translate('eventAnnotation.xyChart.annotationDate.placementType', {
+            defaultMessage: 'Placement type',
+          })}
+          display="rowCompressed"
+          fullWidth
+        >
+          <EuiButtonGroup
+            legend={i18n.translate('eventAnnotation.xyChart.annotationDate.placementType', {
+              defaultMessage: 'Placement type',
+            })}
+            data-test-subj="lns-xyAnnotation-placementType"
+            name="placementType"
+            buttonSize="compressed"
+            options={[
+              {
+                id: `lens_xyChart_annotation_manual`,
+                label: i18n.translate('eventAnnotation.xyChart.annotation.manual', {
+                  defaultMessage: 'Static date',
+                }),
+                'data-test-subj': 'lnsXY_annotation_manual',
+              },
+              {
+                id: `lens_xyChart_annotation_query`,
+                label: i18n.translate('eventAnnotation.xyChart.annotation.query', {
+                  defaultMessage: 'Custom query',
+                }),
+                'data-test-subj': 'lnsXY_annotation_query',
+              },
+            ]}
+            idSelected={`lens_xyChart_annotation_${currentAnnotation?.type}`}
+            onChange={(id) => {
+              const typeFromId = id.replace(
+                'lens_xyChart_annotation_',
+                ''
+              ) as EventAnnotationConfig['type'];
+              if (currentAnnotation?.type === typeFromId) {
+                return;
+              }
+              if (typeFromId === 'query') {
+                // If coming from a range type, it requires some additional resets
+                const additionalRangeResets = isRangeAnnotationConfig(currentAnnotation)
+                  ? {
+                      label:
+                        currentAnnotation.label === defaultRangeAnnotationLabel
+                          ? defaultAnnotationLabel
+                          : currentAnnotation.label,
+                      color: toLineAnnotationColor(currentAnnotation.color),
+                    }
+                  : {};
+                return update({
+                  type: typeFromId,
+                  timeField:
+                    (dataView.timeFieldName ||
+                      // fallback to the first avaiable date field in the dataView
+                      dataView.fields
+                        .filter(isFieldLensCompatible)
+                        .find(({ type: fieldType }) => fieldType === 'date')?.displayName) ??
+                    '',
+                  key: { type: 'point_in_time' },
+                  ...additionalRangeResets,
+                });
+              }
+              // From query to manual annotation
+              return update<PointInTimeEventAnnotationConfig>({
+                type: typeFromId,
+                key: { type: 'point_in_time', timestamp: moment().toISOString() },
+              });
+            }}
+            isFullWidth
+          />
+        </EuiFormRow>
+        {isQueryBased ? (
+          <ConfigPanelQueryAnnotation
+            annotation={currentAnnotation}
+            onChange={update}
+            dataView={dataView}
+            queryInputShouldOpen={queryInputShouldOpen}
+            queryInputServices={queryInputServices}
+            appName={appName}
+          />
+        ) : (
+          <ConfigPanelManualAnnotation
+            annotation={currentAnnotation}
+            onChange={update}
+            getDefaultRangeEnd={getDefaultRangeEnd}
+            calendarClassName={calendarClassName}
+          />
+        )}
+      </DimensionEditorSection>
+      <DimensionEditorSection
+        title={i18n.translate('eventAnnotation.xyChart.appearance', {
+          defaultMessage: 'Appearance',
+        })}
+      >
+        <NameInput
+          value={currentAnnotation?.label || defaultAnnotationLabel}
+          defaultValue={defaultAnnotationLabel}
+          onChange={(value) => {
+            update({ label: value });
+          }}
+        />
+        {!isRange && (
+          <>
+            <IconSelectSetting<AvailableAnnotationIcon>
+              currentIcon={currentAnnotation.icon}
+              setIcon={(icon) => update({ icon })}
+              defaultIcon="triangle"
+              customIconSet={annotationsIconSet}
+            />
+            <TextDecorationSetting
+              idPrefix={idPrefix}
+              setConfig={update}
+              currentConfig={currentAnnotation}
+              isQueryBased={isQueryBased}
+            >
+              {(textDecorationSelected) => {
+                if (textDecorationSelected !== 'field') {
+                  return null;
+                }
+                const options = dataView.fields
+                  .filter(isFieldLensCompatible)
+                  .filter(({ displayName, type }) => displayName && type !== 'document')
+                  .map(
+                    (field) =>
+                      ({
+                        label: field.displayName,
+                        value: {
+                          type: 'field',
+                          field: field.name,
+                          dataType: field.type,
+                        },
+                        exists: hasFieldData(dataView.id!, field.name),
+                        compatible: true,
+                        'data-test-subj': `lnsXY-annotation-fieldOption-${field.name}`,
+                      } as FieldOption<FieldOptionValue>)
+                  );
+                const selectedField = (currentAnnotation as QueryPointEventAnnotationConfig)
+                  .textField;
+
+                const fieldIsValid = selectedField
+                  ? Boolean(dataView.getFieldByName(selectedField))
+                  : true;
+
+                return (
+                  <>
+                    <EuiSpacer size="xs" />
+                    <FieldPicker
+                      selectedOptions={
+                        selectedField
+                          ? [
+                              {
+                                label: selectedField,
+                                value: { type: 'field', field: selectedField },
+                              },
+                            ]
+                          : []
+                      }
+                      options={options}
+                      onChoose={function (choice: FieldOptionValue | undefined): void {
+                        if (choice) {
+                          update({ textField: choice.field, textVisibility: true });
+                        }
+                      }}
+                      fieldIsInvalid={!fieldIsValid}
+                      data-test-subj="lnsXY-annotation-query-based-text-decoration-field-picker"
+                      autoFocus={!selectedField}
+                    />
+                  </>
+                );
+              }}
+            </TextDecorationSetting>
+            <LineStyleSettings
+              idPrefix={idPrefix}
+              setConfig={update}
+              currentConfig={{
+                lineStyle: currentAnnotation.lineStyle,
+                lineWidth: currentAnnotation.lineWidth,
+              }}
+            />
+          </>
+        )}
+        {isRange && (
+          <EuiFormRow
+            label={i18n.translate('eventAnnotation.xyChart.fillStyle', {
+              defaultMessage: 'Fill',
+            })}
+            display="columnCompressed"
+            fullWidth
+          >
+            <EuiButtonGroup
+              legend={i18n.translate('eventAnnotation.xyChart.fillStyle', {
+                defaultMessage: 'Fill',
+              })}
+              data-test-subj="lns-xyAnnotation-fillStyle"
+              name="fillStyle"
+              buttonSize="compressed"
+              options={[
+                {
+                  id: `lens_xyChart_fillStyle_inside`,
+                  label: i18n.translate('eventAnnotation.xyChart.fillStyle.inside', {
+                    defaultMessage: 'Inside',
+                  }),
+                  'data-test-subj': 'lnsXY_fillStyle_inside',
+                },
+                {
+                  id: `lens_xyChart_fillStyle_outside`,
+                  label: i18n.translate('eventAnnotation.xyChart.fillStyle.outside', {
+                    defaultMessage: 'Outside',
+                  }),
+                  'data-test-subj': 'lnsXY_fillStyle_inside',
+                },
+              ]}
+              idSelected={`lens_xyChart_fillStyle_${
+                Boolean(currentAnnotation?.outside) ? 'outside' : 'inside'
+              }`}
+              onChange={(id) => {
+                update({
+                  outside: id === `lens_xyChart_fillStyle_outside`,
+                });
+              }}
+              isFullWidth
+            />
+          </EuiFormRow>
+        )}
+
+        <ColorPicker
+          overwriteColor={currentAnnotation.color}
+          defaultColor={isRange ? defaultAnnotationRangeColor : defaultAnnotationColor}
+          showAlpha={isRange}
+          setConfig={update}
+          disableHelpTooltip
+          label={i18n.translate('eventAnnotation.xyChart.lineColor.label', {
+            defaultMessage: 'Color',
+          })}
+        />
+        <ConfigPanelGenericSwitch
+          label={i18n.translate('eventAnnotation.xyChart.annotation.hide', {
+            defaultMessage: 'Hide annotation',
+          })}
+          data-test-subj="lns-annotations-hide-annotation"
+          value={Boolean(currentAnnotation.isHidden)}
+          onChange={(ev) => update({ isHidden: ev.target.checked })}
+        />
+      </DimensionEditorSection>
+      {isQueryBased && currentAnnotation && (
+        <DimensionEditorSection
+          title={i18n.translate('eventAnnotation.xyChart.tooltip', {
+            defaultMessage: 'Tooltip',
+          })}
+        >
+          <EuiFormRow
+            display="rowCompressed"
+            className="lnsRowCompressedMargin"
+            fullWidth
+            label={i18n.translate('eventAnnotation.xyChart.annotation.tooltip', {
+              defaultMessage: 'Show additional fields',
+            })}
+          >
+            <TooltipSection
+              currentConfig={currentAnnotation}
+              setConfig={update}
+              dataView={dataView}
+            />
+          </EuiFormRow>
+        </DimensionEditorSection>
+      )}
+    </>
+  );
+};
+
+const ConfigPanelGenericSwitch = ({
+  label,
+  ['data-test-subj']: dataTestSubj,
+  value,
+  onChange,
+}: {
+  label: string;
+  'data-test-subj': string;
+  value: boolean;
+  onChange: (event: EuiSwitchEvent) => void;
+}) => (
+  <EuiFormRow label={label} display="columnCompressedSwitch" fullWidth>
+    <EuiSwitch
+      compressed
+      label={label}
+      showLabel={false}
+      data-test-subj={dataTestSubj}
+      checked={value}
+      onChange={onChange}
+    />
+  </EuiFormRow>
+);
+
+// eslint-disable-next-line import/no-default-export
+export default AnnotationEditorControls;
diff --git a/src/plugins/event_annotation/public/components/annotation_editor_controls/helpers.ts b/src/plugins/event_annotation/public/components/annotation_editor_controls/helpers.ts
new file mode 100644
index 0000000000000..f823dab7e7357
--- /dev/null
+++ b/src/plugins/event_annotation/public/components/annotation_editor_controls/helpers.ts
@@ -0,0 +1,93 @@
+/*
+ * 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 { transparentize } from '@elastic/eui';
+import { pick } from 'lodash';
+import { euiLightVars } from '@kbn/ui-theme';
+import { i18n } from '@kbn/i18n';
+import chroma from 'chroma-js';
+import { isQueryAnnotationConfig, isRangeAnnotationConfig } from '../..';
+import type {
+  EventAnnotationConfig,
+  RangeEventAnnotationConfig,
+  PointInTimeEventAnnotationConfig,
+  QueryPointEventAnnotationConfig,
+} from '../../../common';
+
+export const defaultAnnotationColor = euiLightVars.euiColorAccent;
+// Do not compute it live as dependencies will add tens of Kbs to the plugin
+export const defaultAnnotationRangeColor = `#F04E981A`; // defaultAnnotationColor with opacity 0.1
+
+export const defaultAnnotationLabel = i18n.translate(
+  'eventAnnotation.xyChart.defaultAnnotationLabel',
+  {
+    defaultMessage: 'Event',
+  }
+);
+
+export const defaultRangeAnnotationLabel = i18n.translate(
+  'eventAnnotation.xyChart.defaultRangeAnnotationLabel',
+  {
+    defaultMessage: 'Event range',
+  }
+);
+
+export const toRangeAnnotationColor = (color = defaultAnnotationColor) => {
+  return chroma(transparentize(color, 0.1)).hex().toUpperCase();
+};
+
+export const toLineAnnotationColor = (color = defaultAnnotationRangeColor) => {
+  return chroma(transparentize(color, 1)).hex().toUpperCase();
+};
+
+export const sanitizeProperties = (annotation: EventAnnotationConfig) => {
+  if (isRangeAnnotationConfig(annotation)) {
+    const rangeAnnotation: RangeEventAnnotationConfig = pick(annotation, [
+      'type',
+      'label',
+      'key',
+      'id',
+      'isHidden',
+      'color',
+      'outside',
+    ]);
+    return rangeAnnotation;
+  }
+  if (isQueryAnnotationConfig(annotation)) {
+    const lineAnnotation: QueryPointEventAnnotationConfig = pick(annotation, [
+      'type',
+      'id',
+      'label',
+      'key',
+      'timeField',
+      'isHidden',
+      'lineStyle',
+      'lineWidth',
+      'color',
+      'icon',
+      'textVisibility',
+      'textField',
+      'filter',
+      'extraFields',
+    ]);
+    return lineAnnotation;
+  }
+  const lineAnnotation: PointInTimeEventAnnotationConfig = pick(annotation, [
+    'type',
+    'id',
+    'label',
+    'key',
+    'isHidden',
+    'lineStyle',
+    'lineWidth',
+    'color',
+    'icon',
+    'textVisibility',
+  ]);
+  return lineAnnotation;
+};
diff --git a/src/plugins/event_annotation/public/components/annotation_editor_controls/icon_set.ts b/src/plugins/event_annotation/public/components/annotation_editor_controls/icon_set.ts
new file mode 100644
index 0000000000000..e4ea40acb48ed
--- /dev/null
+++ b/src/plugins/event_annotation/public/components/annotation_editor_controls/icon_set.ts
@@ -0,0 +1,111 @@
+/*
+ * 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 { i18n } from '@kbn/i18n';
+import { IconTriangle, IconCircle } from '@kbn/chart-icons';
+import type { IconSet } from '@kbn/visualization-ui-components/public';
+import { AvailableAnnotationIcon } from '../../../common';
+
+export const annotationsIconSet: IconSet<AvailableAnnotationIcon> = [
+  {
+    value: 'asterisk',
+    label: i18n.translate('eventAnnotation.xyChart.iconSelect.asteriskIconLabel', {
+      defaultMessage: 'Asterisk',
+    }),
+  },
+  {
+    value: 'alert',
+    label: i18n.translate('eventAnnotation.xyChart.iconSelect.alertIconLabel', {
+      defaultMessage: 'Alert',
+    }),
+  },
+  {
+    value: 'bell',
+    label: i18n.translate('eventAnnotation.xyChart.iconSelect.bellIconLabel', {
+      defaultMessage: 'Bell',
+    }),
+  },
+  {
+    value: 'bolt',
+    label: i18n.translate('eventAnnotation.xyChart.iconSelect.boltIconLabel', {
+      defaultMessage: 'Bolt',
+    }),
+  },
+  {
+    value: 'bug',
+    label: i18n.translate('eventAnnotation.xyChart.iconSelect.bugIconLabel', {
+      defaultMessage: 'Bug',
+    }),
+  },
+  {
+    value: 'circle',
+    label: i18n.translate('eventAnnotation.xyChart.iconSelect.circleIconLabel', {
+      defaultMessage: 'Circle',
+    }),
+    icon: IconCircle,
+    canFill: true,
+  },
+
+  {
+    value: 'editorComment',
+    label: i18n.translate('eventAnnotation.xyChart.iconSelect.commentIconLabel', {
+      defaultMessage: 'Comment',
+    }),
+  },
+  {
+    value: 'flag',
+    label: i18n.translate('eventAnnotation.xyChart.iconSelect.flagIconLabel', {
+      defaultMessage: 'Flag',
+    }),
+  },
+  {
+    value: 'heart',
+    label: i18n.translate('eventAnnotation.xyChart.iconSelect.heartLabel', {
+      defaultMessage: 'Heart',
+    }),
+  },
+  {
+    value: 'mapMarker',
+    label: i18n.translate('eventAnnotation.xyChart.iconSelect.mapMarkerLabel', {
+      defaultMessage: 'Map Marker',
+    }),
+  },
+  {
+    value: 'pinFilled',
+    label: i18n.translate('eventAnnotation.xyChart.iconSelect.mapPinLabel', {
+      defaultMessage: 'Map Pin',
+    }),
+  },
+  {
+    value: 'starEmpty',
+    label: i18n.translate('eventAnnotation.xyChart.iconSelect.starLabel', {
+      defaultMessage: 'Star',
+    }),
+  },
+  {
+    value: 'starFilled',
+    label: i18n.translate('eventAnnotation.xyChart.iconSelect.starFilledLabel', {
+      defaultMessage: 'Star filled',
+    }),
+  },
+  {
+    value: 'tag',
+    label: i18n.translate('eventAnnotation.xyChart.iconSelect.tagIconLabel', {
+      defaultMessage: 'Tag',
+    }),
+  },
+  {
+    value: 'triangle',
+    label: i18n.translate('eventAnnotation.xyChart.iconSelect.triangleIconLabel', {
+      defaultMessage: 'Triangle',
+    }),
+    icon: IconTriangle,
+    shouldRotate: true,
+    canFill: true,
+  },
+];
diff --git a/x-pack/plugins/lens/public/visualizations/xy/xy_config_panel/annotations_config_panel/index.scss b/src/plugins/event_annotation/public/components/annotation_editor_controls/index.scss
similarity index 100%
rename from x-pack/plugins/lens/public/visualizations/xy/xy_config_panel/annotations_config_panel/index.scss
rename to src/plugins/event_annotation/public/components/annotation_editor_controls/index.scss
diff --git a/src/plugins/event_annotation/public/components/annotation_editor_controls/index.test.tsx b/src/plugins/event_annotation/public/components/annotation_editor_controls/index.test.tsx
new file mode 100644
index 0000000000000..bfbd8a5d65b44
--- /dev/null
+++ b/src/plugins/event_annotation/public/components/annotation_editor_controls/index.test.tsx
@@ -0,0 +1,435 @@
+/*
+ * 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 { DataView, DataViewField, IIndexPatternFieldList } from '@kbn/data-views-plugin/common';
+import AnnotationEditorControls from './annotation_editor_controls';
+
+import React from 'react';
+import { mount } from 'enzyme';
+import { EventAnnotationConfig, RangeEventAnnotationConfig } from '../../../common';
+import { QueryInputServices } from '@kbn/visualization-ui-components/public';
+import moment from 'moment';
+import { act } from 'react-dom/test-utils';
+import { EuiButtonGroup } from '@elastic/eui';
+
+jest.mock('@kbn/unified-search-plugin/public', () => ({
+  QueryStringInput: () => {
+    return 'QueryStringInput';
+  },
+}));
+
+const customLineStaticAnnotation: EventAnnotationConfig = {
+  id: 'ann1',
+  type: 'manual',
+  key: { type: 'point_in_time' as const, timestamp: '2022-03-18T08:25:00.000Z' },
+  label: 'Event',
+  icon: 'triangle' as const,
+  color: 'red',
+  lineStyle: 'dashed' as const,
+  lineWidth: 3,
+};
+
+describe('AnnotationsPanel', () => {
+  const mockDataView: DataView = {
+    fields: [
+      new DataViewField({
+        type: 'date',
+        name: 'field1',
+        searchable: true,
+        aggregatable: true,
+      }),
+      new DataViewField({
+        type: 'date',
+        name: '@timestamp',
+        searchable: true,
+        aggregatable: true,
+      }),
+    ] as unknown as IIndexPatternFieldList,
+    getFieldByName: (name) =>
+      new DataViewField({ type: 'some-type', name, searchable: true, aggregatable: true }),
+    timeFieldName: '@timestamp',
+  } as Partial<DataView> as DataView;
+
+  const mockQueryInputServices = {
+    http: {},
+    uiSettings: {},
+    storage: {},
+    dataViews: {},
+    unifiedSearch: {},
+    docLinks: {},
+    notifications: {},
+    data: {},
+  } as QueryInputServices;
+
+  describe('Dimension Editor', () => {
+    test('shows correct options for line annotations', () => {
+      const component = mount(
+        <AnnotationEditorControls
+          annotation={customLineStaticAnnotation}
+          onAnnotationChange={() => {}}
+          dataView={{} as DataView}
+          getDefaultRangeEnd={() => ''}
+          queryInputServices={mockQueryInputServices}
+          appName="myApp"
+        />
+      );
+
+      expect(
+        component.find('EuiDatePicker[data-test-subj="lns-xyAnnotation-time"]').prop('selected')
+      ).toEqual(moment('2022-03-18T08:25:00.000Z'));
+      expect(
+        component.find('EuiDatePicker[data-test-subj="lns-xyAnnotation-fromTime"]').exists()
+      ).toBeFalsy();
+      expect(
+        component.find('EuiDatePicker[data-test-subj="lns-xyAnnotation-toTime"]').exists()
+      ).toBeFalsy();
+      expect(
+        component.find('EuiSwitch[data-test-subj="lns-xyAnnotation-rangeSwitch"]').prop('checked')
+      ).toEqual(false);
+      expect(component.find('EuiFieldText[data-test-subj="name-input"]').prop('value')).toEqual(
+        'Event'
+      );
+      expect(
+        component.find('EuiComboBox[data-test-subj="lns-icon-select"]').prop('selectedOptions')
+      ).toEqual([{ label: 'Triangle', value: 'triangle' }]);
+      expect(component.find('TextDecorationSetting').exists()).toBeTruthy();
+      expect(component.find('LineStyleSettings').exists()).toBeTruthy();
+      expect(
+        component.find('EuiButtonGroup[data-test-subj="lns-xyAnnotation-fillStyle"]').exists()
+      ).toBeFalsy();
+    });
+    test('shows correct options for range annotations', () => {
+      const rangeAnnotation: EventAnnotationConfig = {
+        color: 'red',
+        icon: 'triangle',
+        id: 'ann1',
+        type: 'manual',
+        isHidden: undefined,
+        key: {
+          endTimestamp: '2022-03-21T10:49:00.000Z',
+          timestamp: '2022-03-18T08:25:00.000Z',
+          type: 'range',
+        },
+        label: 'Event range',
+        lineStyle: 'dashed',
+        lineWidth: 3,
+      };
+
+      const component = mount(
+        <AnnotationEditorControls
+          annotation={rangeAnnotation}
+          onAnnotationChange={() => {}}
+          dataView={{} as DataView}
+          getDefaultRangeEnd={() => ''}
+          queryInputServices={mockQueryInputServices}
+          appName="myApp"
+        />
+      );
+
+      expect(
+        component.find('EuiDatePicker[data-test-subj="lns-xyAnnotation-fromTime"]').prop('selected')
+      ).toEqual(moment('2022-03-18T08:25:00.000Z'));
+      expect(
+        component.find('EuiDatePicker[data-test-subj="lns-xyAnnotation-toTime"]').prop('selected')
+      ).toEqual(moment('2022-03-21T10:49:00.000Z'));
+      expect(
+        component.find('EuiDatePicker[data-test-subj="lns-xyAnnotation-time"]').exists()
+      ).toBeFalsy();
+      expect(
+        component.find('EuiSwitch[data-test-subj="lns-xyAnnotation-rangeSwitch"]').prop('checked')
+      ).toEqual(true);
+      expect(component.find('EuiFieldText[data-test-subj="name-input"]').prop('value')).toEqual(
+        'Event range'
+      );
+      expect(component.find('EuiComboBox[data-test-subj="lns-icon-select"]').exists()).toBeFalsy();
+      expect(component.find('TextDecorationSetting').exists()).toBeFalsy();
+      expect(component.find('LineStyleSettings').exists()).toBeFalsy();
+      expect(component.find('[data-test-subj="lns-xyAnnotation-fillStyle"]').exists()).toBeTruthy();
+    });
+
+    test('calculates correct endTimstamp and transparent color when switching for range annotation and back', async () => {
+      const onAnnotationChange = jest.fn();
+      const rangeEndTimestamp = new Date().toISOString();
+      const component = mount(
+        <AnnotationEditorControls
+          annotation={customLineStaticAnnotation}
+          onAnnotationChange={onAnnotationChange}
+          dataView={{} as DataView}
+          getDefaultRangeEnd={() => rangeEndTimestamp}
+          queryInputServices={mockQueryInputServices}
+          appName="myApp"
+        />
+      );
+
+      component.find('button[data-test-subj="lns-xyAnnotation-rangeSwitch"]').simulate('click');
+
+      const expectedRangeAnnotation: RangeEventAnnotationConfig = {
+        color: '#FF00001A',
+        id: 'ann1',
+        isHidden: undefined,
+        label: 'Event range',
+        type: 'manual',
+        key: {
+          endTimestamp: rangeEndTimestamp,
+          timestamp: '2022-03-18T08:25:00.000Z',
+          type: 'range',
+        },
+      };
+
+      expect(onAnnotationChange).toBeCalledWith<EventAnnotationConfig[]>(expectedRangeAnnotation);
+
+      act(() => {
+        component.setProps({ annotation: expectedRangeAnnotation });
+      });
+
+      expect(
+        component.find('EuiSwitch[data-test-subj="lns-xyAnnotation-rangeSwitch"]').prop('checked')
+      ).toEqual(true);
+
+      component.find('button[data-test-subj="lns-xyAnnotation-rangeSwitch"]').simulate('click');
+
+      expect(onAnnotationChange).toBeCalledWith<EventAnnotationConfig[]>({
+        color: '#FF0000',
+        id: 'ann1',
+        isHidden: undefined,
+        key: {
+          timestamp: '2022-03-18T08:25:00.000Z',
+          type: 'point_in_time',
+        },
+        label: 'Event',
+        type: 'manual',
+      });
+    });
+
+    test('shows correct options for query based', () => {
+      const annotation: EventAnnotationConfig = {
+        color: 'red',
+        icon: 'triangle',
+        id: 'ann1',
+        type: 'query',
+        isHidden: undefined,
+        timeField: 'timestamp',
+        key: {
+          type: 'point_in_time',
+        },
+        label: 'Query based event',
+        lineStyle: 'dashed',
+        lineWidth: 3,
+        filter: { type: 'kibana_query', query: '', language: 'kuery' },
+      };
+
+      const component = mount(
+        <AnnotationEditorControls
+          annotation={annotation}
+          onAnnotationChange={() => {}}
+          dataView={mockDataView}
+          getDefaultRangeEnd={() => ''}
+          queryInputServices={mockQueryInputServices}
+          appName="myApp"
+        />
+      );
+
+      expect(
+        component.find('[data-test-subj="lnsXY-annotation-query-based-field-picker"]').exists()
+      ).toBeTruthy();
+      expect(
+        component.find('[data-test-subj="annotation-query-based-query-input"]').exists()
+      ).toBeTruthy();
+
+      // The provided indexPattern has 2 date fields
+      expect(
+        component
+          .find('[data-test-subj="lnsXY-annotation-query-based-field-picker"]')
+          .at(0)
+          .prop('options')
+      ).toHaveLength(2);
+      // When in query mode a new "field" option is added to the previous 2 ones
+      expect(
+        component.find('[data-test-subj="lns-lineMarker-text-visibility"]').at(0).prop('options')
+      ).toHaveLength(3);
+      expect(
+        component.find('[data-test-subj="lnsXY-annotation-tooltip-add_field"]').exists()
+      ).toBeTruthy();
+    });
+
+    test('should prefill timeField with the default time field when switching to query based annotations', () => {
+      const onAnnotationChange = jest.fn();
+
+      const component = mount(
+        <AnnotationEditorControls
+          annotation={customLineStaticAnnotation}
+          onAnnotationChange={onAnnotationChange}
+          dataView={mockDataView}
+          getDefaultRangeEnd={() => ''}
+          queryInputServices={mockQueryInputServices}
+          appName="myApp"
+        />
+      );
+
+      act(() => {
+        component
+          .find(`[data-test-subj="lns-xyAnnotation-placementType"]`)
+          .find(EuiButtonGroup)
+          .prop('onChange')!('lens_xyChart_annotation_query');
+      });
+      component.update();
+
+      expect(onAnnotationChange).toHaveBeenCalledWith(
+        expect.objectContaining({ timeField: '@timestamp' })
+      );
+    });
+
+    test('should avoid to retain specific manual configurations when switching to query based annotations', () => {
+      const onAnnotationChange = jest.fn();
+
+      const component = mount(
+        <AnnotationEditorControls
+          annotation={customLineStaticAnnotation}
+          onAnnotationChange={onAnnotationChange}
+          dataView={mockDataView}
+          getDefaultRangeEnd={() => ''}
+          queryInputServices={mockQueryInputServices}
+          appName="myApp"
+        />
+      );
+
+      act(() => {
+        component
+          .find(`[data-test-subj="lns-xyAnnotation-placementType"]`)
+          .find(EuiButtonGroup)
+          .prop('onChange')!('lens_xyChart_annotation_query');
+      });
+      component.update();
+
+      expect(onAnnotationChange).toHaveBeenCalledWith(
+        expect.objectContaining({
+          key: expect.not.objectContaining({ timestamp: expect.any('string') }),
+        })
+      );
+    });
+
+    test('should avoid to retain range manual configurations when switching to query based annotations', () => {
+      const annotation: EventAnnotationConfig = {
+        color: 'red',
+        icon: 'triangle',
+        id: 'ann1',
+        type: 'manual',
+        isHidden: undefined,
+        key: {
+          endTimestamp: '2022-03-21T10:49:00.000Z',
+          timestamp: '2022-03-18T08:25:00.000Z',
+          type: 'range',
+        },
+        label: 'Event range',
+        lineStyle: 'dashed',
+        lineWidth: 3,
+      };
+
+      const onAnnotationChange = jest.fn();
+
+      const component = mount(
+        <AnnotationEditorControls
+          annotation={annotation}
+          onAnnotationChange={onAnnotationChange}
+          dataView={mockDataView}
+          getDefaultRangeEnd={() => ''}
+          queryInputServices={mockQueryInputServices}
+          appName="myApp"
+        />
+      );
+
+      act(() => {
+        component
+          .find(`[data-test-subj="lns-xyAnnotation-placementType"]`)
+          .find(EuiButtonGroup)
+          .prop('onChange')!('lens_xyChart_annotation_query');
+      });
+      component.update();
+
+      expect(onAnnotationChange).toHaveBeenCalledWith(
+        expect.objectContaining({ label: expect.not.stringContaining('Event range') })
+      );
+    });
+
+    test('should set a default tiemstamp when switching from query based to manual annotations', () => {
+      const annotation: EventAnnotationConfig = {
+        color: 'red',
+        icon: 'triangle',
+        id: 'ann1',
+        type: 'query',
+        isHidden: undefined,
+        timeField: 'timestamp',
+        key: {
+          type: 'point_in_time',
+        },
+        label: 'Query based event',
+        lineStyle: 'dashed',
+        lineWidth: 3,
+        filter: { type: 'kibana_query', query: '', language: 'kuery' },
+      };
+
+      const onAnnotationChange = jest.fn();
+
+      const component = mount(
+        <AnnotationEditorControls
+          annotation={annotation}
+          onAnnotationChange={onAnnotationChange}
+          dataView={mockDataView}
+          getDefaultRangeEnd={() => ''}
+          queryInputServices={mockQueryInputServices}
+          appName="myApp"
+        />
+      );
+
+      act(() => {
+        component
+          .find(`[data-test-subj="lns-xyAnnotation-placementType"]`)
+          .find(EuiButtonGroup)
+          .prop('onChange')!('lens_xyChart_annotation_manual');
+      });
+      component.update();
+
+      expect(onAnnotationChange).toHaveBeenCalledWith(
+        expect.objectContaining({
+          key: { type: 'point_in_time', timestamp: expect.any(String) },
+        })
+      );
+
+      // also check query specific props are not carried over
+      expect(onAnnotationChange).toHaveBeenCalledWith(
+        expect.not.objectContaining({ timeField: 'timestamp' })
+      );
+    });
+
+    test('should fallback to the first date field available in the dataView if not time-based', () => {
+      const onAnnotationChange = jest.fn();
+      const component = mount(
+        <AnnotationEditorControls
+          annotation={customLineStaticAnnotation}
+          onAnnotationChange={onAnnotationChange}
+          dataView={{ ...mockDataView, timeFieldName: '' } as DataView}
+          getDefaultRangeEnd={() => ''}
+          queryInputServices={mockQueryInputServices}
+          appName="myApp"
+        />
+      );
+
+      act(() => {
+        component
+          .find(`[data-test-subj="lns-xyAnnotation-placementType"]`)
+          .find(EuiButtonGroup)
+          .prop('onChange')!('lens_xyChart_annotation_query');
+      });
+      component.update();
+
+      expect(onAnnotationChange).toHaveBeenCalledWith(
+        expect.objectContaining({ timeField: 'field1' })
+      );
+    });
+  });
+});
diff --git a/src/plugins/event_annotation/public/components/annotation_editor_controls/index.tsx b/src/plugins/event_annotation/public/components/annotation_editor_controls/index.tsx
new file mode 100644
index 0000000000000..9da375cc584ca
--- /dev/null
+++ b/src/plugins/event_annotation/public/components/annotation_editor_controls/index.tsx
@@ -0,0 +1,20 @@
+/*
+ * 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 React, { Suspense, lazy } from 'react';
+import type { Props } from './annotation_editor_controls';
+
+const AnnotationEditorControlsLazy = lazy(() => import('./annotation_editor_controls'));
+
+export const AnnotationEditorControls = (props: Props) => (
+  <Suspense fallback={null}>
+    <AnnotationEditorControlsLazy {...props} />
+  </Suspense>
+);
+
+export { annotationsIconSet } from './icon_set';
diff --git a/x-pack/plugins/lens/public/visualizations/xy/xy_config_panel/annotations_config_panel/manual_annotation_panel.tsx b/src/plugins/event_annotation/public/components/annotation_editor_controls/manual_annotation_panel.tsx
similarity index 80%
rename from x-pack/plugins/lens/public/visualizations/xy/xy_config_panel/annotations_config_panel/manual_annotation_panel.tsx
rename to src/plugins/event_annotation/public/components/annotation_editor_controls/manual_annotation_panel.tsx
index 795d076f22f0f..8ddac6c6173c4 100644
--- a/x-pack/plugins/lens/public/visualizations/xy/xy_config_panel/annotations_config_panel/manual_annotation_panel.tsx
+++ b/src/plugins/event_annotation/public/components/annotation_editor_controls/manual_annotation_panel.tsx
@@ -1,35 +1,31 @@
 /*
  * 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; you may not use this file except in compliance with the Elastic License
- * 2.0.
+ * 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 type { DatatableUtilitiesService } from '@kbn/data-plugin/common';
-import { isRangeAnnotationConfig } from '@kbn/event-annotation-plugin/public';
 import { i18n } from '@kbn/i18n';
 import moment from 'moment';
 import React from 'react';
-import type { FramePublicAPI } from '../../../../types';
-import type { XYState } from '../../types';
+import { isRangeAnnotationConfig } from '../..';
 import {
-  ConfigPanelRangeDatePicker,
   ConfigPanelApplyAsRangeSwitch,
+  ConfigPanelRangeDatePicker,
 } from './range_annotation_panel';
 import type { ManualEventAnnotationType } from './types';
 
 export const ConfigPanelManualAnnotation = ({
   annotation,
-  frame,
-  state,
   onChange,
-  datatableUtilities,
+  getDefaultRangeEnd,
+  calendarClassName,
 }: {
   annotation?: ManualEventAnnotationType | undefined;
   onChange: <T extends ManualEventAnnotationType>(annotation: Partial<T> | undefined) => void;
-  datatableUtilities: DatatableUtilitiesService;
-  frame: FramePublicAPI;
-  state: XYState;
+  getDefaultRangeEnd: (rangeStart: string) => string;
+  calendarClassName: string | undefined;
 }) => {
   const isRange = isRangeAnnotationConfig(annotation);
   return (
@@ -38,7 +34,8 @@ export const ConfigPanelManualAnnotation = ({
         <>
           <ConfigPanelRangeDatePicker
             dataTestSubj="lns-xyAnnotation-fromTime"
-            prependLabel={i18n.translate('xpack.lens.xyChart.annotationDate.from', {
+            calendarClassName={calendarClassName}
+            prependLabel={i18n.translate('eventAnnotation.xyChart.annotationDate.from', {
               defaultMessage: 'From',
             })}
             value={moment(annotation?.key.timestamp)}
@@ -65,13 +62,14 @@ export const ConfigPanelManualAnnotation = ({
                 }
               }
             }}
-            label={i18n.translate('xpack.lens.xyChart.annotationDate', {
+            label={i18n.translate('eventAnnotation.xyChart.annotationDate', {
               defaultMessage: 'Annotation date',
             })}
           />
           <ConfigPanelRangeDatePicker
             dataTestSubj="lns-xyAnnotation-toTime"
-            prependLabel={i18n.translate('xpack.lens.xyChart.annotationDate.to', {
+            calendarClassName={calendarClassName}
+            prependLabel={i18n.translate('eventAnnotation.xyChart.annotationDate.to', {
               defaultMessage: 'To',
             })}
             value={moment(annotation?.key.endTimestamp)}
@@ -103,7 +101,8 @@ export const ConfigPanelManualAnnotation = ({
       ) : (
         <ConfigPanelRangeDatePicker
           dataTestSubj="lns-xyAnnotation-time"
-          label={i18n.translate('xpack.lens.xyChart.annotationDate', {
+          calendarClassName={calendarClassName}
+          label={i18n.translate('eventAnnotation.xyChart.annotationDate', {
             defaultMessage: 'Annotation date',
           })}
           value={moment(annotation?.key.timestamp)}
@@ -122,9 +121,7 @@ export const ConfigPanelManualAnnotation = ({
       <ConfigPanelApplyAsRangeSwitch
         annotation={annotation}
         onChange={onChange}
-        datatableUtilities={datatableUtilities}
-        frame={frame}
-        state={state}
+        getDefaultRangeEnd={getDefaultRangeEnd}
       />
     </>
   );
diff --git a/x-pack/plugins/lens/public/visualizations/xy/xy_config_panel/annotations_config_panel/query_annotation_panel.tsx b/src/plugins/event_annotation/public/components/annotation_editor_controls/query_annotation_panel.tsx
similarity index 64%
rename from x-pack/plugins/lens/public/visualizations/xy/xy_config_panel/annotations_config_panel/query_annotation_panel.tsx
rename to src/plugins/event_annotation/public/components/annotation_editor_controls/query_annotation_panel.tsx
index e502efe559597..85b9c08d0e138 100644
--- a/x-pack/plugins/lens/public/visualizations/xy/xy_config_panel/annotations_config_panel/query_annotation_panel.tsx
+++ b/src/plugins/event_annotation/public/components/annotation_editor_controls/query_annotation_panel.tsx
@@ -1,27 +1,26 @@
 /*
  * 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; you may not use this file except in compliance with the Elastic License
- * 2.0.
+ * 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 { EuiFormRow } from '@elastic/eui';
 import type { Query } from '@kbn/data-plugin/common';
-import type { QueryPointEventAnnotationConfig } from '@kbn/event-annotation-plugin/common';
 import { i18n } from '@kbn/i18n';
 import React from 'react';
 import { useExistingFieldsReader } from '@kbn/unified-field-list-plugin/public';
 import {
   FieldOption,
-  FilterQueryInput,
   FieldOptionValue,
   FieldPicker,
+  FilterQueryInput,
+  type QueryInputServices,
 } from '@kbn/visualization-ui-components/public';
-import { useKibana } from '@kbn/kibana-react-plugin/public';
-import { LENS_APP_NAME } from '../../../../../common/constants';
-import type { FramePublicAPI } from '../../../../types';
-import type { XYState, XYAnnotationLayerConfig } from '../../types';
-import { LensAppServices } from '../../../../app_plugin/types';
+import type { DataView } from '@kbn/data-views-plugin/common';
+import { isFieldLensCompatible } from '@kbn/visualization-ui-components/public';
+import type { QueryPointEventAnnotationConfig } from '../../../common';
 
 export const defaultQuery: Query = {
   query: '',
@@ -30,23 +29,23 @@ export const defaultQuery: Query = {
 
 export const ConfigPanelQueryAnnotation = ({
   annotation,
-  frame,
-  state,
+  dataView,
   onChange,
-  layer,
   queryInputShouldOpen,
+  queryInputServices,
+  appName,
 }: {
   annotation?: QueryPointEventAnnotationConfig;
   onChange: (annotations: Partial<QueryPointEventAnnotationConfig> | undefined) => void;
-  frame: FramePublicAPI;
-  state: XYState;
-  layer: XYAnnotationLayerConfig;
+  dataView: DataView;
   queryInputShouldOpen?: boolean;
+  queryInputServices: QueryInputServices;
+  appName: string;
 }) => {
-  const currentIndexPattern = frame.dataViews.indexPatterns[layer.indexPatternId];
   const { hasFieldData } = useExistingFieldsReader();
   // list only date fields
-  const options = currentIndexPattern.fields
+  const options = dataView.fields
+    .filter(isFieldLensCompatible)
     .filter((field) => field.type === 'date' && field.displayName)
     .map((field) => {
       return {
@@ -56,17 +55,14 @@ export const ConfigPanelQueryAnnotation = ({
           field: field.name,
           dataType: field.type,
         },
-        exists: hasFieldData(currentIndexPattern.id, field.name),
+        exists: dataView.id ? hasFieldData(dataView.id, field.name) : false,
         compatible: true,
         'data-test-subj': `lns-fieldOption-${field.name}`,
       } as FieldOption<FieldOptionValue>;
     });
 
-  const selectedField =
-    annotation?.timeField || currentIndexPattern.timeFieldName || options[0]?.value.field;
-  const fieldIsValid = selectedField
-    ? Boolean(currentIndexPattern.getFieldByName(selectedField))
-    : true;
+  const selectedField = annotation?.timeField || dataView.timeFieldName || options[0]?.value.field;
+  const fieldIsValid = selectedField ? Boolean(dataView.getFieldByName(selectedField)) : true;
 
   return (
     <>
@@ -75,29 +71,28 @@ export const ConfigPanelQueryAnnotation = ({
         display="rowCompressed"
         className="lnsRowCompressedMargin"
         fullWidth
-        label={i18n.translate('xpack.lens.xyChart.annotation.queryInput', {
+        label={i18n.translate('eventAnnotation.xyChart.annotation.queryInput', {
           defaultMessage: 'Annotation query',
         })}
-        data-test-subj="annotation-query-based-query-input"
       >
         <FilterQueryInput
+          data-test-subj="annotation-query-based-query-input"
           initiallyOpen={queryInputShouldOpen}
           label=""
           inputFilter={annotation?.filter ?? defaultQuery}
           onChange={(query: Query) => {
             onChange({ filter: { type: 'kibana_query', ...query } });
           }}
-          data-test-subj="lnsXY-annotation-query-based-query-input"
-          dataView={currentIndexPattern}
-          appName={LENS_APP_NAME}
-          queryInputServices={useKibana<LensAppServices>().services}
+          dataView={dataView}
+          appName={appName}
+          queryInputServices={queryInputServices}
         />
       </EuiFormRow>
 
       <EuiFormRow
         display="rowCompressed"
         fullWidth
-        label={i18n.translate('xpack.lens.xyChart.annotation.queryField', {
+        label={i18n.translate('eventAnnotation.xyChart.annotation.queryField', {
           defaultMessage: 'Target date field',
         })}
       >
diff --git a/x-pack/plugins/lens/public/visualizations/xy/xy_config_panel/annotations_config_panel/range_annotation_panel.tsx b/src/plugins/event_annotation/public/components/annotation_editor_controls/range_annotation_panel.tsx
similarity index 72%
rename from x-pack/plugins/lens/public/visualizations/xy/xy_config_panel/annotations_config_panel/range_annotation_panel.tsx
rename to src/plugins/event_annotation/public/components/annotation_editor_controls/range_annotation_panel.tsx
index 1bed2d760514b..0418994677947 100644
--- a/x-pack/plugins/lens/public/visualizations/xy/xy_config_panel/annotations_config_panel/range_annotation_panel.tsx
+++ b/src/plugins/event_annotation/public/components/annotation_editor_controls/range_annotation_panel.tsx
@@ -1,16 +1,11 @@
 /*
  * 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; you may not use this file except in compliance with the Elastic License
- * 2.0.
+ * 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 type { DatatableUtilitiesService } from '@kbn/data-plugin/common';
-import type {
-  PointInTimeEventAnnotationConfig,
-  RangeEventAnnotationConfig,
-} from '@kbn/event-annotation-plugin/common';
-import { isRangeAnnotationConfig } from '@kbn/event-annotation-plugin/public';
 import { i18n } from '@kbn/i18n';
 import React from 'react';
 import {
@@ -22,26 +17,20 @@ import {
   EuiDatePicker,
 } from '@elastic/eui';
 import moment from 'moment';
-import { DONT_CLOSE_DIMENSION_CONTAINER_ON_CLICK_CLASS } from '../../../../utils';
-import type { FramePublicAPI } from '../../../../types';
-import { defaultRangeAnnotationLabel, defaultAnnotationLabel } from '../../annotations/helpers';
-import type { XYState } from '../../types';
-import { getDataLayers } from '../../visualization_helpers';
-import { toLineAnnotationColor, getEndTimestamp, toRangeAnnotationColor } from './helpers';
+import { isRangeAnnotationConfig } from '../..';
+import type { PointInTimeEventAnnotationConfig, RangeEventAnnotationConfig } from '../../../common';
+import { defaultRangeAnnotationLabel, defaultAnnotationLabel } from './helpers';
+import { toLineAnnotationColor, toRangeAnnotationColor } from './helpers';
 import type { ManualEventAnnotationType } from './types';
 
 export const ConfigPanelApplyAsRangeSwitch = ({
   annotation,
-  datatableUtilities,
   onChange,
-  frame,
-  state,
+  getDefaultRangeEnd,
 }: {
   annotation?: ManualEventAnnotationType;
-  datatableUtilities: DatatableUtilitiesService;
   onChange: <T extends ManualEventAnnotationType>(annotations: Partial<T> | undefined) => void;
-  frame: FramePublicAPI;
-  state: XYState;
+  getDefaultRangeEnd: (rangeStart: string) => string;
 }) => {
   const isRange = isRangeAnnotationConfig(annotation);
   return (
@@ -50,7 +39,7 @@ export const ConfigPanelApplyAsRangeSwitch = ({
         data-test-subj="lns-xyAnnotation-rangeSwitch"
         label={
           <EuiText size="xs">
-            {i18n.translate('xpack.lens.xyChart.applyAsRange', {
+            {i18n.translate('eventAnnotation.xyChart.applyAsRange', {
               defaultMessage: 'Apply as range',
             })}
           </EuiText>
@@ -74,19 +63,12 @@ export const ConfigPanelApplyAsRangeSwitch = ({
             };
             onChange(newPointAnnotation);
           } else if (annotation) {
-            const fromTimestamp = moment(annotation?.key.timestamp);
-            const dataLayers = getDataLayers(state.layers);
             const newRangeAnnotation: RangeEventAnnotationConfig = {
               type: 'manual',
               key: {
                 type: 'range',
                 timestamp: annotation.key.timestamp,
-                endTimestamp: getEndTimestamp(
-                  datatableUtilities,
-                  fromTimestamp.toISOString(),
-                  frame,
-                  dataLayers
-                ),
+                endTimestamp: getDefaultRangeEnd(annotation.key.timestamp),
               },
               id: annotation.id,
               label:
@@ -110,12 +92,14 @@ export const ConfigPanelRangeDatePicker = ({
   label,
   prependLabel,
   onChange,
+  calendarClassName,
   dataTestSubj = 'lnsXY_annotation_date_picker',
 }: {
   value: moment.Moment;
   prependLabel?: string;
   label?: string;
   onChange: (val: moment.Moment | null) => void;
+  calendarClassName: string | undefined;
   dataTestSubj?: string;
 }) => {
   return (
@@ -129,7 +113,7 @@ export const ConfigPanelRangeDatePicker = ({
           }
         >
           <EuiDatePicker
-            calendarClassName={DONT_CLOSE_DIMENSION_CONTAINER_ON_CLICK_CLASS}
+            calendarClassName={calendarClassName}
             fullWidth
             showTimeSelect
             selected={value}
@@ -140,7 +124,7 @@ export const ConfigPanelRangeDatePicker = ({
         </EuiFormControlLayout>
       ) : (
         <EuiDatePicker
-          calendarClassName={DONT_CLOSE_DIMENSION_CONTAINER_ON_CLICK_CLASS}
+          calendarClassName={calendarClassName}
           fullWidth
           showTimeSelect
           selected={value}
diff --git a/x-pack/plugins/lens/public/visualizations/xy/xy_config_panel/annotations_config_panel/tooltip_annotation_panel.tsx b/src/plugins/event_annotation/public/components/annotation_editor_controls/tooltip_annotation_panel.tsx
similarity index 84%
rename from x-pack/plugins/lens/public/visualizations/xy/xy_config_panel/annotations_config_panel/tooltip_annotation_panel.tsx
rename to src/plugins/event_annotation/public/components/annotation_editor_controls/tooltip_annotation_panel.tsx
index 12b2926fa7b14..e0e290e611a01 100644
--- a/x-pack/plugins/lens/public/visualizations/xy/xy_config_panel/annotations_config_panel/tooltip_annotation_panel.tsx
+++ b/src/plugins/event_annotation/public/components/annotation_editor_controls/tooltip_annotation_panel.tsx
@@ -1,28 +1,28 @@
 /*
  * 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; you may not use this file except in compliance with the Elastic License
- * 2.0.
+ * 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 { htmlIdGenerator, EuiFlexItem, EuiPanel, EuiText } from '@elastic/eui';
 import { i18n } from '@kbn/i18n';
 import React, { useCallback, useMemo } from 'react';
-import { QueryPointEventAnnotationConfig } from '@kbn/event-annotation-plugin/common';
 import { useExistingFieldsReader } from '@kbn/unified-field-list-plugin/public';
 import {
   FieldOption,
   FieldOptionValue,
   FieldPicker,
-} from '@kbn/visualization-ui-components/public';
-import {
   useDebouncedValue,
   NewBucketButton,
   DragDropBuckets,
   DraggableBucketContainer,
   FieldsBucketContainer,
 } from '@kbn/visualization-ui-components/public';
-import type { IndexPattern } from '../../../../types';
+import { DataView } from '@kbn/data-views-plugin/common';
+import { isFieldLensCompatible } from '@kbn/visualization-ui-components/public';
+import { QueryPointEventAnnotationConfig } from '../../../common';
 
 export const MAX_TOOLTIP_FIELDS_SIZE = 2;
 
@@ -32,7 +32,7 @@ const supportedTypes = new Set(['string', 'boolean', 'number', 'ip', 'date']);
 export interface FieldInputsProps {
   currentConfig: QueryPointEventAnnotationConfig;
   setConfig: (config: QueryPointEventAnnotationConfig) => void;
-  indexPattern: IndexPattern;
+  dataView: DataView;
   invalidFields?: string[];
 }
 
@@ -51,7 +51,7 @@ function removeNewEmptyField(v: WrappedValue): v is SafeWrappedValue {
 export function TooltipSection({
   currentConfig,
   setConfig,
-  indexPattern,
+  dataView,
   invalidFields,
 }: FieldInputsProps) {
   const { hasFieldData } = useExistingFieldsReader();
@@ -81,14 +81,14 @@ export function TooltipSection({
     (choice, index = 0) => {
       const fields = [...localValues];
 
-      if (indexPattern.getFieldByName(choice.field)) {
+      if (dataView.getFieldByName(choice.field)) {
         fields[index] = { id: generateId(), value: choice.field };
 
         // update the layer state
         handleInputChange(fields);
       }
     },
-    [localValues, indexPattern, handleInputChange]
+    [localValues, dataView, handleInputChange]
   );
 
   const newBucketButton = (
@@ -98,7 +98,7 @@ export function TooltipSection({
       onClick={() => {
         handleInputChange([...localValues, { id: generateId(), value: undefined, isNew: true }]);
       }}
-      label={i18n.translate('xpack.lens.xyChart.annotation.tooltip.addField', {
+      label={i18n.translate('eventAnnotation.xyChart.annotation.tooltip.addField', {
         defaultMessage: 'Add field',
       })}
       isDisabled={localValues.length > MAX_TOOLTIP_FIELDS_SIZE}
@@ -115,7 +115,7 @@ export function TooltipSection({
             className="lnsConfigPanelAnnotations__noFieldsPrompt"
           >
             <EuiText color="subdued" size="s" textAlign="center">
-              {i18n.translate('xpack.lens.xyChart.annotation.tooltip.noFields', {
+              {i18n.translate('eventAnnotation.xyChart.annotation.tooltip.noFields', {
                 defaultMessage: 'None selected',
               })}
             </EuiText>
@@ -126,7 +126,8 @@ export function TooltipSection({
     );
   }
 
-  const options = indexPattern.fields
+  const options = dataView.fields
+    .filter(isFieldLensCompatible)
     .filter(
       ({ displayName, type }) =>
         displayName && !rawValuesLookup.has(displayName) && supportedTypes.has(type)
@@ -140,7 +141,7 @@ export function TooltipSection({
             field: field.name,
             dataType: field.type,
           },
-          exists: hasFieldData(indexPattern.id, field.name),
+          exists: dataView.id ? hasFieldData(dataView.id, field.name) : false,
           compatible: true,
           'data-test-subj': `lnsXY-annotation-tooltip-fieldOption-${field.name}`,
         } as FieldOption<FieldOptionValue>)
@@ -158,7 +159,7 @@ export function TooltipSection({
         bgColor="subdued"
       >
         {localValues.map(({ id, value, isNew }, index, arrayRef) => {
-          const fieldIsValid = value ? Boolean(indexPattern.getFieldByName(value)) : true;
+          const fieldIsValid = value ? Boolean(dataView.getFieldByName(value)) : true;
 
           return (
             <DraggableBucketContainer
@@ -169,7 +170,7 @@ export function TooltipSection({
                 handleInputChange(arrayRef.filter((_, i) => i !== index));
               }}
               removeTitle={i18n.translate(
-                'xpack.lens.xyChart.annotation.tooltip.deleteButtonLabel',
+                'eventAnnotation.xyChart.annotation.tooltip.deleteButtonLabel',
                 {
                   defaultMessage: 'Delete',
                 }
diff --git a/src/plugins/event_annotation/public/components/annotation_editor_controls/types.ts b/src/plugins/event_annotation/public/components/annotation_editor_controls/types.ts
new file mode 100644
index 0000000000000..094543e4f71d8
--- /dev/null
+++ b/src/plugins/event_annotation/public/components/annotation_editor_controls/types.ts
@@ -0,0 +1,13 @@
+/*
+ * 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 type { PointInTimeEventAnnotationConfig, RangeEventAnnotationConfig } from '../../../common';
+
+export type ManualEventAnnotationType =
+  | PointInTimeEventAnnotationConfig
+  | RangeEventAnnotationConfig;
diff --git a/src/plugins/event_annotation/public/components/get_annotation_accessor.ts b/src/plugins/event_annotation/public/components/get_annotation_accessor.ts
new file mode 100644
index 0000000000000..1ece7164f2557
--- /dev/null
+++ b/src/plugins/event_annotation/public/components/get_annotation_accessor.ts
@@ -0,0 +1,32 @@
+/*
+ * 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 type { AccessorConfig } from '@kbn/visualization-ui-components/public';
+import { EventAnnotationConfig } from '../../common';
+import {
+  defaultAnnotationColor,
+  defaultAnnotationRangeColor,
+  isRangeAnnotationConfig,
+} from '../event_annotation_service/helpers';
+import { annotationsIconSet } from './annotation_editor_controls/icon_set';
+
+export const getAnnotationAccessor = (annotation: EventAnnotationConfig): AccessorConfig => {
+  const annotationIcon = !isRangeAnnotationConfig(annotation)
+    ? annotationsIconSet.find((option) => option.value === annotation?.icon) ||
+      annotationsIconSet.find((option) => option.value === 'triangle')
+    : undefined;
+  const icon = annotationIcon?.icon ?? annotationIcon?.value;
+  return {
+    columnId: annotation.id,
+    triggerIconType: annotation.isHidden ? 'invisible' : icon ? 'custom' : 'color',
+    customIcon: icon,
+    color:
+      annotation?.color ||
+      (isRangeAnnotationConfig(annotation) ? defaultAnnotationRangeColor : defaultAnnotationColor),
+  };
+};
diff --git a/src/plugins/event_annotation/public/components/group_editor_controls/annotation_list.tsx b/src/plugins/event_annotation/public/components/group_editor_controls/annotation_list.tsx
new file mode 100644
index 0000000000000..f29638e202548
--- /dev/null
+++ b/src/plugins/event_annotation/public/components/group_editor_controls/annotation_list.tsx
@@ -0,0 +1,132 @@
+/*
+ * 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 { DragContext, DragDrop, DropTargetSwapDuplicateCombine } from '@kbn/dom-drag-drop';
+import {
+  DimensionButton,
+  DimensionTrigger,
+  EmptyDimensionButton,
+} from '@kbn/visualization-ui-components/public';
+import React, { useCallback, useContext, useEffect, useState } from 'react';
+import { v4 as uuidv4 } from 'uuid';
+import { euiThemeVars } from '@kbn/ui-theme';
+import { i18n } from '@kbn/i18n';
+import { createCopiedAnnotation, EventAnnotationConfig } from '../../../common';
+import { getAnnotationAccessor } from '..';
+
+export const AnnotationList = ({
+  annotations,
+  selectAnnotation,
+  update: updateAnnotations,
+}: {
+  annotations: EventAnnotationConfig[];
+  selectAnnotation: (annotation: EventAnnotationConfig) => void;
+  update: (annotations: EventAnnotationConfig[]) => void;
+}) => {
+  const [newAnnotationId, setNewAnnotationId] = useState<string>(uuidv4());
+  useEffect(() => {
+    setNewAnnotationId(uuidv4());
+  }, [annotations.length]);
+
+  const { dragging } = useContext(DragContext);
+
+  const addAnnotationText = i18n.translate('eventAnnotation.annotationList.add', {
+    defaultMessage: 'Add annotation',
+  });
+
+  const addNewAnnotation = useCallback(
+    (sourceAnnotationId?: string) => {
+      const source = sourceAnnotationId
+        ? annotations.find(({ id }) => id === sourceAnnotationId)
+        : undefined;
+      const newAnnotation = createCopiedAnnotation(
+        newAnnotationId,
+        new Date().toISOString(),
+        source
+      );
+
+      if (!source) {
+        selectAnnotation(newAnnotation);
+      }
+      updateAnnotations([...annotations, newAnnotation]);
+    },
+    [annotations, newAnnotationId, selectAnnotation, updateAnnotations]
+  );
+
+  return (
+    <div>
+      {annotations.map((annotation, index) => (
+        <div
+          key={index}
+          css={css`
+            margin-top: ${euiThemeVars.euiSizeS};
+            position: relative; // this is to properly contain the absolutely-positioned drop target in DragDrop
+          `}
+        >
+          <DragDrop
+            order={[index]}
+            key={annotation.id}
+            value={{
+              id: annotation.id,
+              humanData: {
+                label: annotation.label,
+              },
+            }}
+            dragType="copy"
+            dropTypes={[]}
+            draggable
+          >
+            <DimensionButton
+              groupLabel={i18n.translate('eventAnnotation.groupEditor.addAnnotation', {
+                defaultMessage: 'Annotations',
+              })}
+              onClick={() => selectAnnotation(annotation)}
+              onRemoveClick={() =>
+                updateAnnotations(annotations.filter(({ id }) => id !== annotation.id))
+              }
+              accessorConfig={getAnnotationAccessor(annotation)}
+              label={annotation.label}
+            >
+              <DimensionTrigger label={annotation.label} />
+            </DimensionButton>
+          </DragDrop>
+        </div>
+      ))}
+
+      <div
+        css={css`
+          margin-top: ${euiThemeVars.euiSizeS};
+        `}
+      >
+        <DragDrop
+          order={[annotations.length]}
+          getCustomDropTarget={DropTargetSwapDuplicateCombine.getCustomDropTarget}
+          getAdditionalClassesOnDroppable={
+            DropTargetSwapDuplicateCombine.getAdditionalClassesOnDroppable
+          }
+          dropTypes={dragging ? ['field_add'] : []}
+          value={{
+            id: 'addAnnotation',
+            humanData: {
+              label: addAnnotationText,
+            },
+          }}
+          onDrop={({ id: sourceId }) => addNewAnnotation(sourceId)}
+        >
+          <EmptyDimensionButton
+            dataTestSubj="addAnnotation"
+            label={addAnnotationText}
+            ariaLabel={addAnnotationText}
+            onClick={() => addNewAnnotation()}
+          />
+        </DragDrop>
+      </div>
+    </div>
+  );
+};
diff --git a/src/plugins/event_annotation/public/components/group_editor_controls/get_annotation_accessor.ts b/src/plugins/event_annotation/public/components/group_editor_controls/get_annotation_accessor.ts
new file mode 100644
index 0000000000000..59cdfe15d0814
--- /dev/null
+++ b/src/plugins/event_annotation/public/components/group_editor_controls/get_annotation_accessor.ts
@@ -0,0 +1,32 @@
+/*
+ * 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 type { AccessorConfig } from '@kbn/visualization-ui-components/public';
+import type { EventAnnotationConfig } from '../../../common';
+import {
+  defaultAnnotationColor,
+  defaultAnnotationRangeColor,
+  isRangeAnnotationConfig,
+} from '../../event_annotation_service/helpers';
+import { annotationsIconSet } from '../annotation_editor_controls';
+
+export const getAnnotationAccessor = (annotation: EventAnnotationConfig): AccessorConfig => {
+  const annotationIcon = !isRangeAnnotationConfig(annotation)
+    ? annotationsIconSet.find((option) => option.value === annotation?.icon) ||
+      annotationsIconSet.find((option) => option.value === 'triangle')
+    : undefined;
+  const icon = annotationIcon?.icon ?? annotationIcon?.value;
+  return {
+    columnId: annotation.id,
+    triggerIconType: annotation.isHidden ? 'invisible' : icon ? 'custom' : 'color',
+    customIcon: icon,
+    color:
+      annotation?.color ||
+      (isRangeAnnotationConfig(annotation) ? defaultAnnotationRangeColor : defaultAnnotationColor),
+  };
+};
diff --git a/src/plugins/event_annotation/public/components/group_editor_controls/group_editor_controls.test.tsx b/src/plugins/event_annotation/public/components/group_editor_controls/group_editor_controls.test.tsx
new file mode 100644
index 0000000000000..d3b9f384a185f
--- /dev/null
+++ b/src/plugins/event_annotation/public/components/group_editor_controls/group_editor_controls.test.tsx
@@ -0,0 +1,235 @@
+/*
+ * 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 React, { ChangeEvent, FormEvent } from 'react';
+import { EventAnnotationGroupConfig, getDefaultManualAnnotation } from '../../../common';
+import { ReactWrapper } from 'enzyme';
+import { mountWithIntl } from '@kbn/test-jest-helpers';
+import { GroupEditorControls } from './group_editor_controls';
+import { EuiTextAreaProps, EuiTextProps } from '@elastic/eui';
+import type { DataView } from '@kbn/data-views-plugin/common';
+import { act } from 'react-dom/test-utils';
+import type { QueryInputServices } from '@kbn/visualization-ui-components/public';
+import { AnnotationEditorControls, ENABLE_INDIVIDUAL_ANNOTATION_EDITING } from '..';
+
+jest.mock('@elastic/eui', () => {
+  return {
+    ...jest.requireActual('@elastic/eui'),
+    EuiDatePicker: () => <></>, // for some reason this component caused an infinite loop when the props updated
+  };
+});
+
+describe('event annotation group editor', () => {
+  const dataViewId = 'my-index-pattern';
+  const adHocDataViewId = 'ad-hoc';
+  const adHocDataViewSpec = {
+    id: adHocDataViewId,
+    title: 'Ad Hoc Data View',
+  };
+
+  const group: EventAnnotationGroupConfig = {
+    annotations: [],
+    description: '',
+    tags: [],
+    indexPatternId: dataViewId,
+    title: 'My group',
+    ignoreGlobalFilters: false,
+    dataViewSpec: adHocDataViewSpec,
+  };
+
+  let wrapper: ReactWrapper;
+  let updateMock: jest.Mock;
+  let setSelectedAnnotationMock: jest.Mock;
+
+  const TagSelector = (_props: { onTagsSelected: (tags: string[]) => void }) => <div />;
+
+  beforeEach(async () => {
+    updateMock = jest.fn();
+    setSelectedAnnotationMock = jest.fn();
+
+    wrapper = mountWithIntl(
+      <GroupEditorControls
+        group={group}
+        update={updateMock}
+        TagSelector={TagSelector}
+        dataViews={
+          [
+            {
+              id: dataViewId,
+              title: 'My Data View',
+            },
+          ] as DataView[]
+        }
+        selectedAnnotation={undefined}
+        setSelectedAnnotation={setSelectedAnnotationMock}
+        createDataView={(spec) =>
+          Promise.resolve({
+            id: spec.id,
+            title: spec.title,
+            toSpec: () => spec,
+          } as unknown as DataView)
+        }
+        queryInputServices={{} as QueryInputServices}
+        showValidation={false}
+      />
+    );
+
+    await act(async () => {
+      await new Promise((resolve) => setImmediate(resolve));
+      wrapper.update();
+    });
+  });
+
+  it('reports group updates', () => {
+    (
+      wrapper.find(
+        "EuiFieldText[data-test-subj='annotationGroupTitle']"
+      ) as ReactWrapper<EuiTextProps>
+    ).prop('onChange')!({
+      target: {
+        value: 'im a new title!',
+      } as Partial<EventTarget> as EventTarget,
+    } as FormEvent<HTMLDivElement>);
+
+    (
+      wrapper.find(
+        "EuiTextArea[data-test-subj='annotationGroupDescription']"
+      ) as ReactWrapper<EuiTextAreaProps>
+    ).prop('onChange')!({
+      target: {
+        value: 'im a new description!',
+      },
+    } as ChangeEvent<HTMLTextAreaElement>);
+
+    act(() => {
+      wrapper.find(TagSelector).prop('onTagsSelected')(['im a new tag!']);
+    });
+
+    // TODO - reenable data view selection tests when ENABLE_INDIVIDUAL_ANNOTATION_EDITING is set to true!
+    // this will happen in https://github.com/elastic/kibana/issues/158774
+
+    // const setDataViewId = (id: string) =>
+    //   (
+    //     wrapper.find(
+    //       "EuiSelect[data-test-subj='annotationDataViewSelection']"
+    //     ) as ReactWrapper<EuiSelectProps>
+    //   ).prop('onChange')!({ target: { value: id } } as React.ChangeEvent<HTMLSelectElement>);
+
+    // setDataViewId(dataViewId);
+    // setDataViewId(adHocDataViewId);
+
+    expect(updateMock.mock.calls).toMatchInlineSnapshot(`
+      Array [
+        Array [
+          Object {
+            "annotations": Array [],
+            "dataViewSpec": Object {
+              "id": "ad-hoc",
+              "title": "Ad Hoc Data View",
+            },
+            "description": "",
+            "ignoreGlobalFilters": false,
+            "indexPatternId": "my-index-pattern",
+            "tags": Array [],
+            "title": "im a new title!",
+          },
+        ],
+        Array [
+          Object {
+            "annotations": Array [],
+            "dataViewSpec": Object {
+              "id": "ad-hoc",
+              "title": "Ad Hoc Data View",
+            },
+            "description": "im a new description!",
+            "ignoreGlobalFilters": false,
+            "indexPatternId": "my-index-pattern",
+            "tags": Array [],
+            "title": "My group",
+          },
+        ],
+        Array [
+          Object {
+            "annotations": Array [],
+            "dataViewSpec": Object {
+              "id": "ad-hoc",
+              "title": "Ad Hoc Data View",
+            },
+            "description": "",
+            "ignoreGlobalFilters": false,
+            "indexPatternId": "my-index-pattern",
+            "tags": Array [
+              "im a new tag!",
+            ],
+            "title": "My group",
+          },
+        ],
+      ]
+    `);
+  });
+
+  if (ENABLE_INDIVIDUAL_ANNOTATION_EDITING) {
+    it('adds a new annotation group', () => {
+      act(() => {
+        wrapper.find('button[data-test-subj="addAnnotation"]').simulate('click');
+      });
+
+      expect(updateMock).toHaveBeenCalledTimes(2);
+      const newAnnotations = (updateMock.mock.calls[0][0] as EventAnnotationGroupConfig)
+        .annotations;
+      expect(newAnnotations.length).toBe(group.annotations.length + 1);
+      expect(wrapper.exists(AnnotationEditorControls)); // annotation controls opened
+    });
+
+    it('incorporates annotation updates into group', () => {
+      const annotations = [
+        getDefaultManualAnnotation('1', ''),
+        getDefaultManualAnnotation('2', ''),
+      ];
+
+      act(() => {
+        wrapper.setProps({
+          selectedAnnotation: annotations[0],
+          group: { ...group, annotations },
+        });
+      });
+
+      wrapper.find(AnnotationEditorControls).prop('onAnnotationChange')({
+        ...annotations[0],
+        color: 'newColor',
+      });
+
+      expect(updateMock).toHaveBeenCalledTimes(1);
+      expect(updateMock.mock.calls[0][0].annotations[0].color).toBe('newColor');
+      expect(setSelectedAnnotationMock).toHaveBeenCalledTimes(1);
+    });
+
+    it('removes an annotation from a group', () => {
+      const annotations = [
+        getDefaultManualAnnotation('1', ''),
+        getDefaultManualAnnotation('2', ''),
+      ];
+
+      act(() => {
+        wrapper.setProps({
+          group: { ...group, annotations },
+        });
+      });
+
+      act(() => {
+        wrapper
+          .find('button[data-test-subj="indexPattern-dimension-remove"]')
+          .last()
+          .simulate('click');
+      });
+
+      expect(updateMock).toHaveBeenCalledTimes(1);
+      expect(updateMock.mock.calls[0][0].annotations).toEqual(annotations.slice(0, 1));
+    });
+  }
+});
diff --git a/src/plugins/event_annotation/public/components/group_editor_controls/group_editor_controls.tsx b/src/plugins/event_annotation/public/components/group_editor_controls/group_editor_controls.tsx
new file mode 100644
index 0000000000000..c0a07a6fcb72b
--- /dev/null
+++ b/src/plugins/event_annotation/public/components/group_editor_controls/group_editor_controls.tsx
@@ -0,0 +1,212 @@
+/*
+ * 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 {
+  EuiFieldText,
+  EuiForm,
+  EuiFormRow,
+  EuiSelect,
+  EuiText,
+  EuiTextArea,
+  EuiTitle,
+} from '@elastic/eui';
+import { css } from '@emotion/react';
+import type { DataView, DataViewSpec } from '@kbn/data-views-plugin/common';
+import { i18n } from '@kbn/i18n';
+import { FormattedMessage } from '@kbn/i18n-react';
+import { SavedObjectsTaggingApiUiComponent } from '@kbn/saved-objects-tagging-oss-plugin/public';
+import { euiThemeVars } from '@kbn/ui-theme';
+import { QueryInputServices } from '@kbn/visualization-ui-components/public';
+import React, { useCallback, useEffect, useMemo, useState } from 'react';
+import { EVENT_ANNOTATION_APP_NAME } from '../../../common/constants';
+import { EventAnnotationConfig, EventAnnotationGroupConfig } from '../../../common';
+import { AnnotationEditorControls } from '../annotation_editor_controls';
+import { AnnotationList } from './annotation_list';
+
+export const ENABLE_INDIVIDUAL_ANNOTATION_EDITING = false;
+
+const isTitleValid = (title: string) => Boolean(title.length);
+
+export const isGroupValid = (group: EventAnnotationGroupConfig) => isTitleValid(group.title);
+
+export const GroupEditorControls = ({
+  group,
+  update,
+  setSelectedAnnotation: _setSelectedAnnotation,
+  selectedAnnotation,
+  TagSelector,
+  dataViews: globalDataViews,
+  createDataView,
+  queryInputServices,
+  showValidation,
+}: {
+  group: EventAnnotationGroupConfig;
+  update: (group: EventAnnotationGroupConfig) => void;
+  selectedAnnotation: EventAnnotationConfig | undefined;
+  setSelectedAnnotation: (annotation: EventAnnotationConfig) => void;
+  TagSelector: SavedObjectsTaggingApiUiComponent['SavedObjectSaveModalTagSelector'];
+  dataViews: DataView[];
+  createDataView: (spec: DataViewSpec) => Promise<DataView>;
+  queryInputServices: QueryInputServices;
+  showValidation: boolean;
+}) => {
+  // save the spec for the life of the component since the user might change their mind after selecting another data view
+  const [adHocDataView, setAdHocDataView] = useState<DataView>();
+
+  useEffect(() => {
+    if (group.dataViewSpec) {
+      createDataView(group.dataViewSpec).then(setAdHocDataView);
+    }
+  }, [createDataView, group.dataViewSpec]);
+
+  const setSelectedAnnotation = useCallback(
+    (newSelection: EventAnnotationConfig) => {
+      update({
+        ...group,
+        annotations: group.annotations.map((annotation) =>
+          annotation.id === newSelection.id ? newSelection : annotation
+        ),
+      });
+      _setSelectedAnnotation(newSelection);
+    },
+    [_setSelectedAnnotation, group, update]
+  );
+
+  const dataViews = useMemo(() => {
+    const items = [...globalDataViews];
+    if (adHocDataView) {
+      items.push(adHocDataView);
+    }
+    return items;
+  }, [adHocDataView, globalDataViews]);
+
+  const currentDataView = useMemo(
+    () => dataViews.find((dataView) => dataView.id === group.indexPatternId) || dataViews[0],
+    [dataViews, group.indexPatternId]
+  );
+
+  return !selectedAnnotation ? (
+    <>
+      <EuiTitle
+        size="xs"
+        css={css`
+          margin-bottom: ${euiThemeVars.euiSize};
+        `}
+      >
+        <h4>
+          <FormattedMessage id="eventAnnotation.groupEditor.details" defaultMessage="Details" />
+        </h4>
+      </EuiTitle>
+      <EuiForm>
+        <EuiFormRow
+          label={i18n.translate('eventAnnotation.groupEditor.title', {
+            defaultMessage: 'Title',
+          })}
+          isInvalid={showValidation && !isTitleValid(group.title)}
+          error={i18n.translate('eventAnnotation.groupEditor.titleRequired', {
+            defaultMessage: 'A title is required.',
+          })}
+        >
+          <EuiFieldText
+            data-test-subj="annotationGroupTitle"
+            value={group.title}
+            isInvalid={showValidation && !isTitleValid(group.title)}
+            onChange={({ target: { value } }) =>
+              update({
+                ...group,
+                title: value,
+              })
+            }
+          />
+        </EuiFormRow>
+        <EuiFormRow
+          label={i18n.translate('eventAnnotation.groupEditor.description', {
+            defaultMessage: 'Description',
+          })}
+          labelAppend={
+            <EuiText color="subdued" size="xs">
+              <FormattedMessage
+                id="eventAnnotation.groupEditor.optional"
+                defaultMessage="Optional"
+              />
+            </EuiText>
+          }
+        >
+          <EuiTextArea
+            data-test-subj="annotationGroupDescription"
+            value={group.description}
+            onChange={({ target: { value } }) =>
+              update({
+                ...group,
+                description: value,
+              })
+            }
+          />
+        </EuiFormRow>
+        <EuiFormRow>
+          <TagSelector
+            initialSelection={group.tags}
+            markOptional
+            onTagsSelected={(tags: string[]) =>
+              update({
+                ...group,
+                tags,
+              })
+            }
+          />
+        </EuiFormRow>
+        {ENABLE_INDIVIDUAL_ANNOTATION_EDITING && (
+          <>
+            <EuiFormRow
+              label={i18n.translate('eventAnnotation.groupEditor.dataView', {
+                defaultMessage: 'Data view',
+              })}
+            >
+              <EuiSelect
+                data-test-subj="annotationDataViewSelection"
+                options={dataViews.map(({ id: value, title, name }) => ({
+                  value,
+                  text: name ?? title,
+                }))}
+                value={group.indexPatternId}
+                onChange={({ target: { value } }) =>
+                  update({
+                    ...group,
+                    indexPatternId: value,
+                    dataViewSpec:
+                      value === adHocDataView?.id ? adHocDataView.toSpec(false) : undefined,
+                  })
+                }
+              />
+            </EuiFormRow>
+            <EuiFormRow
+              label={i18n.translate('eventAnnotation.groupEditor.addAnnotation', {
+                defaultMessage: 'Annotations',
+              })}
+            >
+              <AnnotationList
+                annotations={group.annotations}
+                selectAnnotation={setSelectedAnnotation}
+                update={(newAnnotations) => update({ ...group, annotations: newAnnotations })}
+              />
+            </EuiFormRow>
+          </>
+        )}
+      </EuiForm>
+    </>
+  ) : (
+    <AnnotationEditorControls
+      annotation={selectedAnnotation}
+      onAnnotationChange={(changes) => setSelectedAnnotation({ ...selectedAnnotation, ...changes })}
+      dataView={currentDataView}
+      getDefaultRangeEnd={(rangeStart) => rangeStart}
+      queryInputServices={queryInputServices}
+      appName={EVENT_ANNOTATION_APP_NAME}
+    />
+  );
+};
diff --git a/src/plugins/event_annotation/public/components/group_editor_controls/index.ts b/src/plugins/event_annotation/public/components/group_editor_controls/index.ts
new file mode 100644
index 0000000000000..b9db18b4682f4
--- /dev/null
+++ b/src/plugins/event_annotation/public/components/group_editor_controls/index.ts
@@ -0,0 +1,9 @@
+/*
+ * 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.
+ */
+
+export * from './group_editor_controls';
diff --git a/src/plugins/event_annotation/public/components/group_editor_flyout.test.tsx b/src/plugins/event_annotation/public/components/group_editor_flyout.test.tsx
new file mode 100644
index 0000000000000..7f732aa882ffb
--- /dev/null
+++ b/src/plugins/event_annotation/public/components/group_editor_flyout.test.tsx
@@ -0,0 +1,134 @@
+/*
+ * 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 { EuiButton, EuiFlyout } from '@elastic/eui';
+import { EventAnnotationGroupConfig, getDefaultManualAnnotation } from '../../common';
+import { taggingApiMock } from '@kbn/saved-objects-tagging-oss-plugin/public/api.mock';
+import { shallow, ShallowWrapper } from 'enzyme';
+import React from 'react';
+import { GroupEditorControls } from './group_editor_controls';
+import { GroupEditorFlyout } from './group_editor_flyout';
+import { DataView } from '@kbn/data-views-plugin/common';
+import type { QueryInputServices } from '@kbn/visualization-ui-components/public';
+
+const simulateButtonClick = (component: ShallowWrapper, selector: string) => {
+  (component.find(selector) as ShallowWrapper<Parameters<typeof EuiButton>[0]>).prop('onClick')!(
+    {} as any
+  );
+};
+
+const SELECTORS = {
+  SAVE_BUTTON: '[data-test-subj="saveAnnotationGroup"]',
+  CANCEL_BUTTON: '[data-test-subj="cancelGroupEdit"]',
+  BACK_BUTTON: '[data-test-subj="backToGroupSettings"]',
+};
+
+const assertGroupEditingState = (component: ShallowWrapper) => {
+  expect(component.exists(SELECTORS.SAVE_BUTTON)).toBeTruthy();
+  expect(component.exists(SELECTORS.CANCEL_BUTTON)).toBeTruthy();
+  expect(component.exists(SELECTORS.BACK_BUTTON)).toBeFalsy();
+};
+
+const assertAnnotationEditingState = (component: ShallowWrapper) => {
+  expect(component.exists(SELECTORS.BACK_BUTTON)).toBeTruthy();
+  expect(component.exists(SELECTORS.SAVE_BUTTON)).toBeFalsy();
+  expect(component.exists(SELECTORS.CANCEL_BUTTON)).toBeFalsy();
+};
+
+describe('group editor flyout', () => {
+  const annotation = getDefaultManualAnnotation('my-id', 'some-timestamp');
+
+  const group: EventAnnotationGroupConfig = {
+    annotations: [annotation],
+    description: '',
+    tags: [],
+    indexPatternId: 'some-id',
+    title: 'My group',
+    ignoreGlobalFilters: false,
+  };
+
+  const mockTaggingApi = taggingApiMock.create();
+
+  let component: ShallowWrapper;
+  let onSave: jest.Mock;
+  let onClose: jest.Mock;
+  let updateGroup: jest.Mock;
+
+  beforeEach(() => {
+    onSave = jest.fn();
+    onClose = jest.fn();
+    updateGroup = jest.fn();
+    component = shallow(
+      <GroupEditorFlyout
+        group={group}
+        onSave={onSave}
+        onClose={onClose}
+        updateGroup={updateGroup}
+        dataViews={[
+          {
+            id: 'some-id',
+            title: 'My Data View',
+          } as DataView,
+        ]}
+        savedObjectsTagging={mockTaggingApi}
+        createDataView={jest.fn()}
+        queryInputServices={{} as QueryInputServices}
+      />
+    );
+  });
+
+  it('renders controls', () => {
+    expect(component.find(GroupEditorControls).props()).toMatchSnapshot();
+  });
+  it('signals close', () => {
+    component.find(EuiFlyout).prop('onClose')({} as MouseEvent);
+    simulateButtonClick(component, SELECTORS.CANCEL_BUTTON);
+
+    expect(onClose).toHaveBeenCalledTimes(2);
+  });
+  it('signals save', () => {
+    simulateButtonClick(component, SELECTORS.SAVE_BUTTON);
+
+    expect(onSave).toHaveBeenCalledTimes(1);
+  });
+  it("doesn't save invalid group config", () => {
+    component.setProps({
+      group: { ...group, title: '' },
+    });
+
+    simulateButtonClick(component, SELECTORS.SAVE_BUTTON);
+
+    expect(onSave).not.toHaveBeenCalled();
+  });
+  it('reports group updates', () => {
+    const newGroup = { ...group, description: 'new description' };
+    component.find(GroupEditorControls).prop('update')(newGroup);
+
+    expect(updateGroup).toHaveBeenCalledWith(newGroup);
+  });
+  test('specific annotation editing', () => {
+    assertGroupEditingState(component);
+
+    component.find(GroupEditorControls).prop('setSelectedAnnotation')(annotation);
+
+    assertAnnotationEditingState(component);
+
+    component.find(SELECTORS.BACK_BUTTON).simulate('click');
+
+    assertGroupEditingState(component);
+  });
+  it('removes active annotation instead of signaling close', () => {
+    component.find(GroupEditorControls).prop('setSelectedAnnotation')(annotation);
+
+    assertAnnotationEditingState(component);
+
+    component.find(EuiFlyout).prop('onClose')({} as MouseEvent);
+
+    assertGroupEditingState(component);
+  });
+});
diff --git a/src/plugins/event_annotation/public/components/group_editor_flyout.tsx b/src/plugins/event_annotation/public/components/group_editor_flyout.tsx
new file mode 100644
index 0000000000000..3e296951542a8
--- /dev/null
+++ b/src/plugins/event_annotation/public/components/group_editor_flyout.tsx
@@ -0,0 +1,146 @@
+/*
+ * 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 {
+  EuiFlyout,
+  EuiFlyoutHeader,
+  EuiTitle,
+  EuiFlyoutBody,
+  EuiFlyoutFooter,
+  EuiFlexGroup,
+  EuiFlexItem,
+  EuiButtonEmpty,
+  EuiButton,
+  htmlIdGenerator,
+} from '@elastic/eui';
+import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
+import { FormattedMessage } from '@kbn/i18n-react';
+import { SavedObjectsTaggingApi } from '@kbn/saved-objects-tagging-oss-plugin/public';
+import { DataView, DataViewSpec } from '@kbn/data-views-plugin/common';
+import type { QueryInputServices } from '@kbn/visualization-ui-components/public';
+import { EventAnnotationConfig, EventAnnotationGroupConfig } from '../../common';
+import { GroupEditorControls, isGroupValid } from './group_editor_controls';
+
+export const GroupEditorFlyout = ({
+  group,
+  updateGroup,
+  onClose: parentOnClose,
+  onSave,
+  savedObjectsTagging,
+  dataViews,
+  createDataView,
+  queryInputServices,
+}: {
+  group: EventAnnotationGroupConfig;
+  updateGroup: (newGroup: EventAnnotationGroupConfig) => void;
+  onClose: () => void;
+  onSave: () => void;
+  savedObjectsTagging: SavedObjectsTaggingApi;
+  dataViews: DataView[];
+  createDataView: (spec: DataViewSpec) => Promise<DataView>;
+  queryInputServices: QueryInputServices;
+}) => {
+  const flyoutHeadingId = useMemo(() => htmlIdGenerator()(), []);
+  const flyoutBodyOverflowRef = useRef<Element | null>(null);
+  useEffect(() => {
+    if (!flyoutBodyOverflowRef.current) {
+      flyoutBodyOverflowRef.current = document.querySelector('.euiFlyoutBody__overflow');
+    }
+  }, []);
+
+  const [hasAttemptedSave, setHasAttemptedSave] = useState(false);
+
+  const resetContentScroll = useCallback(
+    () => flyoutBodyOverflowRef.current && flyoutBodyOverflowRef.current.scroll(0, 0),
+    []
+  );
+
+  const [selectedAnnotation, _setSelectedAnnotation] = useState<EventAnnotationConfig>();
+  const setSelectedAnnotation = useCallback(
+    (newValue: EventAnnotationConfig | undefined) => {
+      if ((!newValue && selectedAnnotation) || (newValue && !selectedAnnotation))
+        resetContentScroll();
+      _setSelectedAnnotation(newValue);
+    },
+    [resetContentScroll, selectedAnnotation]
+  );
+
+  const onClose = () => (selectedAnnotation ? setSelectedAnnotation(undefined) : parentOnClose());
+
+  return (
+    <EuiFlyout onClose={onClose} size={'s'}>
+      <EuiFlyoutHeader hasBorder aria-labelledby={flyoutHeadingId}>
+        <EuiTitle size="s">
+          <h2 id={flyoutHeadingId}>
+            <FormattedMessage
+              id="eventAnnotation.groupEditorFlyout.title"
+              defaultMessage="Edit annotation group"
+            />
+          </h2>
+        </EuiTitle>
+      </EuiFlyoutHeader>
+
+      <EuiFlyoutBody>
+        <GroupEditorControls
+          group={group}
+          update={updateGroup}
+          selectedAnnotation={selectedAnnotation}
+          setSelectedAnnotation={setSelectedAnnotation}
+          TagSelector={savedObjectsTagging.ui.components.SavedObjectSaveModalTagSelector}
+          dataViews={dataViews}
+          createDataView={createDataView}
+          queryInputServices={queryInputServices}
+          showValidation={hasAttemptedSave}
+        />
+      </EuiFlyoutBody>
+
+      <EuiFlyoutFooter>
+        <EuiFlexGroup justifyContent="spaceBetween">
+          {selectedAnnotation ? (
+            <EuiFlexItem grow={false}>
+              <EuiButtonEmpty
+                iconType="arrowLeft"
+                data-test-subj="backToGroupSettings"
+                onClick={() => setSelectedAnnotation(undefined)}
+              >
+                <FormattedMessage id="eventAnnotation.edit.back" defaultMessage="Back" />
+              </EuiButtonEmpty>
+            </EuiFlexItem>
+          ) : (
+            <>
+              <EuiFlexItem grow={false}>
+                <EuiButtonEmpty data-test-subj="cancelGroupEdit" onClick={onClose}>
+                  <FormattedMessage id="eventAnnotation.edit.cancel" defaultMessage="Cancel" />
+                </EuiButtonEmpty>
+              </EuiFlexItem>
+              <EuiFlexItem grow={false}>
+                <EuiButton
+                  iconType="save"
+                  data-test-subj="saveAnnotationGroup"
+                  fill
+                  onClick={() => {
+                    setHasAttemptedSave(true);
+
+                    if (isGroupValid(group)) {
+                      onSave();
+                    }
+                  }}
+                >
+                  <FormattedMessage
+                    id="eventAnnotation.edit.save"
+                    defaultMessage="Save annotation group"
+                  />
+                </EuiButton>
+              </EuiFlexItem>
+            </>
+          )}
+        </EuiFlexGroup>
+      </EuiFlyoutFooter>
+    </EuiFlyout>
+  );
+};
diff --git a/src/plugins/event_annotation/public/components/index.ts b/src/plugins/event_annotation/public/components/index.ts
new file mode 100644
index 0000000000000..5433625c6344b
--- /dev/null
+++ b/src/plugins/event_annotation/public/components/index.ts
@@ -0,0 +1,13 @@
+/*
+ * 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.
+ */
+
+export { AnnotationEditorControls, annotationsIconSet } from './annotation_editor_controls';
+
+export * from './group_editor_controls';
+
+export * from './get_annotation_accessor';
diff --git a/src/plugins/event_annotation/public/components/table_list.test.tsx b/src/plugins/event_annotation/public/components/table_list.test.tsx
new file mode 100644
index 0000000000000..b6d20e64dc8d4
--- /dev/null
+++ b/src/plugins/event_annotation/public/components/table_list.test.tsx
@@ -0,0 +1,222 @@
+/*
+ * 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 React from 'react';
+import {
+  EventAnnotationGroupTableList,
+  SAVED_OBJECTS_LIMIT_SETTING,
+  SAVED_OBJECTS_PER_PAGE_SETTING,
+} from './table_list';
+import {
+  TableListViewTable,
+  type UserContentCommonSchema,
+} from '@kbn/content-management-table-list-view-table';
+import { EventAnnotationServiceType } from '../event_annotation_service/types';
+import { IUiSettingsClient } from '@kbn/core-ui-settings-browser';
+import { shallow, ShallowWrapper } from 'enzyme';
+import { EventAnnotationGroupConfig, EVENT_ANNOTATION_GROUP_TYPE } from '../../common';
+import { taggingApiMock } from '@kbn/saved-objects-tagging-oss-plugin/public/mocks';
+
+import { act } from 'react-dom/test-utils';
+import { GroupEditorFlyout } from './group_editor_flyout';
+import { DataView } from '@kbn/data-views-plugin/common';
+import { QueryInputServices } from '@kbn/visualization-ui-components/public';
+import { toastsServiceMock } from '@kbn/core-notifications-browser-mocks/src/toasts_service.mock';
+import { IToasts } from '@kbn/core-notifications-browser';
+
+describe('annotation list view', () => {
+  const adHocDVId = 'ad-hoc';
+
+  const group: EventAnnotationGroupConfig = {
+    annotations: [],
+    description: '',
+    tags: [],
+    indexPatternId: adHocDVId,
+    title: 'My group',
+    ignoreGlobalFilters: false,
+    dataViewSpec: {
+      id: adHocDVId,
+      title: 'Ad hoc data view',
+    },
+  };
+
+  let wrapper: ShallowWrapper<typeof EventAnnotationGroupTableList>;
+  let mockEventAnnotationService: EventAnnotationServiceType;
+  let mockToasts: IToasts;
+
+  beforeEach(() => {
+    mockEventAnnotationService = {
+      findAnnotationGroupContent: jest.fn(),
+      deleteAnnotationGroups: jest.fn(),
+      loadAnnotationGroup: jest.fn().mockResolvedValue(group),
+      updateAnnotationGroup: jest.fn(() => Promise.resolve()),
+    } as Partial<EventAnnotationServiceType> as EventAnnotationServiceType;
+
+    const mockUiSettings = {
+      get: jest.fn(
+        (key) =>
+          ({
+            [SAVED_OBJECTS_LIMIT_SETTING]: 30,
+            [SAVED_OBJECTS_PER_PAGE_SETTING]: 10,
+          }[key])
+      ),
+    } as Partial<IUiSettingsClient> as IUiSettingsClient;
+
+    mockToasts = toastsServiceMock.createStartContract();
+
+    wrapper = shallow<typeof EventAnnotationGroupTableList>(
+      <EventAnnotationGroupTableList
+        eventAnnotationService={mockEventAnnotationService}
+        savedObjectsTagging={taggingApiMock.create()}
+        uiSettings={mockUiSettings}
+        visualizeCapabilities={{
+          delete: true,
+          save: true,
+        }}
+        parentProps={{
+          onFetchSuccess: () => {},
+          setPageDataTestSubject: () => {},
+        }}
+        dataViews={[
+          {
+            id: 'some-id',
+            title: 'Some data view',
+          } as DataView,
+        ]}
+        createDataView={() => Promise.resolve({} as DataView)}
+        queryInputServices={{} as QueryInputServices}
+        toasts={mockToasts}
+        navigateToLens={() => {}}
+      />
+    );
+  });
+
+  it('searches for groups', () => {
+    const searchQuery = 'My Search Query';
+    const references = [{ id: 'first_id', type: 'sometype' }];
+    const referencesToExclude = [{ id: 'second_id', type: 'sometype' }];
+    wrapper.find(TableListViewTable).prop('findItems')(searchQuery, {
+      references,
+      referencesToExclude,
+    });
+
+    expect(mockEventAnnotationService.findAnnotationGroupContent).toHaveBeenCalledWith(
+      'My Search Query',
+      30,
+      [{ id: 'first_id', type: 'sometype' }],
+      [{ id: 'second_id', type: 'sometype' }]
+    );
+  });
+
+  describe('deleting groups', () => {
+    it('prevent deleting when user is missing perms', () => {
+      wrapper.setProps({ visualizeCapabilities: { delete: false } });
+
+      expect(wrapper.find(TableListViewTable).prop('deleteItems')).toBeUndefined();
+    });
+
+    it('deletes groups using the service', () => {
+      expect(wrapper.find(TableListViewTable).prop('deleteItems')).toBeDefined();
+
+      wrapper.find(TableListViewTable).prop('deleteItems')!([
+        {
+          id: 'some-id-1',
+          references: [
+            {
+              type: 'index-pattern',
+              name: 'metrics-*',
+              id: 'metrics-*',
+            },
+          ],
+          type: EVENT_ANNOTATION_GROUP_TYPE,
+          updatedAt: '',
+          attributes: {
+            title: 'group1',
+          },
+        },
+        {
+          id: 'some-id-2',
+          references: [],
+          type: EVENT_ANNOTATION_GROUP_TYPE,
+          updatedAt: '',
+          attributes: {
+            title: 'group2',
+          },
+        },
+      ]);
+
+      expect((mockEventAnnotationService.deleteAnnotationGroups as jest.Mock).mock.calls)
+        .toMatchInlineSnapshot(`
+        Array [
+          Array [
+            Array [
+              "some-id-1",
+              "some-id-2",
+            ],
+          ],
+        ]
+      `);
+    });
+  });
+
+  describe('editing groups', () => {
+    it('prevents editing when user is missing perms', () => {
+      wrapper.setProps({ visualizeCapabilities: { save: false } });
+
+      expect(wrapper.find(TableListViewTable).prop('deleteItems')).toBeUndefined();
+    });
+
+    it('edits existing group', async () => {
+      expect(wrapper.find(GroupEditorFlyout).exists()).toBeFalsy();
+      const initialBouncerValue = wrapper.find(TableListViewTable).prop('refreshListBouncer');
+
+      act(() => {
+        wrapper.find(TableListViewTable).prop('editItem')!({
+          id: '1234',
+        } as UserContentCommonSchema);
+      });
+
+      // wait one tick to give promise time to settle
+      await new Promise((resolve) => setTimeout(resolve, 0));
+
+      expect(mockEventAnnotationService.loadAnnotationGroup).toHaveBeenCalledWith('1234');
+
+      expect(wrapper.find(GroupEditorFlyout).exists()).toBeTruthy();
+
+      const updatedGroup = { ...group, tags: ['my-new-tag'] };
+
+      wrapper.find(GroupEditorFlyout).prop('updateGroup')(updatedGroup);
+
+      wrapper.find(GroupEditorFlyout).prop('onSave')();
+
+      await new Promise((resolve) => setTimeout(resolve, 0));
+
+      expect(mockEventAnnotationService.updateAnnotationGroup).toHaveBeenCalledWith(
+        updatedGroup,
+        '1234'
+      );
+
+      expect(wrapper.find(GroupEditorFlyout).exists()).toBeFalsy();
+      expect(wrapper.find(TableListViewTable).prop('refreshListBouncer')).not.toBe(
+        initialBouncerValue
+      ); // (should refresh list)
+    });
+
+    it('opens editor when title is clicked', async () => {
+      act(() => {
+        wrapper.find(TableListViewTable).prop('onClickTitle')!({
+          id: '1234',
+        } as UserContentCommonSchema);
+      });
+
+      await new Promise((resolve) => setTimeout(resolve, 0));
+
+      expect(wrapper.find(GroupEditorFlyout).exists()).toBeTruthy();
+    });
+  });
+});
diff --git a/src/plugins/event_annotation/public/components/table_list.tsx b/src/plugins/event_annotation/public/components/table_list.tsx
new file mode 100644
index 0000000000000..84535f2ffefc5
--- /dev/null
+++ b/src/plugins/event_annotation/public/components/table_list.tsx
@@ -0,0 +1,205 @@
+/*
+ * 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 React, { useCallback, useState } from 'react';
+import { TableListViewTable } from '@kbn/content-management-table-list-view-table';
+import type { TableListTabParentProps } from '@kbn/content-management-tabbed-table-list-view';
+import { i18n } from '@kbn/i18n';
+import type { IUiSettingsClient } from '@kbn/core-ui-settings-browser';
+import { SavedObjectsFindOptionsReference } from '@kbn/core-saved-objects-api-browser';
+import { SavedObjectsTaggingApi } from '@kbn/saved-objects-tagging-oss-plugin/public';
+import { DataView, DataViewSpec } from '@kbn/data-views-plugin/common';
+import type { QueryInputServices } from '@kbn/visualization-ui-components/public';
+import { IToasts } from '@kbn/core-notifications-browser';
+import { EuiButton, EuiEmptyPrompt, EuiTitle } from '@elastic/eui';
+import { FormattedMessage } from '@kbn/i18n-react';
+import { EventAnnotationGroupConfig } from '../../common';
+import type { EventAnnotationServiceType } from '../event_annotation_service/types';
+import { EventAnnotationGroupContent } from '../../common/types';
+import { GroupEditorFlyout } from './group_editor_flyout';
+
+export const SAVED_OBJECTS_LIMIT_SETTING = 'savedObjects:listingLimit';
+export const SAVED_OBJECTS_PER_PAGE_SETTING = 'savedObjects:perPage';
+
+const getCustomColumn = (dataViews: DataView[]) => {
+  const dataViewNameMap = Object.fromEntries(
+    dataViews.map((dataView) => [dataView.id, dataView.name ?? dataView.title])
+  );
+
+  return {
+    field: 'dataView',
+    name: i18n.translate('eventAnnotation.tableList.dataView', {
+      defaultMessage: 'Data view',
+    }),
+    sortable: false,
+    width: '150px',
+    render: (_field: string, record: EventAnnotationGroupContent) => (
+      <div>
+        {record.attributes.dataViewSpec
+          ? record.attributes.dataViewSpec.name
+          : dataViewNameMap[record.attributes.indexPatternId]}
+      </div>
+    ),
+  };
+};
+
+export const EventAnnotationGroupTableList = ({
+  uiSettings,
+  eventAnnotationService,
+  visualizeCapabilities,
+  savedObjectsTagging,
+  parentProps,
+  dataViews,
+  createDataView,
+  queryInputServices,
+  toasts,
+  navigateToLens,
+}: {
+  uiSettings: IUiSettingsClient;
+  eventAnnotationService: EventAnnotationServiceType;
+  visualizeCapabilities: Record<string, boolean | Record<string, boolean>>;
+  savedObjectsTagging: SavedObjectsTaggingApi;
+  parentProps: TableListTabParentProps;
+  dataViews: DataView[];
+  createDataView: (spec: DataViewSpec) => Promise<DataView>;
+  queryInputServices: QueryInputServices;
+  toasts: IToasts;
+  navigateToLens: () => void;
+}) => {
+  const listingLimit = uiSettings.get(SAVED_OBJECTS_LIMIT_SETTING);
+  const initialPageSize = uiSettings.get(SAVED_OBJECTS_PER_PAGE_SETTING);
+
+  const [refreshListBouncer, setRefreshListBouncer] = useState(false);
+
+  const refreshList = useCallback(() => {
+    setRefreshListBouncer(!refreshListBouncer);
+  }, [refreshListBouncer]);
+
+  const fetchItems = useCallback(
+    (
+      searchTerm: string,
+      {
+        references,
+        referencesToExclude,
+      }: {
+        references?: SavedObjectsFindOptionsReference[];
+        referencesToExclude?: SavedObjectsFindOptionsReference[];
+      } = {}
+    ) => {
+      // todo - allow page size changes
+      return eventAnnotationService.findAnnotationGroupContent(
+        searchTerm,
+        listingLimit, // TODO is this right?
+        references,
+        referencesToExclude
+      );
+    },
+    [eventAnnotationService, listingLimit]
+  );
+
+  const editItem = useCallback(
+    ({ id }: EventAnnotationGroupContent) => {
+      if (visualizeCapabilities.save) {
+        eventAnnotationService
+          .loadAnnotationGroup(id)
+          .then((group) => setGroupToEditInfo({ group, id }));
+      }
+    },
+    [eventAnnotationService, visualizeCapabilities.save]
+  );
+
+  const [groupToEditInfo, setGroupToEditInfo] = useState<{
+    group: EventAnnotationGroupConfig;
+    id: string;
+  }>();
+
+  const flyout = groupToEditInfo ? (
+    <GroupEditorFlyout
+      group={groupToEditInfo.group}
+      updateGroup={(newGroup) => setGroupToEditInfo({ group: newGroup, id: groupToEditInfo.id })}
+      onClose={() => setGroupToEditInfo(undefined)}
+      onSave={() =>
+        (groupToEditInfo.id
+          ? eventAnnotationService.updateAnnotationGroup(groupToEditInfo.group, groupToEditInfo.id)
+          : eventAnnotationService.createAnnotationGroup(groupToEditInfo.group)
+        ).then(() => {
+          setGroupToEditInfo(undefined);
+          toasts.addSuccess(`Saved "${groupToEditInfo.group.title}"`);
+          refreshList();
+        })
+      }
+      savedObjectsTagging={savedObjectsTagging}
+      dataViews={dataViews}
+      createDataView={createDataView}
+      queryInputServices={queryInputServices}
+    />
+  ) : undefined;
+
+  return (
+    <>
+      <TableListViewTable<EventAnnotationGroupContent>
+        refreshListBouncer={refreshListBouncer}
+        tableCaption={i18n.translate('eventAnnotation.tableList.listTitle', {
+          defaultMessage: 'Annotation Library',
+        })}
+        findItems={fetchItems}
+        deleteItems={
+          visualizeCapabilities.delete
+            ? (items) => eventAnnotationService.deleteAnnotationGroups(items.map(({ id }) => id))
+            : undefined
+        }
+        editItem={editItem}
+        listingLimit={listingLimit}
+        initialPageSize={initialPageSize}
+        initialFilter={''}
+        customTableColumn={getCustomColumn(dataViews)}
+        emptyPrompt={
+          <EuiEmptyPrompt
+            title={
+              <EuiTitle>
+                <h2>
+                  <FormattedMessage
+                    id="eventAnnotation.tableList.emptyPrompt.title"
+                    defaultMessage="Create your first annotation in Lens"
+                  />
+                </h2>
+              </EuiTitle>
+            }
+            body={
+              <p>
+                <FormattedMessage
+                  id="eventAnnotation.tableList.emptyPrompt.body"
+                  defaultMessage="You can create and save annotations for use across multiple visualization in the
+                    Lens visualization editor."
+                />
+              </p>
+            }
+            actions={
+              <EuiButton onClick={navigateToLens}>
+                <FormattedMessage
+                  id="eventAnnotation.tableList.emptyPrompt.cta"
+                  defaultMessage="Create new annotation in Lens"
+                />
+              </EuiButton>
+            }
+            iconType="flag"
+          />
+        }
+        entityName={i18n.translate('eventAnnotation.tableList.entityName', {
+          defaultMessage: 'annotation group',
+        })}
+        entityNamePlural={i18n.translate('eventAnnotation.tableList.entityNamePlural', {
+          defaultMessage: 'annotation groups',
+        })}
+        onClickTitle={editItem}
+        {...parentProps}
+      />
+      {flyout}
+    </>
+  );
+};
diff --git a/src/plugins/event_annotation/public/event_annotation_service/__snapshots__/service.test.ts.snap b/src/plugins/event_annotation/public/event_annotation_service/__snapshots__/service.test.ts.snap
index 73073c0e7ea16..0bd643095dcdc 100644
--- a/src/plugins/event_annotation/public/event_annotation_service/__snapshots__/service.test.ts.snap
+++ b/src/plugins/event_annotation/public/event_annotation_service/__snapshots__/service.test.ts.snap
@@ -1,5 +1,80 @@
 // Jest Snapshot v1, https://goo.gl/fbAQLP
 
+exports[`Event Annotation Service findAnnotationGroupContent should retrieve saved objects and format them 1`] = `
+Object {
+  "hits": Array [
+    Object {
+      "attributes": Object {
+        "dataViewSpec": undefined,
+        "description": undefined,
+        "indexPatternId": undefined,
+        "title": undefined,
+      },
+      "id": "nonExistingGroup",
+      "references": Array [],
+      "type": undefined,
+      "updatedAt": "",
+    },
+    Object {
+      "attributes": Object {
+        "dataViewSpec": undefined,
+        "description": "",
+        "indexPatternId": "ipid",
+        "title": "groupTitle",
+      },
+      "id": undefined,
+      "references": Array [
+        Object {
+          "id": "ipid",
+          "name": "ipid",
+          "type": "index-pattern",
+        },
+        Object {
+          "id": "some-tag",
+          "name": "some-tag",
+          "type": "tag",
+        },
+      ],
+      "type": "event-annotation-group",
+      "updatedAt": "",
+    },
+    Object {
+      "attributes": Object {
+        "dataViewSpec": undefined,
+        "description": undefined,
+        "indexPatternId": "ipid",
+        "title": "groupTitle",
+      },
+      "id": "multiAnnotations",
+      "references": Array [
+        Object {
+          "id": "ipid",
+          "name": "ipid",
+          "type": "index-pattern",
+        },
+      ],
+      "type": "event-annotation-group",
+      "updatedAt": "",
+    },
+    Object {
+      "attributes": Object {
+        "dataViewSpec": Object {
+          "id": "my-id",
+        },
+        "description": undefined,
+        "indexPatternId": "my-id",
+        "title": "groupTitle",
+      },
+      "id": "multiAnnotations",
+      "references": Array [],
+      "type": "event-annotation-group",
+      "updatedAt": "",
+    },
+  ],
+  "total": 10,
+}
+`;
+
 exports[`Event Annotation Service loadAnnotationGroup should properly load an annotation group with a multiple annotation 1`] = `
 Object {
   "annotations": undefined,
@@ -7,7 +82,7 @@ Object {
   "description": undefined,
   "ignoreGlobalFilters": undefined,
   "indexPatternId": "ipid",
-  "tags": undefined,
+  "tags": Array [],
   "title": "groupTitle",
 }
 `;
diff --git a/src/plugins/event_annotation/public/event_annotation_service/helpers.ts b/src/plugins/event_annotation/public/event_annotation_service/helpers.ts
index afe64a5a47eb2..31dabb34cdc09 100644
--- a/src/plugins/event_annotation/public/event_annotation_service/helpers.ts
+++ b/src/plugins/event_annotation/public/event_annotation_service/helpers.ts
@@ -5,7 +5,6 @@
  * in compliance with, at your election, the Elastic License 2.0 or the Server
  * Side Public License, v 1.
  */
-import { i18n } from '@kbn/i18n';
 import { euiLightVars } from '@kbn/ui-theme';
 import {
   EventAnnotationConfig,
@@ -17,13 +16,6 @@ export const defaultAnnotationColor = euiLightVars.euiColorAccent;
 // Do not compute it live as dependencies will add tens of Kbs to the plugin
 export const defaultAnnotationRangeColor = `#F04E981A`; // defaultAnnotationColor with opacity 0.1
 
-export const defaultAnnotationLabel = i18n.translate(
-  'eventAnnotation.manualAnnotation.defaultAnnotationLabel',
-  {
-    defaultMessage: 'Event',
-  }
-);
-
 export const isRangeAnnotationConfig = (
   annotation?: EventAnnotationConfig
 ): annotation is RangeEventAnnotationConfig => {
diff --git a/src/plugins/event_annotation/public/event_annotation_service/service.test.ts b/src/plugins/event_annotation/public/event_annotation_service/service.test.ts
index c131ca288a88d..905435bc4f4d0 100644
--- a/src/plugins/event_annotation/public/event_annotation_service/service.test.ts
+++ b/src/plugins/event_annotation/public/event_annotation_service/service.test.ts
@@ -6,6 +6,7 @@
  * Side Public License, v 1.
  */
 
+import { SavedObjectsFindResponse } from '@kbn/core-saved-objects-api-browser';
 import { CoreStart, SimpleSavedObject } from '@kbn/core/public';
 import { coreMock } from '@kbn/core/public/mocks';
 import { SavedObjectsManagementPluginStart } from '@kbn/saved-objects-management-plugin/public';
@@ -41,6 +42,11 @@ const annotationGroupResolveMocks: Record<string, AnnotationGroupSavedObject> =
         name: 'ipid',
         type: 'index-pattern',
       },
+      {
+        id: 'some-tag',
+        name: 'some-tag',
+        type: 'tag',
+      },
     ],
   } as Partial<AnnotationGroupSavedObject> as AnnotationGroupSavedObject,
   multiAnnotations: {
@@ -138,6 +144,10 @@ describe('Event Annotation Service', () => {
       const typedId = id as keyof typeof annotationGroupResolveMocks;
       return annotationGroupResolveMocks[typedId];
     });
+    (core.savedObjects.client.find as jest.Mock).mockResolvedValue({
+      total: 10,
+      savedObjects: Object.values(annotationGroupResolveMocks),
+    } as Pick<SavedObjectsFindResponse<EventAnnotationGroupAttributes>, 'total' | 'savedObjects'>);
     (core.savedObjects.client.bulkCreate as jest.Mock).mockImplementation(() => {
       return annotationResolveMocks.multiAnnotations;
     });
@@ -474,7 +484,9 @@ describe('Event Annotation Service', () => {
           "description": "",
           "ignoreGlobalFilters": false,
           "indexPatternId": "ipid",
-          "tags": Array [],
+          "tags": Array [
+            "some-tag",
+          ],
           "title": "groupTitle",
         }
       `);
@@ -490,16 +502,53 @@ describe('Event Annotation Service', () => {
       expect(group.indexPatternId).toBe(group.dataViewSpec?.id);
     });
   });
-  // describe.skip('deleteAnnotationGroup', () => {
-  //   it('deletes annotation group along with annotations that reference them', async () => {
-  //     await eventAnnotationService.deleteAnnotationGroup('multiAnnotations');
-  //     expect(core.savedObjects.client.bulkDelete).toHaveBeenCalledWith([
-  //       { id: 'multiAnnotations', type: 'event-annotation-group' },
-  //       { id: 'annotation1', type: 'event-annotation' },
-  //       { id: 'annotation2', type: 'event-annotation' },
-  //     ]);
-  //   });
-  // });
+  describe('findAnnotationGroupContent', () => {
+    it('should retrieve saved objects and format them', async () => {
+      const searchTerm = 'my search';
+
+      const content = await eventAnnotationService.findAnnotationGroupContent(searchTerm, 20, [
+        { type: 'mytype', id: '1234' },
+      ]);
+
+      expect(content).toMatchSnapshot();
+
+      expect((core.savedObjects.client.find as jest.Mock).mock.calls).toMatchInlineSnapshot(`
+        Array [
+          Array [
+            Object {
+              "defaultSearchOperator": "AND",
+              "hasNoReference": undefined,
+              "hasReference": Array [
+                Object {
+                  "id": "1234",
+                  "type": "mytype",
+                },
+              ],
+              "page": 1,
+              "perPage": 20,
+              "search": "my search*",
+              "searchFields": Array [
+                "title^3",
+                "description",
+              ],
+              "type": Array [
+                "event-annotation-group",
+              ],
+            },
+          ],
+        ]
+      `);
+    });
+  });
+  describe('deleteAnnotationGroups', () => {
+    it('deletes annotation group along with annotations that reference them', async () => {
+      await eventAnnotationService.deleteAnnotationGroups(['id1', 'id2']);
+      expect(core.savedObjects.client.bulkDelete).toHaveBeenCalledWith([
+        { id: 'id1', type: 'event-annotation-group' },
+        { id: 'id2', type: 'event-annotation-group' },
+      ]);
+    });
+  });
   describe('createAnnotationGroup', () => {
     it('creates annotation group along with annotations', async () => {
       const annotations = [
@@ -509,7 +558,7 @@ describe('Event Annotation Service', () => {
       await eventAnnotationService.createAnnotationGroup({
         title: 'newGroupTitle',
         description: 'my description',
-        tags: ['my', 'many', 'tags'],
+        tags: ['tag1', 'tag2', 'tag3'],
         indexPatternId: 'ipid',
         ignoreGlobalFilters: false,
         annotations,
@@ -519,7 +568,6 @@ describe('Event Annotation Service', () => {
         {
           title: 'newGroupTitle',
           description: 'my description',
-          tags: ['my', 'many', 'tags'],
           ignoreGlobalFilters: false,
           dataViewSpec: null,
           annotations,
@@ -531,6 +579,21 @@ describe('Event Annotation Service', () => {
               name: 'event-annotation-group_dataView-ref-ipid',
               type: 'index-pattern',
             },
+            {
+              id: 'tag1',
+              name: 'tag1',
+              type: 'tag',
+            },
+            {
+              id: 'tag2',
+              name: 'tag2',
+              type: 'tag',
+            },
+            {
+              id: 'tag3',
+              name: 'tag3',
+              type: 'tag',
+            },
           ],
         }
       );
@@ -555,11 +618,10 @@ describe('Event Annotation Service', () => {
         {
           title: 'newTitle',
           description: '',
-          tags: [],
           annotations: [],
           dataViewSpec: null,
           ignoreGlobalFilters: false,
-        },
+        } as EventAnnotationGroupAttributes,
         {
           references: [
             {
@@ -572,72 +634,4 @@ describe('Event Annotation Service', () => {
       );
     });
   });
-  // describe.skip('updateAnnotations', () => {
-  //   const upsert = [
-  //     {
-  //       id: 'annotation2',
-  //       label: 'Query based event',
-  //       icon: 'triangle',
-  //       color: 'red',
-  //       type: 'query',
-  //       timeField: 'timestamp',
-  //       key: {
-  //         type: 'point_in_time',
-  //       },
-  //       lineStyle: 'dashed',
-  //       lineWidth: 3,
-  //       filter: { type: 'kibana_query', query: '', language: 'kuery' },
-  //     },
-  //     {
-  //       id: 'annotation4',
-  //       label: 'Query based event',
-  //       type: 'query',
-  //       timeField: 'timestamp',
-  //       key: {
-  //         type: 'point_in_time',
-  //       },
-  //       filter: { type: 'kibana_query', query: '', language: 'kuery' },
-  //     },
-  //   ] as EventAnnotationConfig[];
-  //   it('updates annotations - deletes annotations', async () => {
-  //     await eventAnnotationService.updateAnnotations('multiAnnotations', {
-  //       delete: ['annotation1', 'annotation2'],
-  //     });
-  //     expect(core.savedObjects.client.bulkDelete).toHaveBeenCalledWith([
-  //       { id: 'annotation1', type: 'event-annotation' },
-  //       { id: 'annotation2', type: 'event-annotation' },
-  //     ]);
-  //   });
-  //   it('updates annotations - inserts new annotations', async () => {
-  //     await eventAnnotationService.updateAnnotations('multiAnnotations', { upsert });
-  //     expect(core.savedObjects.client.bulkCreate).toHaveBeenCalledWith([
-  //       {
-  //         id: 'annotation2',
-  //         type: 'event-annotation',
-  //         attributes: upsert[0],
-  //         overwrite: true,
-  //         references: [
-  //           {
-  //             id: 'multiAnnotations',
-  //             name: 'event-annotation-group-ref-annotation2',
-  //             type: 'event-annotation-group',
-  //           },
-  //         ],
-  //       },
-  //       {
-  //         id: 'annotation4',
-  //         type: 'event-annotation',
-  //         attributes: upsert[1],
-  //         overwrite: true,
-  //         references: [
-  //           {
-  //             id: 'multiAnnotations',
-  //             name: 'event-annotation-group-ref-annotation4',
-  //             type: 'event-annotation-group',
-  //           },
-  //         ],
-  //       },
-  //     ]);
-  //   });
-  // });
 });
diff --git a/src/plugins/event_annotation/public/event_annotation_service/service.tsx b/src/plugins/event_annotation/public/event_annotation_service/service.tsx
index a5ac2e265b0f7..65c2b9146df1c 100644
--- a/src/plugins/event_annotation/public/event_annotation_service/service.tsx
+++ b/src/plugins/event_annotation/public/event_annotation_service/service.tsx
@@ -10,9 +10,18 @@ import React from 'react';
 import { partition } from 'lodash';
 import { queryToAst } from '@kbn/data-plugin/common';
 import { ExpressionAstExpression } from '@kbn/expressions-plugin/common';
-import { CoreStart, SavedObjectReference, SavedObjectsClientContract } from '@kbn/core/public';
+import {
+  CoreStart,
+  SavedObjectReference,
+  SavedObjectsClientContract,
+  SavedObjectsFindOptions,
+  SavedObjectsFindOptionsReference,
+  SimpleSavedObject,
+} from '@kbn/core/public';
 import { SavedObjectsManagementPluginStart } from '@kbn/saved-objects-management-plugin/public';
 import { DataViewPersistableStateService } from '@kbn/data-views-plugin/common';
+import { defaultAnnotationLabel } from '../../common/manual_event_annotation';
+import { EventAnnotationGroupContent } from '../../common/types';
 import {
   EventAnnotationConfig,
   EventAnnotationGroupAttributes,
@@ -23,7 +32,6 @@ import { EventAnnotationServiceType } from './types';
 import {
   defaultAnnotationColor,
   defaultAnnotationRangeColor,
-  defaultAnnotationLabel,
   isRangeAnnotationConfig,
   isQueryAnnotationConfig,
 } from './helpers';
@@ -39,18 +47,9 @@ export function getEventAnnotationService(
 ): EventAnnotationServiceType {
   const client: SavedObjectsClientContract = core.savedObjects.client;
 
-  const loadAnnotationGroup = async (
-    savedObjectId: string
-  ): Promise<EventAnnotationGroupConfig> => {
-    const savedObject = await client.get<EventAnnotationGroupAttributes>(
-      EVENT_ANNOTATION_GROUP_TYPE,
-      savedObjectId
-    );
-
-    if (savedObject.error) {
-      throw savedObject.error;
-    }
-
+  const mapSavedObjectToGroupConfig = (
+    savedObject: SimpleSavedObject<EventAnnotationGroupAttributes>
+  ): EventAnnotationGroupConfig => {
     const adHocDataViewSpec = savedObject.attributes.dataViewSpec
       ? DataViewPersistableStateService.inject(
           savedObject.attributes.dataViewSpec,
@@ -61,7 +60,7 @@ export function getEventAnnotationService(
     return {
       title: savedObject.attributes.title,
       description: savedObject.attributes.description,
-      tags: savedObject.attributes.tags,
+      tags: savedObject.references.filter((ref) => ref.type === 'tag').map(({ id }) => id),
       ignoreGlobalFilters: savedObject.attributes.ignoreGlobalFilters,
       indexPatternId: adHocDataViewSpec
         ? adHocDataViewSpec.id!
@@ -71,6 +70,71 @@ export function getEventAnnotationService(
     };
   };
 
+  const mapSavedObjectToGroupContent = (
+    savedObject: SimpleSavedObject<EventAnnotationGroupAttributes>
+  ): EventAnnotationGroupContent => {
+    const groupConfig = mapSavedObjectToGroupConfig(savedObject);
+
+    return {
+      id: savedObject.id,
+      references: savedObject.references,
+      type: savedObject.type,
+      updatedAt: savedObject.updatedAt ? savedObject.updatedAt : '',
+      attributes: {
+        title: groupConfig.title,
+        description: groupConfig.description,
+        indexPatternId: groupConfig.indexPatternId,
+        dataViewSpec: groupConfig.dataViewSpec,
+      },
+    };
+  };
+
+  const loadAnnotationGroup = async (
+    savedObjectId: string
+  ): Promise<EventAnnotationGroupConfig> => {
+    const savedObject = await client.get<EventAnnotationGroupAttributes>(
+      EVENT_ANNOTATION_GROUP_TYPE,
+      savedObjectId
+    );
+
+    if (savedObject.error) {
+      throw savedObject.error;
+    }
+
+    return mapSavedObjectToGroupConfig(savedObject);
+  };
+
+  const findAnnotationGroupContent = async (
+    searchTerm: string,
+    pageSize: number,
+    references?: SavedObjectsFindOptionsReference[],
+    referencesToExclude?: SavedObjectsFindOptionsReference[]
+  ): Promise<{ total: number; hits: EventAnnotationGroupContent[] }> => {
+    const searchOptions: SavedObjectsFindOptions = {
+      type: [EVENT_ANNOTATION_GROUP_TYPE],
+      searchFields: ['title^3', 'description'],
+      search: searchTerm ? `${searchTerm}*` : undefined,
+      perPage: pageSize,
+      page: 1,
+      defaultSearchOperator: 'AND' as const,
+      hasReference: references,
+      hasNoReference: referencesToExclude,
+    };
+
+    const { total, savedObjects } = await client.find<EventAnnotationGroupAttributes>(
+      searchOptions
+    );
+
+    return {
+      total,
+      hits: savedObjects.map(mapSavedObjectToGroupContent),
+    };
+  };
+
+  const deleteAnnotationGroups = async (ids: string[]): Promise<void> => {
+    await client.bulkDelete([...ids.map((id) => ({ type: EVENT_ANNOTATION_GROUP_TYPE, id }))]);
+  };
+
   const extractDataViewInformation = (group: EventAnnotationGroupConfig) => {
     let { dataViewSpec = null } = group;
 
@@ -99,20 +163,35 @@ export function getEventAnnotationService(
     return { references, dataViewSpec };
   };
 
-  const createAnnotationGroup = async (
+  const getAnnotationGroupAttributesAndReferences = (
     group: EventAnnotationGroupConfig
-  ): Promise<{ id: string }> => {
+  ): { attributes: EventAnnotationGroupAttributes; references: SavedObjectReference[] } => {
     const { references, dataViewSpec } = extractDataViewInformation(group);
     const { title, description, tags, ignoreGlobalFilters, annotations } = group;
 
+    references.push(
+      ...tags.map((tag) => ({
+        id: tag,
+        name: tag,
+        type: 'tag',
+      }))
+    );
+
+    return {
+      attributes: { title, description, ignoreGlobalFilters, annotations, dataViewSpec },
+      references,
+    };
+  };
+
+  const createAnnotationGroup = async (
+    group: EventAnnotationGroupConfig
+  ): Promise<{ id: string }> => {
+    const { attributes, references } = getAnnotationGroupAttributesAndReferences(group);
+
     const groupSavedObjectId = (
-      await client.create(
-        EVENT_ANNOTATION_GROUP_TYPE,
-        { title, description, tags, ignoreGlobalFilters, annotations, dataViewSpec },
-        {
-          references,
-        }
-      )
+      await client.create(EVENT_ANNOTATION_GROUP_TYPE, attributes, {
+        references,
+      })
     ).id;
 
     return { id: groupSavedObjectId };
@@ -122,17 +201,11 @@ export function getEventAnnotationService(
     group: EventAnnotationGroupConfig,
     annotationGroupId: string
   ): Promise<void> => {
-    const { references, dataViewSpec } = extractDataViewInformation(group);
-    const { title, description, tags, ignoreGlobalFilters, annotations } = group;
+    const { attributes, references } = getAnnotationGroupAttributesAndReferences(group);
 
-    await client.update(
-      EVENT_ANNOTATION_GROUP_TYPE,
-      annotationGroupId,
-      { title, description, tags, ignoreGlobalFilters, annotations, dataViewSpec },
-      {
-        references,
-      }
-    );
+    await client.update(EVENT_ANNOTATION_GROUP_TYPE, annotationGroupId, attributes, {
+      references,
+    });
   };
 
   const checkHasAnnotationGroups = async (): Promise<boolean> => {
@@ -148,6 +221,8 @@ export function getEventAnnotationService(
     loadAnnotationGroup,
     updateAnnotationGroup,
     createAnnotationGroup,
+    deleteAnnotationGroups,
+    findAnnotationGroupContent,
     renderEventAnnotationGroupSavedObjectFinder: (props) => {
       return (
         <EventAnnotationGroupSavedObjectFinder
diff --git a/src/plugins/event_annotation/public/event_annotation_service/types.ts b/src/plugins/event_annotation/public/event_annotation_service/types.ts
index 603cc20c34bc8..8742ea86afff8 100644
--- a/src/plugins/event_annotation/public/event_annotation_service/types.ts
+++ b/src/plugins/event_annotation/public/event_annotation_service/types.ts
@@ -7,11 +7,20 @@
  */
 
 import { ExpressionAstExpression } from '@kbn/expressions-plugin/common/ast';
+import { SavedObjectsFindOptionsReference } from '@kbn/core-saved-objects-api-browser';
 import type { SavedObjectCommon } from '@kbn/saved-objects-finder-plugin/common';
+import { EventAnnotationGroupContent } from '../../common/types';
 import { EventAnnotationConfig, EventAnnotationGroupConfig } from '../../common';
 
 export interface EventAnnotationServiceType {
   loadAnnotationGroup: (savedObjectId: string) => Promise<EventAnnotationGroupConfig>;
+  findAnnotationGroupContent: (
+    searchTerm: string,
+    pageSize: number,
+    references?: SavedObjectsFindOptionsReference[],
+    referencesToExclude?: SavedObjectsFindOptionsReference[]
+  ) => Promise<{ total: number; hits: EventAnnotationGroupContent[] }>;
+  deleteAnnotationGroups: (ids: string[]) => Promise<void>;
   createAnnotationGroup: (group: EventAnnotationGroupConfig) => Promise<{ id: string }>;
   updateAnnotationGroup: (
     group: EventAnnotationGroupConfig,
diff --git a/src/plugins/event_annotation/public/get_table_list.tsx b/src/plugins/event_annotation/public/get_table_list.tsx
new file mode 100644
index 0000000000000..5226d31894d15
--- /dev/null
+++ b/src/plugins/event_annotation/public/get_table_list.tsx
@@ -0,0 +1,61 @@
+/*
+ * 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 React, { FC } from 'react';
+import { toMountPoint } from '@kbn/kibana-react-plugin/public';
+import { FormattedRelative } from '@kbn/i18n-react';
+import { TableListViewKibanaProvider } from '@kbn/content-management-table-list-view-table';
+import { type TableListTabParentProps } from '@kbn/content-management-tabbed-table-list-view';
+import type { CoreStart } from '@kbn/core-lifecycle-browser';
+import type { SavedObjectsTaggingApi } from '@kbn/saved-objects-tagging-oss-plugin/public';
+import type { DataView, DataViewSpec } from '@kbn/data-views-plugin/common';
+import type { QueryInputServices } from '@kbn/visualization-ui-components/public';
+import { RootDragDropProvider } from '@kbn/dom-drag-drop';
+import type { EventAnnotationServiceType } from './event_annotation_service/types';
+import { EventAnnotationGroupTableList } from './components/table_list';
+
+export interface EventAnnotationListingPageServices {
+  core: CoreStart;
+  savedObjectsTagging: SavedObjectsTaggingApi;
+  eventAnnotationService: EventAnnotationServiceType;
+  PresentationUtilContextProvider: FC;
+  dataViews: DataView[];
+  createDataView: (spec: DataViewSpec) => Promise<DataView>;
+  queryInputServices: QueryInputServices;
+}
+
+export const getTableList = (
+  parentProps: TableListTabParentProps,
+  services: EventAnnotationListingPageServices
+) => {
+  return (
+    <RootDragDropProvider>
+      <TableListViewKibanaProvider
+        {...{
+          core: services.core,
+          toMountPoint,
+          savedObjectsTagging: services.savedObjectsTagging,
+          FormattedRelative,
+        }}
+      >
+        <EventAnnotationGroupTableList
+          toasts={services.core.notifications.toasts}
+          savedObjectsTagging={services.savedObjectsTagging}
+          uiSettings={services.core.uiSettings}
+          eventAnnotationService={services.eventAnnotationService}
+          visualizeCapabilities={services.core.application.capabilities.visualize}
+          parentProps={parentProps}
+          dataViews={services.dataViews}
+          createDataView={services.createDataView}
+          queryInputServices={services.queryInputServices}
+          navigateToLens={() => services.core.application.navigateToApp('lens')}
+        />
+      </TableListViewKibanaProvider>
+    </RootDragDropProvider>
+  );
+};
diff --git a/src/plugins/event_annotation/public/index.ts b/src/plugins/event_annotation/public/index.ts
index 58f6e2c7c9f22..1b96e39546bcb 100644
--- a/src/plugins/event_annotation/public/index.ts
+++ b/src/plugins/event_annotation/public/index.ts
@@ -21,3 +21,8 @@ export {
   isManualPointAnnotationConfig,
   isQueryAnnotationConfig,
 } from './event_annotation_service/helpers';
+export {
+  AnnotationEditorControls,
+  annotationsIconSet,
+} from './components/annotation_editor_controls';
+export { getAnnotationAccessor } from './components/get_annotation_accessor';
diff --git a/src/plugins/event_annotation/public/plugin.ts b/src/plugins/event_annotation/public/plugin.ts
index 4d390f308a474..576f8a3b2a8f0 100644
--- a/src/plugins/event_annotation/public/plugin.ts
+++ b/src/plugins/event_annotation/public/plugin.ts
@@ -6,10 +6,17 @@
  * Side Public License, v 1.
  */
 
-import { Plugin, CoreSetup, CoreStart } from '@kbn/core/public';
-import { ExpressionsSetup } from '@kbn/expressions-plugin/public';
-import { SavedObjectsManagementPluginStart } from '@kbn/saved-objects-management-plugin/public';
-import { DataPublicPluginStart } from '@kbn/data-plugin/public';
+import type { Plugin, CoreSetup, CoreStart } from '@kbn/core/public';
+import type { PresentationUtilPluginStart } from '@kbn/presentation-util-plugin/public';
+import type { SavedObjectTaggingPluginStart } from '@kbn/saved-objects-tagging-plugin/public';
+import type { ExpressionsSetup } from '@kbn/expressions-plugin/public';
+import { Storage } from '@kbn/kibana-utils-plugin/public';
+import type { SavedObjectsManagementPluginStart } from '@kbn/saved-objects-management-plugin/public';
+import type { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public/types';
+import type { DataPublicPluginStart } from '@kbn/data-plugin/public';
+import type { VisualizationsSetup } from '@kbn/visualizations-plugin/public';
+import type { UnifiedSearchPublicPluginStart } from '@kbn/unified-search-plugin/public';
+import { i18n } from '@kbn/i18n';
 import { EventAnnotationService } from './event_annotation_service';
 import {
   manualPointEventAnnotation,
@@ -18,14 +25,21 @@ import {
   eventAnnotationGroup,
 } from '../common';
 import { getFetchEventAnnotations } from './fetch_event_annotations';
+import type { EventAnnotationListingPageServices } from './get_table_list';
+import { ANNOTATIONS_LISTING_VIEW_ID } from '../common/constants';
 
 export interface EventAnnotationStartDependencies {
   savedObjectsManagement: SavedObjectsManagementPluginStart;
   data: DataPublicPluginStart;
+  savedObjectsTagging: SavedObjectTaggingPluginStart;
+  presentationUtil: PresentationUtilPluginStart;
+  dataViews: DataViewsPublicPluginStart;
+  unifiedSearch: UnifiedSearchPublicPluginStart;
 }
 
 interface SetupDependencies {
   expressions: ExpressionsSetup;
+  visualizations: VisualizationsSetup;
 }
 
 /** @public */
@@ -47,6 +61,46 @@ export class EventAnnotationPlugin
     dependencies.expressions.registerFunction(
       getFetchEventAnnotations({ getStartServices: core.getStartServices })
     );
+
+    dependencies.visualizations.listingViewRegistry.add({
+      title: i18n.translate('eventAnnotation.listingViewTitle', {
+        defaultMessage: 'Annotation groups',
+      }),
+      id: ANNOTATIONS_LISTING_VIEW_ID,
+      getTableList: async (props) => {
+        const [coreStart, pluginsStart] = await core.getStartServices();
+
+        const eventAnnotationService = await new EventAnnotationService(
+          coreStart,
+          pluginsStart.savedObjectsManagement
+        ).getService();
+
+        const ids = await pluginsStart.dataViews.getIds();
+        const dataViews = await Promise.all(ids.map((id) => pluginsStart.dataViews.get(id)));
+
+        const services: EventAnnotationListingPageServices = {
+          core: coreStart,
+          savedObjectsTagging: pluginsStart.savedObjectsTagging,
+          eventAnnotationService,
+          PresentationUtilContextProvider: pluginsStart.presentationUtil.ContextProvider,
+          dataViews,
+          createDataView: pluginsStart.dataViews.create.bind(pluginsStart.dataViews),
+          queryInputServices: {
+            http: coreStart.http,
+            docLinks: coreStart.docLinks,
+            notifications: coreStart.notifications,
+            uiSettings: coreStart.uiSettings,
+            dataViews: pluginsStart.dataViews,
+            unifiedSearch: pluginsStart.unifiedSearch,
+            data: pluginsStart.data,
+            storage: new Storage(localStorage),
+          },
+        };
+
+        const { getTableList } = await import('./get_table_list');
+        return getTableList(props, services);
+      },
+    });
   }
 
   public start(
diff --git a/src/plugins/event_annotation/server/plugin.ts b/src/plugins/event_annotation/server/plugin.ts
index 0ae55744016e6..d5e2fee433230 100644
--- a/src/plugins/event_annotation/server/plugin.ts
+++ b/src/plugins/event_annotation/server/plugin.ts
@@ -16,7 +16,6 @@ import {
   queryPointEventAnnotation,
 } from '../common';
 import { setupSavedObjects } from './saved_objects';
-// import { getFetchEventAnnotations } from './fetch_event_annotations';
 
 interface SetupDependencies {
   expressions: ExpressionsServerSetup;
diff --git a/src/plugins/event_annotation/server/saved_objects.ts b/src/plugins/event_annotation/server/saved_objects.ts
index 768def6b27f79..ef357aae0c546 100644
--- a/src/plugins/event_annotation/server/saved_objects.ts
+++ b/src/plugins/event_annotation/server/saved_objects.ts
@@ -14,7 +14,8 @@ import {
 } from '@kbn/core/server';
 
 import { DataViewPersistableStateService } from '@kbn/data-views-plugin/common';
-import { EVENT_ANNOTATION_GROUP_TYPE } from '../common/constants';
+import { VISUALIZE_APP_NAME } from '@kbn/visualizations-plugin/common/constants';
+import { ANNOTATIONS_LISTING_VIEW_ID, EVENT_ANNOTATION_GROUP_TYPE } from '../common/constants';
 import { EventAnnotationGroupAttributes } from '../common/types';
 
 export function setupSavedObjects(coreSetup: CoreSetup) {
@@ -28,6 +29,11 @@ export function setupSavedObjects(coreSetup: CoreSetup) {
       defaultSearchField: 'title',
       importableAndExportable: true,
       getTitle: (obj: { attributes: EventAnnotationGroupAttributes }) => obj.attributes.title,
+      getInAppUrl: (obj: { id: string }) => ({
+        // TODO link to specific object
+        path: `/app/${VISUALIZE_APP_NAME}#/${ANNOTATIONS_LISTING_VIEW_ID}`,
+        uiCapabilitiesPath: 'visualize.show',
+      }),
     },
     migrations: () => {
       const dataViewMigrations = DataViewPersistableStateService.getAllMigrations();
diff --git a/src/plugins/event_annotation/tsconfig.json b/src/plugins/event_annotation/tsconfig.json
index 562bf05259c44..d8d9d61af2ac3 100644
--- a/src/plugins/event_annotation/tsconfig.json
+++ b/src/plugins/event_annotation/tsconfig.json
@@ -21,8 +21,29 @@
     "@kbn/ui-theme",
     "@kbn/saved-objects-finder-plugin",
     "@kbn/saved-objects-management-plugin",
+    "@kbn/saved-objects-tagging-plugin",
+    "@kbn/presentation-util-plugin",
+    "@kbn/content-management-table-list-view",
+    "@kbn/visualizations-plugin",
+    "@kbn/data-views-plugin",
+    "@kbn/visualization-ui-components",
+    "@kbn/chart-icons",
+    "@kbn/unified-field-list-plugin",
+    "@kbn/dom-drag-drop",
     "@kbn/i18n-react",
-    "@kbn/core-saved-objects-server"
+    "@kbn/core-saved-objects-server",
+    "@kbn/test-jest-helpers",
+    "@kbn/saved-objects-tagging-oss-plugin",
+    "@kbn/core-saved-objects-api-browser",
+    "@kbn/kibana-react-plugin",
+    "@kbn/core-lifecycle-browser",
+    "@kbn/kibana-utils-plugin",
+    "@kbn/unified-search-plugin",
+    "@kbn/content-management-table-list-view",
+    "@kbn/content-management-table-list-view-table",
+    "@kbn/content-management-tabbed-table-list-view",
+    "@kbn/core-notifications-browser",
+    "@kbn/core-notifications-browser-mocks",
   ],
   "exclude": [
     "target/**/*",
diff --git a/src/plugins/files_management/public/app.tsx b/src/plugins/files_management/public/app.tsx
index 3ee4e5f52720c..08f942c644789 100644
--- a/src/plugins/files_management/public/app.tsx
+++ b/src/plugins/files_management/public/app.tsx
@@ -9,7 +9,7 @@
 import type { FunctionComponent } from 'react';
 import React, { useState } from 'react';
 import { EuiButtonEmpty } from '@elastic/eui';
-import { TableListView, UserContentCommonSchema } from '@kbn/content-management-table-list';
+import { TableListView, UserContentCommonSchema } from '@kbn/content-management-table-list-view';
 import numeral from '@elastic/numeral';
 import type { FileJSON } from '@kbn/files-plugin/common';
 
@@ -42,8 +42,8 @@ export const App: FunctionComponent = () => {
   return (
     <div data-test-subj="filesManagementApp">
       <TableListView<FilesUserContentSchema>
-        tableListTitle={i18nTexts.tableListTitle}
-        tableListDescription={i18nTexts.tableListDescription}
+        title={i18nTexts.tableListTitle}
+        description={i18nTexts.tableListDescription}
         titleColumnName={i18nTexts.titleColumnName}
         emptyPrompt={<EmptyPrompt />}
         entityName={i18nTexts.entityName}
diff --git a/src/plugins/files_management/public/mount_management_section.tsx b/src/plugins/files_management/public/mount_management_section.tsx
index 9c7091516d46e..229e1d2b306f6 100755
--- a/src/plugins/files_management/public/mount_management_section.tsx
+++ b/src/plugins/files_management/public/mount_management_section.tsx
@@ -17,7 +17,7 @@ import type { ManagementAppMountParams } from '@kbn/management-plugin/public';
 import {
   TableListViewKibanaProvider,
   TableListViewKibanaDependencies,
-} from '@kbn/content-management-table-list';
+} from '@kbn/content-management-table-list-view-table';
 import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
 import type { StartDependencies } from './types';
 import { App } from './app';
diff --git a/src/plugins/files_management/tsconfig.json b/src/plugins/files_management/tsconfig.json
index d15175fae0470..28986030e75f8 100644
--- a/src/plugins/files_management/tsconfig.json
+++ b/src/plugins/files_management/tsconfig.json
@@ -9,7 +9,8 @@
     "@kbn/files-plugin",
     "@kbn/management-plugin",
     "@kbn/i18n",
-    "@kbn/content-management-table-list",
+    "@kbn/content-management-table-list-view-table",
+    "@kbn/content-management-table-list-view",
     "@kbn/kibana-react-plugin",
     "@kbn/i18n-react",
     "@kbn/shared-ux-file-image",
diff --git a/src/plugins/visualization_ui_components/common/index.ts b/src/plugins/visualization_ui_components/common/index.ts
new file mode 100644
index 0000000000000..3f66f3b659d8a
--- /dev/null
+++ b/src/plugins/visualization_ui_components/common/index.ts
@@ -0,0 +1,9 @@
+/*
+ * 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.
+ */
+
+export {};
diff --git a/src/plugins/visualization_ui_components/common/types.ts b/src/plugins/visualization_ui_components/common/types.ts
new file mode 100644
index 0000000000000..7da9f13aced8d
--- /dev/null
+++ b/src/plugins/visualization_ui_components/common/types.ts
@@ -0,0 +1,9 @@
+/*
+ * 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.
+ */
+
+export type LineStyle = 'solid' | 'dashed' | 'dotted';
diff --git a/src/plugins/visualization_ui_components/kibana.jsonc b/src/plugins/visualization_ui_components/kibana.jsonc
index f7d42af513338..6a2ed84a93149 100644
--- a/src/plugins/visualization_ui_components/kibana.jsonc
+++ b/src/plugins/visualization_ui_components/kibana.jsonc
@@ -8,7 +8,11 @@
     "browser": true,
     "requiredBundles": [
       "unifiedSearch",
-      "unifiedFieldList"
+      "unifiedFieldList",
+      "dataViews"
+    ],
+    "extraPublicDirs": [
+      "common"
     ]
   }
 }
diff --git a/src/plugins/visualization_ui_components/public/components/dimension_buttons/dimension_button.tsx b/src/plugins/visualization_ui_components/public/components/dimension_buttons/dimension_button.tsx
index 4a91d74ebe5f6..7c9cbb881c2f3 100644
--- a/src/plugins/visualization_ui_components/public/components/dimension_buttons/dimension_button.tsx
+++ b/src/plugins/visualization_ui_components/public/components/dimension_buttons/dimension_button.tsx
@@ -7,7 +7,14 @@
  */
 
 import React from 'react';
-import { EuiButtonIcon, EuiLink, EuiToolTip, EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
+import {
+  EuiButtonIcon,
+  EuiLink,
+  EuiToolTip,
+  EuiFlexGroup,
+  EuiFlexItem,
+  useEuiFontSize,
+} from '@elastic/eui';
 import { i18n } from '@kbn/i18n';
 import { css } from '@emotion/react';
 import { euiThemeVars } from '@kbn/ui-theme';
@@ -41,12 +48,38 @@ export function DimensionButton({
   message?: Message;
 }) {
   return (
-    <div {...otherProps}>
+    <div
+      {...otherProps}
+      css={css`
+        ${useEuiFontSize('s')}
+        border-radius: ${euiThemeVars.euiBorderRadius};
+        display: flex;
+        align-items: center;
+        overflow: hidden;
+        min-height: ${euiThemeVars.euiSizeXL};
+        position: relative;
+
+        &:hover,
+        &:focus {
+          .lnsLayerPanel__dimensionRemove {
+            visibility: visible;
+            opacity: 1;
+            transition: opacity ${euiThemeVars.euiAnimSpeedFast} ease-in-out;
+          }
+        }
+      `}
+    >
       <EuiFlexGroup direction="row" alignItems="center" gutterSize="none" responsive={false}>
         <EuiFlexItem>
           <EuiToolTip content={message?.content} position="left">
             <EuiLink
               className="lnsLayerPanel__dimensionLink"
+              css={css`
+                width: 100%;
+                &:hover {
+                  text-decoration: none;
+                }
+              `}
               data-test-subj="lnsLayerPanel-dimensionLink"
               onClick={() => onClick(accessorConfig.columnId)}
               aria-label={triggerLinkA11yText(label)}
@@ -82,7 +115,11 @@ export function DimensionButton({
         })}
         onClick={() => onRemoveClick(accessorConfig.columnId)}
         css={css`
+          margin-right: ${euiThemeVars.euiSizeS};
+          visibility: hidden;
+          opacity: 0;
           color: ${euiThemeVars.euiTextSubduedColor};
+
           &:hover {
             color: ${euiThemeVars.euiColorDangerText};
           }
diff --git a/src/plugins/visualization_ui_components/public/components/dimension_buttons/dimension_button_icon.tsx b/src/plugins/visualization_ui_components/public/components/dimension_buttons/dimension_button_icon.tsx
index bcb3ddb1e44bb..6294c27254a56 100644
--- a/src/plugins/visualization_ui_components/public/components/dimension_buttons/dimension_button_icon.tsx
+++ b/src/plugins/visualization_ui_components/public/components/dimension_buttons/dimension_button_icon.tsx
@@ -9,10 +9,14 @@
 import React from 'react';
 import { EuiFlexGroup, EuiFlexItem, EuiIcon } from '@elastic/eui';
 import { i18n } from '@kbn/i18n';
+import { css } from '@emotion/react';
+import { euiThemeVars } from '@kbn/ui-theme';
 import type { AccessorConfig, Message } from './types';
 
 const baseIconProps = {
-  className: 'lnsLayerPanel__colorIndicator',
+  css: css`
+    margin-left: ${euiThemeVars.euiSizeS};
+  `,
 } as const;
 
 const getIconFromAccessorConfig = (accessorConfig: AccessorConfig) => (
diff --git a/src/plugins/visualization_ui_components/public/components/dimension_buttons/empty_button.tsx b/src/plugins/visualization_ui_components/public/components/dimension_buttons/empty_button.tsx
new file mode 100644
index 0000000000000..e66d512e80f73
--- /dev/null
+++ b/src/plugins/visualization_ui_components/public/components/dimension_buttons/empty_button.tsx
@@ -0,0 +1,64 @@
+/*
+ * 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 React from 'react';
+import { css } from '@emotion/react';
+import { euiThemeVars } from '@kbn/ui-theme';
+import { EuiButtonEmpty } from '@elastic/eui';
+import { DimensionTrigger } from './trigger';
+
+export const EmptyDimensionButton = ({
+  label,
+  ariaLabel,
+  onClick,
+  dataTestSubj,
+  iconType,
+  ...otherProps // from Drag&Drop integration
+}: {
+  label: React.ReactNode;
+  ariaLabel: string;
+  onClick: () => void;
+  dataTestSubj?: string;
+  iconType?: string;
+}) => {
+  return (
+    <EuiButtonEmpty
+      {...otherProps}
+      css={css`
+        width: 100%;
+        border-radius: ${euiThemeVars.euiBorderRadius} !important;
+        border: ${euiThemeVars.euiBorderWidthThin} dashed ${euiThemeVars.euiBorderColor} !important;
+      `}
+      color="text" // as far as I can tell all this currently adds is the correct active background color
+      size="s"
+      iconType={iconType ?? 'plus'}
+      contentProps={{
+        css: css`
+          justify-content: flex-start;
+          padding: 0 !important;
+          color: ${euiThemeVars.euiTextSubduedColor};
+
+          .euiButtonEmpty__text {
+            margin-left: 0;
+          }
+
+          .euiIcon {
+            margin-left: ${euiThemeVars.euiSizeS};
+          }
+        `,
+      }}
+      aria-label={ariaLabel}
+      data-test-subj={dataTestSubj}
+      onClick={() => {
+        onClick();
+      }}
+    >
+      <DimensionTrigger label={label} dataTestSubj="emptyDimensionTrigger" />
+    </EuiButtonEmpty>
+  );
+};
diff --git a/src/plugins/visualization_ui_components/public/components/dimension_buttons/index.ts b/src/plugins/visualization_ui_components/public/components/dimension_buttons/index.ts
index 54df2c7911488..b3037035fb57b 100644
--- a/src/plugins/visualization_ui_components/public/components/dimension_buttons/index.ts
+++ b/src/plugins/visualization_ui_components/public/components/dimension_buttons/index.ts
@@ -8,4 +8,8 @@
 
 export * from './dimension_button';
 
+export * from './empty_button';
+
+export * from './trigger';
+
 export type { AccessorConfig } from './types';
diff --git a/src/plugins/visualization_ui_components/public/components/dimension_buttons/palette_indicator.tsx b/src/plugins/visualization_ui_components/public/components/dimension_buttons/palette_indicator.tsx
index 5838a7e5c5236..ae627c4a11b15 100644
--- a/src/plugins/visualization_ui_components/public/components/dimension_buttons/palette_indicator.tsx
+++ b/src/plugins/visualization_ui_components/public/components/dimension_buttons/palette_indicator.tsx
@@ -8,14 +8,32 @@
 
 import React from 'react';
 import { EuiColorPaletteDisplay } from '@elastic/eui';
+import { css } from '@emotion/react';
+import { euiThemeVars } from '@kbn/ui-theme';
 import type { AccessorConfig } from './types';
 
 export function PaletteIndicator({ accessorConfig }: { accessorConfig: AccessorConfig }) {
   if (accessorConfig.triggerIconType !== 'colorBy' || !accessorConfig.palette) return null;
   return (
-    <div className="lnsLayerPanel__paletteContainer">
+    <div
+      css={css`
+        position: absolute;
+        bottom: 0;
+        left: 0;
+        right: 0;
+      `}
+    >
       <EuiColorPaletteDisplay
         className="lnsLayerPanel__palette"
+        css={css`
+          height: ${euiThemeVars.euiSizeXS} / 2;
+          border-radius: 0 0 (${euiThemeVars.euiBorderRadius} - 1px)
+            (${euiThemeVars.euiBorderRadius} - 1px);
+
+          &::after {
+            border: none;
+          }
+        `}
         size="xs"
         palette={accessorConfig.palette}
       />
diff --git a/src/plugins/visualization_ui_components/public/components/dimension_buttons/trigger.tsx b/src/plugins/visualization_ui_components/public/components/dimension_buttons/trigger.tsx
new file mode 100644
index 0000000000000..c050582332b05
--- /dev/null
+++ b/src/plugins/visualization_ui_components/public/components/dimension_buttons/trigger.tsx
@@ -0,0 +1,70 @@
+/*
+ * 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 { EuiText, EuiFlexItem } from '@elastic/eui';
+import { i18n } from '@kbn/i18n';
+import React from 'react';
+import { EuiTextProps } from '@elastic/eui/src/components/text/text';
+import { css } from '@emotion/react';
+import { euiThemeVars } from '@kbn/ui-theme';
+
+export const defaultDimensionTriggerTooltip = (
+  <p>
+    {i18n.translate('visualizationUiComponents.configure.invalidConfigTooltip', {
+      defaultMessage: 'Invalid configuration.',
+    })}
+    <br />
+    {i18n.translate('visualizationUiComponents.configure.invalidConfigTooltipClick', {
+      defaultMessage: 'Click for more details.',
+    })}
+  </p>
+);
+
+export const DimensionTrigger = ({
+  id,
+  label,
+  color,
+  dataTestSubj,
+}: {
+  label: React.ReactNode;
+  id?: string;
+  color?: EuiTextProps['color'];
+  dataTestSubj?: string;
+}) => {
+  return (
+    <EuiText
+      size="s"
+      id={id}
+      color={color}
+      css={css`
+        width: 100%;
+        padding: ${euiThemeVars.euiSizeXS} ${euiThemeVars.euiSizeS};
+        word-break: break-word;
+        font-weight: ${euiThemeVars.euiFontWeightRegular};
+      `}
+      data-test-subj={dataTestSubj || 'lns-dimensionTrigger'}
+    >
+      <EuiFlexItem grow={true}>
+        <span>
+          <span
+            className="dimensionTrigger__textLabel"
+            css={css`
+              transition: background-color ${euiThemeVars.euiAnimSpeedFast} ease-in-out;
+
+              &:hover {
+                text-decoration: underline;
+              }
+            `}
+          >
+            {label}
+          </span>
+        </span>
+      </EuiFlexItem>
+    </EuiText>
+  );
+};
diff --git a/src/plugins/visualization_ui_components/public/components/index.ts b/src/plugins/visualization_ui_components/public/components/index.ts
index 4de1805bc472a..e20879f9e9990 100644
--- a/src/plugins/visualization_ui_components/public/components/index.ts
+++ b/src/plugins/visualization_ui_components/public/components/index.ts
@@ -28,8 +28,14 @@ export * from './dimension_editor_section';
 
 export * from './dimension_buttons';
 
+export * from './line_style_settings';
+
+export * from './text_decoration_setting';
+
 export type { AccessorConfig } from './dimension_buttons';
 
 export type { FieldOptionValue, FieldOption, DataType } from './field_picker';
 
 export type { IconSet } from './icon_select';
+
+export type { QueryInputServices } from './query_input';
diff --git a/x-pack/plugins/lens/public/visualizations/xy/xy_config_panel/shared/line_style_settings.tsx b/src/plugins/visualization_ui_components/public/components/line_style_settings.tsx
similarity index 83%
rename from x-pack/plugins/lens/public/visualizations/xy/xy_config_panel/shared/line_style_settings.tsx
rename to src/plugins/visualization_ui_components/public/components/line_style_settings.tsx
index a479daeb75919..0b7f09b6a7444 100644
--- a/x-pack/plugins/lens/public/visualizations/xy/xy_config_panel/shared/line_style_settings.tsx
+++ b/src/plugins/visualization_ui_components/public/components/line_style_settings.tsx
@@ -1,8 +1,9 @@
 /*
  * 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; you may not use this file except in compliance with the Elastic License
- * 2.0.
+ * 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 React, { useState } from 'react';
@@ -14,30 +15,28 @@ import {
   EuiFlexItem,
   EuiFormRow,
 } from '@elastic/eui';
-import { LineStyle } from '@kbn/expression-xy-plugin/common';
-
-import { idPrefix } from '../dimension_editor';
+import { LineStyle } from '../../common/types';
 
 interface LineStyleConfig {
-  lineStyle?: Exclude<LineStyle, 'dot-dashed'>;
+  lineStyle?: LineStyle;
   lineWidth?: number;
 }
 
 export const LineStyleSettings = ({
   currentConfig,
   setConfig,
-  isHorizontal,
+  idPrefix,
 }: {
   currentConfig?: LineStyleConfig;
   setConfig: (config: LineStyleConfig) => void;
-  isHorizontal: boolean;
+  idPrefix: string;
 }) => {
   return (
     <>
       <EuiFormRow
         display="columnCompressed"
         fullWidth
-        label={i18n.translate('xpack.lens.xyChart.lineStyle.label', {
+        label={i18n.translate('visualizationUiComponents.xyChart.lineStyle.label', {
           defaultMessage: 'Line',
         })}
       >
@@ -52,7 +51,7 @@ export const LineStyleSettings = ({
           </EuiFlexItem>
           <EuiFlexItem grow={false}>
             <EuiButtonGroup
-              legend={i18n.translate('xpack.lens.xyChart.lineStyle.label', {
+              legend={i18n.translate('visualizationUiComponents.xyChart.lineStyle.label', {
                 defaultMessage: 'Line',
               })}
               data-test-subj="lnsXY_line_style"
@@ -61,7 +60,7 @@ export const LineStyleSettings = ({
               options={[
                 {
                   id: `${idPrefix}solid`,
-                  label: i18n.translate('xpack.lens.xyChart.lineStyle.solid', {
+                  label: i18n.translate('visualizationUiComponents.xyChart.lineStyle.solid', {
                     defaultMessage: 'Solid',
                   }),
                   'data-test-subj': 'lnsXY_line_style_solid',
@@ -69,7 +68,7 @@ export const LineStyleSettings = ({
                 },
                 {
                   id: `${idPrefix}dashed`,
-                  label: i18n.translate('xpack.lens.xyChart.lineStyle.dashed', {
+                  label: i18n.translate('visualizationUiComponents.xyChart.lineStyle.dashed', {
                     defaultMessage: 'Dashed',
                   }),
                   'data-test-subj': 'lnsXY_line_style_dashed',
@@ -77,7 +76,7 @@ export const LineStyleSettings = ({
                 },
                 {
                   id: `${idPrefix}dotted`,
-                  label: i18n.translate('xpack.lens.xyChart.lineStyle.dotted', {
+                  label: i18n.translate('visualizationUiComponents.xyChart.lineStyle.dotted', {
                     defaultMessage: 'Dotted',
                   }),
                   'data-test-subj': 'lnsXY_line_style_dotted',
diff --git a/src/plugins/visualization_ui_components/public/components/name_input.tsx b/src/plugins/visualization_ui_components/public/components/name_input.tsx
index 1a3f3e836e98d..89f303e57156e 100644
--- a/src/plugins/visualization_ui_components/public/components/name_input.tsx
+++ b/src/plugins/visualization_ui_components/public/components/name_input.tsx
@@ -32,7 +32,7 @@ export const NameInput = ({
       <DebouncedInput
         fullWidth
         compressed
-        data-test-subj="column-label-edit"
+        data-test-subj="name-input"
         value={value}
         onChange={onChange}
         defaultValue={defaultValue}
diff --git a/src/plugins/visualization_ui_components/public/components/query_input/filter_query_input.scss b/src/plugins/visualization_ui_components/public/components/query_input/filter_query_input.scss
new file mode 100644
index 0000000000000..b971b65e897ce
--- /dev/null
+++ b/src/plugins/visualization_ui_components/public/components/query_input/filter_query_input.scss
@@ -0,0 +1,11 @@
+// TODO - use emotion instead
+.filterQueryInput__popoverButton {
+  @include euiTextBreakWord;
+  @include euiFontSizeS;
+  min-height: $euiSizeXL;
+  width: 100%;
+}
+
+.filterQueryInput__popover {
+  width: $euiSize * 60;
+}
\ No newline at end of file
diff --git a/src/plugins/visualization_ui_components/public/components/query_input/filter_query_input.tsx b/src/plugins/visualization_ui_components/public/components/query_input/filter_query_input.tsx
index 2747f87c90f31..e998e48eefe99 100644
--- a/src/plugins/visualization_ui_components/public/components/query_input/filter_query_input.tsx
+++ b/src/plugins/visualization_ui_components/public/components/query_input/filter_query_input.tsx
@@ -22,6 +22,7 @@ import type { DataViewBase, Query } from '@kbn/es-query';
 import { useDebouncedValue } from '../debounced_value';
 import { QueryInput, validateQuery } from '.';
 import type { QueryInputServices } from '.';
+import './filter_query_input.scss';
 
 const filterByLabel = i18n.translate('visualizationUiComponents.filterQueryInput.label', {
   defaultMessage: 'Filter by',
@@ -101,7 +102,7 @@ export function FilterQueryInput({
             isOpen={filterPopoverOpen}
             closePopover={onClosePopup}
             anchorClassName="eui-fullWidth"
-            panelClassName="lnsIndexPatternDimensionEditor__filtersEditor"
+            panelClassName="filterQueryInput__popover"
             initialFocus={dataTestSubj ? `textarea[data-test-subj='${dataTestSubj}']` : undefined}
             button={
               <EuiPanel paddingSize="none" hasShadow={false} hasBorder>
@@ -109,7 +110,7 @@ export function FilterQueryInput({
                   <EuiFlexItem grow={false}>{/* Empty for spacing */}</EuiFlexItem>
                   <EuiFlexItem grow={true}>
                     <EuiLink
-                      className="lnsFiltersOperation__popoverButton"
+                      className="filterQueryInput__popoverButton"
                       data-test-subj="indexPattern-filters-existingFilterTrigger"
                       onClick={() => {
                         setFilterPopoverOpen(!filterPopoverOpen);
diff --git a/src/plugins/visualization_ui_components/public/components/text_decoration_setting.tsx b/src/plugins/visualization_ui_components/public/components/text_decoration_setting.tsx
new file mode 100644
index 0000000000000..7f52fc1935e01
--- /dev/null
+++ b/src/plugins/visualization_ui_components/public/components/text_decoration_setting.tsx
@@ -0,0 +1,122 @@
+/*
+ * 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 { EuiButtonGroup, EuiFormRow } from '@elastic/eui';
+import { i18n } from '@kbn/i18n';
+import React, { useState } from 'react';
+
+interface TextDecorationConfig {
+  textVisibility?: boolean;
+  textField?: string;
+}
+
+function getSelectedOption(
+  { textField, textVisibility }: TextDecorationConfig = {},
+  isQueryBased?: boolean
+) {
+  if (!textVisibility) {
+    return 'none';
+  }
+  if (isQueryBased && textField) {
+    return 'field';
+  }
+  return 'name';
+}
+
+export function TextDecorationSetting({
+  idPrefix,
+  currentConfig,
+  setConfig,
+  isQueryBased,
+  children,
+}: {
+  idPrefix: string;
+  currentConfig?: TextDecorationConfig;
+  setConfig: (config: TextDecorationConfig) => void;
+  isQueryBased?: boolean;
+  /** A children render function for custom sub fields on textDecoration change */
+  children?: (textDecoration: 'none' | 'name' | 'field') => JSX.Element | null;
+}) {
+  // To model the temporary state for label based on field when user didn't pick up the field yet,
+  // use a local state
+  const [selectedVisibleOption, setVisibleOption] = useState<'none' | 'name' | 'field'>(
+    getSelectedOption(currentConfig, isQueryBased)
+  );
+  const options = [
+    {
+      id: `${idPrefix}none`,
+      label: i18n.translate('visualizationUiComponents.xyChart.lineMarker.textVisibility.none', {
+        defaultMessage: 'None',
+      }),
+      'data-test-subj': 'lnsXY_textVisibility_none',
+    },
+    {
+      id: `${idPrefix}name`,
+      label: i18n.translate('visualizationUiComponents.xyChart.lineMarker.textVisibility.name', {
+        defaultMessage: 'Name',
+      }),
+      'data-test-subj': 'lnsXY_textVisibility_name',
+    },
+  ];
+  if (isQueryBased) {
+    options.push({
+      id: `${idPrefix}field`,
+      label: i18n.translate('visualizationUiComponents.xyChart.lineMarker.textVisibility.field', {
+        defaultMessage: 'Field',
+      }),
+      'data-test-subj': 'lnsXY_textVisibility_field',
+    });
+  }
+
+  return (
+    <EuiFormRow
+      label={i18n.translate('visualizationUiComponents.lineMarker.textVisibility', {
+        defaultMessage: 'Text decoration',
+      })}
+      display="columnCompressed"
+      fullWidth
+    >
+      <div>
+        <EuiButtonGroup
+          legend={i18n.translate('visualizationUiComponents.lineMarker.textVisibility', {
+            defaultMessage: 'Text decoration',
+          })}
+          data-test-subj="lns-lineMarker-text-visibility"
+          name="textVisibilityStyle"
+          buttonSize="compressed"
+          options={options}
+          idSelected={
+            selectedVisibleOption ? `${idPrefix}${selectedVisibleOption}` : `${idPrefix}none`
+          }
+          onChange={(id) => {
+            const chosenOption = id.replace(idPrefix, '') as 'none' | 'name' | 'field';
+            if (chosenOption === 'none') {
+              setConfig({
+                textVisibility: false,
+                textField: undefined,
+              });
+            } else if (chosenOption === 'name') {
+              setConfig({
+                textVisibility: true,
+                textField: undefined,
+              });
+            } else if (chosenOption === 'field') {
+              setConfig({
+                textVisibility: Boolean(currentConfig?.textField),
+              });
+            }
+
+            setVisibleOption(chosenOption);
+          }}
+          isFullWidth
+        />
+        {children?.(selectedVisibleOption)}
+      </div>
+    </EuiFormRow>
+  );
+}
diff --git a/src/plugins/visualization_ui_components/public/index.ts b/src/plugins/visualization_ui_components/public/index.ts
index d0495697dad01..b8be5d3afbd78 100644
--- a/src/plugins/visualization_ui_components/public/index.ts
+++ b/src/plugins/visualization_ui_components/public/index.ts
@@ -28,16 +28,25 @@ export {
   isQueryValid,
   DimensionEditorSection,
   DimensionButton,
+  DimensionTrigger,
+  EmptyDimensionButton,
+  LineStyleSettings,
+  TextDecorationSetting,
 } from './components';
 
+export { isFieldLensCompatible } from './util';
+
 export type {
   DataType,
   FieldOptionValue,
   FieldOption,
   IconSet,
   AccessorConfig,
+  QueryInputServices,
 } from './components';
 
+export type { FormatFactory } from './types';
+
 export function plugin() {
   return new VisualizationUiComponentsPlugin();
 }
diff --git a/src/plugins/visualization_ui_components/public/types.ts b/src/plugins/visualization_ui_components/public/types.ts
new file mode 100644
index 0000000000000..8d259c3b90f90
--- /dev/null
+++ b/src/plugins/visualization_ui_components/public/types.ts
@@ -0,0 +1,11 @@
+/*
+ * 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 type { IFieldFormat, SerializedFieldFormat } from '@kbn/field-formats-plugin/common';
+
+export type FormatFactory = (mapping?: SerializedFieldFormat) => IFieldFormat;
diff --git a/src/plugins/visualization_ui_components/public/util.ts b/src/plugins/visualization_ui_components/public/util.ts
new file mode 100644
index 0000000000000..2eb71ee858759
--- /dev/null
+++ b/src/plugins/visualization_ui_components/public/util.ts
@@ -0,0 +1,12 @@
+/*
+ * 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 { DataViewField, isNestedField } from '@kbn/data-views-plugin/common';
+
+export const isFieldLensCompatible = (field: DataViewField) =>
+  !isNestedField(field) && (!!field.aggregatable || !!field.scripted);
diff --git a/src/plugins/visualization_ui_components/tsconfig.json b/src/plugins/visualization_ui_components/tsconfig.json
index 7175696eef5f5..cbafd595ecc62 100644
--- a/src/plugins/visualization_ui_components/tsconfig.json
+++ b/src/plugins/visualization_ui_components/tsconfig.json
@@ -24,7 +24,8 @@
     "@kbn/core-doc-links-browser",
     "@kbn/core",
     "@kbn/ui-theme",
-    "@kbn/coloring"
+    "@kbn/coloring",
+    "@kbn/field-formats-plugin"
   ],
   "exclude": [
     "target/**/*",
diff --git a/src/plugins/visualizations/common/constants.ts b/src/plugins/visualizations/common/constants.ts
index f3e2fd30c4288..7b6e18708f3f9 100644
--- a/src/plugins/visualizations/common/constants.ts
+++ b/src/plugins/visualizations/common/constants.ts
@@ -20,6 +20,7 @@ export const VISUALIZE_APP_NAME = 'visualize';
 export const VisualizeConstants = {
   VISUALIZE_BASE_PATH: '/app/visualize',
   LANDING_PAGE_PATH: '/',
+  LANDING_PAGE_PATH_WITH_TAB: '/:activeTab',
   WIZARD_STEP_1_PAGE_PATH: '/new',
   WIZARD_STEP_2_PAGE_PATH: '/new/configure',
   CREATE_PATH: '/create',
diff --git a/src/plugins/visualizations/public/mocks.ts b/src/plugins/visualizations/public/mocks.ts
index 4c9ea92d83739..9bc31adccd3ba 100644
--- a/src/plugins/visualizations/public/mocks.ts
+++ b/src/plugins/visualizations/public/mocks.ts
@@ -35,6 +35,7 @@ const createSetupContract = (): VisualizationsSetup => ({
   createBaseVisualization: jest.fn(),
   registerAlias: jest.fn(),
   visEditorsRegistry: { registerDefault: jest.fn(), register: jest.fn(), get: jest.fn() },
+  listingViewRegistry: { add: jest.fn() },
 });
 
 const createStartContract = (): VisualizationsStart => ({
diff --git a/src/plugins/visualizations/public/plugin.ts b/src/plugins/visualizations/public/plugin.ts
index 4e6d3d429eb1d..2b906620e5f8b 100644
--- a/src/plugins/visualizations/public/plugin.ts
+++ b/src/plugins/visualizations/public/plugin.ts
@@ -110,6 +110,7 @@ import {
 } from './services';
 import { VisualizeConstants } from '../common/constants';
 import { EditInLensAction } from './actions/edit_in_lens_action';
+import { ListingViewRegistry } from './types';
 import { LATEST_VERSION, CONTENT_ID } from '../common/content_management';
 
 /**
@@ -118,8 +119,10 @@ import { LATEST_VERSION, CONTENT_ID } from '../common/content_management';
  * @public
  */
 
-export type VisualizationsSetup = TypesSetup & { visEditorsRegistry: VisEditorsRegistry };
-
+export type VisualizationsSetup = TypesSetup & {
+  visEditorsRegistry: VisEditorsRegistry;
+  listingViewRegistry: ListingViewRegistry;
+};
 export interface VisualizationsStart extends TypesStart {
   showNewVisModal: typeof showNewVisModal;
 }
@@ -246,6 +249,7 @@ export class VisualizationsPlugin
     };
 
     const start = createStartServicesGetter(core.getStartServices);
+    const listingViewRegistry: ListingViewRegistry = new Set();
     const visEditorsRegistry = createVisEditorsRegistry();
 
     core.application.register({
@@ -321,6 +325,7 @@ export class VisualizationsPlugin
           getKibanaVersion: () => this.initializerContext.env.packageInfo.version,
           spaces: pluginsStart.spaces,
           visEditorsRegistry,
+          listingViewRegistry,
           unifiedSearch: pluginsStart.unifiedSearch,
         };
 
@@ -388,6 +393,7 @@ export class VisualizationsPlugin
     return {
       ...this.types.setup(),
       visEditorsRegistry,
+      listingViewRegistry,
     };
   }
 
diff --git a/src/plugins/visualizations/public/types.ts b/src/plugins/visualizations/public/types.ts
index c69f7894fa326..c23f40dc85bae 100644
--- a/src/plugins/visualizations/public/types.ts
+++ b/src/plugins/visualizations/public/types.ts
@@ -16,6 +16,7 @@ import {
 import type { ISearchSource } from '@kbn/data-plugin/common';
 import { ExpressionAstExpression } from '@kbn/expressions-plugin/public';
 
+import type { TableListTab } from '@kbn/content-management-tabbed-table-list-view';
 import type { Vis } from './vis';
 import type { PersistedState } from './persisted_state';
 import type { VisParams, SerializedVis } from '../common';
@@ -94,3 +95,5 @@ export interface VisEditorOptionsProps<VisParamType = unknown> {
   setValidity(isValid: boolean): void;
   setTouched(isTouched: boolean): void;
 }
+
+export type ListingViewRegistry = Pick<Set<TableListTab>, 'add'>;
diff --git a/src/plugins/visualizations/public/visualize_app/app.tsx b/src/plugins/visualizations/public/visualize_app/app.tsx
index 0a4e46288616f..625c0846221a5 100644
--- a/src/plugins/visualizations/public/visualize_app/app.tsx
+++ b/src/plugins/visualizations/public/visualize_app/app.tsx
@@ -139,7 +139,11 @@ export const VisualizeApp = ({ onAppLeave }: VisualizeAppProps) => {
       </Route>
       <Route
         exact
-        path={[VisualizeConstants.LANDING_PAGE_PATH, VisualizeConstants.WIZARD_STEP_1_PAGE_PATH]}
+        path={[
+          VisualizeConstants.LANDING_PAGE_PATH,
+          VisualizeConstants.WIZARD_STEP_1_PAGE_PATH,
+          VisualizeConstants.LANDING_PAGE_PATH_WITH_TAB,
+        ]}
       >
         <VisualizeListing />
       </Route>
diff --git a/src/plugins/visualizations/public/visualize_app/components/visualize_listing.tsx b/src/plugins/visualizations/public/visualize_app/components/visualize_listing.tsx
index 8dd9885ef5520..e169a7ebaa034 100644
--- a/src/plugins/visualizations/public/visualize_app/components/visualize_listing.tsx
+++ b/src/plugins/visualizations/public/visualize_app/components/visualize_listing.tsx
@@ -8,20 +8,34 @@
 
 import './visualize_listing.scss';
 
-import React, { useCallback, useRef, useMemo, useEffect, MouseEvent } from 'react';
+import React, {
+  useCallback,
+  useRef,
+  useMemo,
+  useEffect,
+  MouseEvent,
+  MutableRefObject,
+} from 'react';
 import { EuiCallOut, EuiLink, EuiSpacer } from '@elastic/eui';
 import { i18n } from '@kbn/i18n';
 import { FormattedMessage } from '@kbn/i18n-react';
 import useUnmount from 'react-use/lib/useUnmount';
 import useMount from 'react-use/lib/useMount';
 
-import { useLocation } from 'react-router-dom';
+import { useLocation, useParams } from 'react-router-dom';
 
 import type { SavedObjectReference } from '@kbn/core/public';
 import { useKibana, useExecutionContext } from '@kbn/kibana-react-plugin/public';
-import { TableListView } from '@kbn/content-management-table-list';
+import {
+  TabbedTableListView,
+  type TableListTab,
+} from '@kbn/content-management-tabbed-table-list-view';
 import type { OpenContentEditorParams } from '@kbn/content-management-content-editor';
-import type { UserContentCommonSchema } from '@kbn/content-management-table-list';
+import {
+  type UserContentCommonSchema,
+  TableListViewProps,
+} from '@kbn/content-management-table-list-view';
+import { TableListViewTable } from '@kbn/content-management-table-list-view-table';
 import { findListItems } from '../../utils/saved_visualize_utils';
 import { updateBasicSoAttributes } from '../../utils/saved_objects_utils/update_basic_attributes';
 import { checkForDuplicateTitle } from '../../utils/saved_objects_utils/check_for_duplicate_title';
@@ -71,71 +85,38 @@ const toTableListViewSavedObject = (savedObject: Record<string, unknown>): Visua
     },
   };
 };
+type CustomTableViewProps = Pick<
+  TableListViewProps<VisualizeUserContent>,
+  | 'createItem'
+  | 'findItems'
+  | 'deleteItems'
+  | 'editItem'
+  | 'contentEditor'
+  | 'emptyPrompt'
+  | 'showEditActionForItem'
+>;
 
-export const VisualizeListing = () => {
+const useTableListViewProps = (
+  closeNewVisModal: MutableRefObject<() => void>,
+  listingLimit: number
+): CustomTableViewProps => {
   const {
     services: {
-      core,
       application,
-      executionContext,
-      chrome,
       history,
-      toastNotifications,
-      stateTransferService,
       savedObjects,
-      uiSettings,
-      visualizeCapabilities,
-      dashboardCapabilities,
-      kbnUrlStateStorage,
-      overlays,
       savedObjectsTagging,
+      overlays,
+      toastNotifications,
+      visualizeCapabilities,
     },
   } = useKibana<VisualizeServices>();
-  const { pathname } = useLocation();
-  const closeNewVisModal = useRef(() => {});
-  const visualizedUserContent = useRef<VisualizeUserContent[]>();
-  const listingLimit = uiSettings.get(SAVED_OBJECTS_LIMIT_SETTING);
-  const initialPageSize = uiSettings.get(SAVED_OBJECTS_PER_PAGE_SETTING);
-
-  useExecutionContext(executionContext, {
-    type: 'application',
-    page: 'list',
-  });
-
-  useEffect(() => {
-    if (pathname === '/new') {
-      // In case the user navigated to the page via the /visualize/new URL we start the dialog immediately
-      closeNewVisModal.current = showNewVisModal({
-        onClose: () => {
-          // In case the user came via a URL to this page, change the URL to the regular landing page URL after closing the modal
-          history.push(VisualizeConstants.LANDING_PAGE_PATH);
-        },
-      });
-    } else {
-      // close modal window if exists
-      closeNewVisModal.current();
-    }
-  }, [history, pathname]);
 
-  useMount(() => {
-    // Reset editor state for all apps if the visualize listing page is loaded.
-    stateTransferService.clearEditorState();
-    chrome.setBreadcrumbs([
-      {
-        text: i18n.translate('visualizations.visualizeListingBreadcrumbsTitle', {
-          defaultMessage: 'Visualize Library',
-        }),
-      },
-    ]);
-    chrome.docTitle.change(
-      i18n.translate('visualizations.listingPageTitle', { defaultMessage: 'Visualize Library' })
-    );
-  });
-  useUnmount(() => closeNewVisModal.current());
+  const visualizedUserContent = useRef<VisualizeUserContent[]>();
 
   const createNewVis = useCallback(() => {
     closeNewVisModal.current = showNewVisModal();
-  }, []);
+  }, [closeNewVisModal]);
 
   const editItem = useCallback(
     ({ attributes: { editUrl, editApp } }: VisualizeUserContent) => {
@@ -259,74 +240,172 @@ export const VisualizeListing = () => {
     [savedObjects.client, toastNotifications]
   );
 
-  const calloutMessage = (
-    <FormattedMessage
-      data-test-subj="visualize-dashboard-flow-prompt"
-      id="visualizations.visualizeListingDashboardFlowDescription"
-      defaultMessage="Building a dashboard? Create and add your visualizations right from the {dashboardApp}."
-      values={{
-        dashboardApp: (
-          <EuiLink
-            className="visListingCallout__link"
-            onClick={(event: MouseEvent) => {
-              event.preventDefault();
-              application.navigateToUrl(application.getUrlForApp('dashboards'));
-            }}
-          >
-            <FormattedMessage
-              id="visualizations.visualizeListingDashboardAppName"
-              defaultMessage="Dashboard application"
-            />
-          </EuiLink>
-        ),
-      }}
-    />
+  const props: CustomTableViewProps = {
+    findItems: fetchItems,
+    deleteItems,
+    contentEditor: {
+      isReadonly: !visualizeCapabilities.save,
+      onSave: onContentEditorSave,
+      customValidators: contentEditorValidators,
+    },
+    editItem,
+    emptyPrompt: noItemsFragment,
+    createItem: createNewVis,
+    showEditActionForItem: ({ attributes: { readOnly } }) =>
+      visualizeCapabilities.save && !readOnly,
+  };
+
+  return props;
+};
+
+export const VisualizeListing = () => {
+  const {
+    services: {
+      application,
+      executionContext,
+      chrome,
+      history,
+      stateTransferService,
+      dashboardCapabilities,
+      uiSettings,
+      kbnUrlStateStorage,
+      listingViewRegistry,
+    },
+  } = useKibana<VisualizeServices>();
+  const { pathname } = useLocation();
+  const closeNewVisModal = useRef(() => {});
+
+  useExecutionContext(executionContext, {
+    type: 'application',
+    page: 'list',
+  });
+
+  useEffect(() => {
+    if (pathname === '/new') {
+      // In case the user navigated to the page via the /visualize/new URL we start the dialog immediately
+      closeNewVisModal.current = showNewVisModal({
+        onClose: () => {
+          // In case the user came via a URL to this page, change the URL to the regular landing page URL after closing the modal
+          history.push(VisualizeConstants.LANDING_PAGE_PATH);
+        },
+      });
+    } else {
+      // close modal window if exists
+      closeNewVisModal.current();
+    }
+  }, [history, pathname]);
+
+  useMount(() => {
+    // Reset editor state for all apps if the visualize listing page is loaded.
+    stateTransferService.clearEditorState();
+    chrome.setBreadcrumbs([
+      {
+        text: i18n.translate('visualizations.visualizeListingBreadcrumbsTitle', {
+          defaultMessage: 'Visualize Library',
+        }),
+      },
+    ]);
+    chrome.docTitle.change(
+      i18n.translate('visualizations.listingPageTitle', { defaultMessage: 'Visualize Library' })
+    );
+  });
+  useUnmount(() => closeNewVisModal.current());
+
+  const listingLimit = uiSettings.get(SAVED_OBJECTS_LIMIT_SETTING);
+  const initialPageSize = uiSettings.get(SAVED_OBJECTS_PER_PAGE_SETTING);
+
+  const tableViewProps = useTableListViewProps(closeNewVisModal, listingLimit);
+
+  const visualizeLibraryTitle = i18n.translate('visualizations.listing.table.listTitle', {
+    defaultMessage: 'Visualize Library',
+  });
+
+  const visualizeTab: TableListTab<VisualizeUserContent> = useMemo(() => {
+    const calloutMessage = (
+      <FormattedMessage
+        data-test-subj="visualize-dashboard-flow-prompt"
+        id="visualizations.visualizeListingDashboardFlowDescription"
+        defaultMessage="Building a dashboard? Create and add your visualizations right from the {dashboardApp}."
+        values={{
+          dashboardApp: (
+            <EuiLink
+              className="visListingCallout__link"
+              onClick={(event: MouseEvent) => {
+                event.preventDefault();
+                application.navigateToUrl(application.getUrlForApp('dashboards'));
+              }}
+            >
+              <FormattedMessage
+                id="visualizations.visualizeListingDashboardAppName"
+                defaultMessage="Dashboard application"
+              />
+            </EuiLink>
+          ),
+        }}
+      />
+    );
+
+    return {
+      title: 'Visualizations',
+      id: 'visualizations',
+      getTableList: (propsFromParent) => (
+        <>
+          {dashboardCapabilities.createNew && (
+            <>
+              <EuiCallOut size="s" title={calloutMessage} iconType="iInCircle" />
+              <EuiSpacer size="m" />
+            </>
+          )}
+          <TableListViewTable<VisualizeUserContent>
+            id="vis"
+            // we allow users to create visualizations even if they can't save them
+            // for data exploration purposes
+            customTableColumn={getCustomColumn()}
+            listingLimit={listingLimit}
+            initialPageSize={initialPageSize}
+            initialFilter={''}
+            entityName={i18n.translate('visualizations.listing.table.entityName', {
+              defaultMessage: 'visualization',
+            })}
+            entityNamePlural={i18n.translate('visualizations.listing.table.entityNamePlural', {
+              defaultMessage: 'visualizations',
+            })}
+            getDetailViewLink={({ attributes: { editApp, editUrl, error } }) =>
+              getVisualizeListItemLink(application, kbnUrlStateStorage, editApp, editUrl, error)
+            }
+            tableCaption={visualizeLibraryTitle}
+            {...tableViewProps}
+            {...propsFromParent}
+          />
+        </>
+      ),
+    };
+  }, [
+    application,
+    dashboardCapabilities.createNew,
+    initialPageSize,
+    kbnUrlStateStorage,
+    listingLimit,
+    tableViewProps,
+    visualizeLibraryTitle,
+  ]);
+
+  const tabs = useMemo(
+    () => [visualizeTab, ...Array.from(listingViewRegistry as Set<TableListTab>)],
+    [listingViewRegistry, visualizeTab]
   );
 
+  const { activeTab } = useParams<{ activeTab: string }>();
+
   return (
-    <TableListView<VisualizeUserContent>
-      id="vis"
+    <TabbedTableListView
       headingId="visualizeListingHeading"
-      // we allow users to create visualizations even if they can't save them
-      // for data exploration purposes
-      createItem={createNewVis}
-      findItems={fetchItems}
-      deleteItems={visualizeCapabilities.delete ? deleteItems : undefined}
-      editItem={visualizeCapabilities.save ? editItem : undefined}
-      showEditActionForItem={({ attributes: { readOnly } }) =>
-        visualizeCapabilities.save && !readOnly
-      }
-      customTableColumn={getCustomColumn()}
-      listingLimit={listingLimit}
-      initialPageSize={initialPageSize}
-      initialFilter={''}
-      contentEditor={{
-        isReadonly: !visualizeCapabilities.save,
-        onSave: onContentEditorSave,
-        customValidators: contentEditorValidators,
+      title={visualizeLibraryTitle}
+      tabs={tabs}
+      activeTabId={activeTab}
+      changeActiveTab={(id) => {
+        application.navigateToUrl(`#/${id}`);
       }}
-      emptyPrompt={noItemsFragment}
-      entityName={i18n.translate('visualizations.listing.table.entityName', {
-        defaultMessage: 'visualization',
-      })}
-      entityNamePlural={i18n.translate('visualizations.listing.table.entityNamePlural', {
-        defaultMessage: 'visualizations',
-      })}
-      tableListTitle={i18n.translate('visualizations.listing.table.listTitle', {
-        defaultMessage: 'Visualize Library',
-      })}
-      getDetailViewLink={({ attributes: { editApp, editUrl, error, readOnly } }) =>
-        readOnly
-          ? undefined
-          : getVisualizeListItemLink(core.application, kbnUrlStateStorage, editApp, editUrl, error)
-      }
-    >
-      {dashboardCapabilities.createNew && (
-        <>
-          <EuiCallOut size="s" title={calloutMessage} iconType="iInCircle" />
-          <EuiSpacer size="m" />
-        </>
-      )}
-    </TableListView>
+    />
   );
 };
diff --git a/src/plugins/visualizations/public/visualize_app/index.tsx b/src/plugins/visualizations/public/visualize_app/index.tsx
index e432275c755e6..0dc41f8f35d07 100644
--- a/src/plugins/visualizations/public/visualize_app/index.tsx
+++ b/src/plugins/visualizations/public/visualize_app/index.tsx
@@ -17,7 +17,7 @@ import {
   toMountPoint,
 } from '@kbn/kibana-react-plugin/public';
 import { FormattedRelative } from '@kbn/i18n-react';
-import { TableListViewKibanaProvider } from '@kbn/content-management-table-list';
+import { TableListViewKibanaProvider } from '@kbn/content-management-table-list-view-table';
 import { VisualizeApp } from './app';
 import { VisualizeServices } from './types';
 import { addHelpMenuToAppChrome, addBadgeToAppChrome } from './utils';
diff --git a/src/plugins/visualizations/public/visualize_app/types.ts b/src/plugins/visualizations/public/visualize_app/types.ts
index c340342dab8f0..8d86648e9d685 100644
--- a/src/plugins/visualizations/public/visualize_app/types.ts
+++ b/src/plugins/visualizations/public/visualize_app/types.ts
@@ -48,7 +48,7 @@ import type {
   VisParams,
 } from '..';
 
-import type { SavedVisState } from '../types';
+import type { ListingViewRegistry, SavedVisState } from '../types';
 import type { createVisEmbeddableFromObject } from '../embeddable';
 import type { VisEditorsRegistry } from '../vis_editors_registry';
 
@@ -113,6 +113,7 @@ export interface VisualizeServices extends CoreStart {
   spaces?: SpacesPluginStart;
   theme: ThemeServiceStart;
   visEditorsRegistry: VisEditorsRegistry;
+  listingViewRegistry: ListingViewRegistry;
   unifiedSearch: UnifiedSearchPublicPluginStart;
 }
 
diff --git a/src/plugins/visualizations/tsconfig.json b/src/plugins/visualizations/tsconfig.json
index dd5edc8961361..65e1053e4f7e1 100644
--- a/src/plugins/visualizations/tsconfig.json
+++ b/src/plugins/visualizations/tsconfig.json
@@ -42,7 +42,8 @@
     "@kbn/i18n-react",
     "@kbn/safer-lodash-set",
     "@kbn/shared-ux-page-analytics-no-data",
-    "@kbn/content-management-table-list",
+    "@kbn/content-management-table-list-view",
+    "@kbn/content-management-tabbed-table-list-view",
     "@kbn/test-jest-helpers",
     "@kbn/analytics",
     "@kbn/content-management-content-editor",
@@ -57,7 +58,10 @@
     "@kbn/core-saved-objects-api-server",
     "@kbn/object-versioning",
     "@kbn/core-saved-objects-server",
-    "@kbn/core-saved-objects-utils-server"
+    "@kbn/core-saved-objects-utils-server",
+    "@kbn/content-management-table-list-view-table",
+    "@kbn/content-management-tabbed-table-list-view",
+    "@kbn/content-management-table-list-view"
   ],
   "exclude": [
     "target/**/*",
diff --git a/test/functional/page_objects/visualize_page.ts b/test/functional/page_objects/visualize_page.ts
index bcfbc8caa9ce1..a08b950ce9853 100644
--- a/test/functional/page_objects/visualize_page.ts
+++ b/test/functional/page_objects/visualize_page.ts
@@ -69,6 +69,14 @@ export class VisualizePageObject extends FtrService {
     await this.common.navigateToApp('visualize');
   }
 
+  public async selectVisualizationsTab() {
+    await this.listingTable.selectTab(1);
+  }
+
+  public async selectAnnotationsTab() {
+    await this.listingTable.selectTab(2);
+  }
+
   public async clickNewVisualization() {
     await this.listingTable.clickNewButton();
   }
diff --git a/test/functional/services/listing_table.ts b/test/functional/services/listing_table.ts
index 96e9ba2e49d34..9bd2eb80132a8 100644
--- a/test/functional/services/listing_table.ts
+++ b/test/functional/services/listing_table.ts
@@ -204,6 +204,10 @@ export class ListingTableService extends FtrService {
     await this.testSubjects.click('deleteSelectedItems');
   }
 
+  public async selectFirstItemInList() {
+    await this.find.clickByCssSelector('.euiTableCellContent .euiCheckbox__input');
+  }
+
   public async clickItemCheckbox(id: string) {
     await this.testSubjects.click(`checkboxSelectRow-${id}`);
   }
@@ -213,9 +217,13 @@ export class ListingTableService extends FtrService {
    * @param name item name
    * @param id row id
    */
-  public async deleteItem(name: string, id: string) {
+  public async deleteItem(name: string, id?: string) {
     await this.searchForItemWithName(name);
-    await this.clickItemCheckbox(id);
+    if (id) {
+      await this.clickItemCheckbox(id);
+    } else {
+      await this.selectFirstItemInList();
+    }
     await this.clickDeleteSelected();
     await this.common.clickConfirmOnModal();
   }
@@ -253,4 +261,8 @@ export class ListingTableService extends FtrService {
       timeout: 5000,
     });
   }
+
+  public async selectTab(which: number) {
+    await this.find.clickByCssSelector(`.euiTab:nth-child(${which})`);
+  }
 }
diff --git a/tsconfig.base.json b/tsconfig.base.json
index 5ab10d6e5de8e..6830598bf44a8 100644
--- a/tsconfig.base.json
+++ b/tsconfig.base.json
@@ -170,8 +170,12 @@
       "@kbn/content-management-examples-plugin/*": ["examples/content_management_examples/*"],
       "@kbn/content-management-plugin": ["src/plugins/content_management"],
       "@kbn/content-management-plugin/*": ["src/plugins/content_management/*"],
-      "@kbn/content-management-table-list": ["packages/content-management/table_list"],
-      "@kbn/content-management-table-list/*": ["packages/content-management/table_list/*"],
+      "@kbn/content-management-tabbed-table-list-view": ["packages/content-management/tabbed_table_list_view"],
+      "@kbn/content-management-tabbed-table-list-view/*": ["packages/content-management/tabbed_table_list_view/*"],
+      "@kbn/content-management-table-list-view": ["packages/content-management/table_list_view"],
+      "@kbn/content-management-table-list-view/*": ["packages/content-management/table_list_view/*"],
+      "@kbn/content-management-table-list-view-table": ["packages/content-management/table_list_view_table"],
+      "@kbn/content-management-table-list-view-table/*": ["packages/content-management/table_list_view_table/*"],
       "@kbn/content-management-utils": ["packages/kbn-content-management-utils"],
       "@kbn/content-management-utils/*": ["packages/kbn-content-management-utils/*"],
       "@kbn/controls-example-plugin": ["examples/controls_example"],
diff --git a/x-pack/plugins/graph/public/application.tsx b/x-pack/plugins/graph/public/application.tsx
index 28b44b804373a..82e1a061c67c8 100644
--- a/x-pack/plugins/graph/public/application.tsx
+++ b/x-pack/plugins/graph/public/application.tsx
@@ -27,7 +27,7 @@ import { NavigationPublicPluginStart as NavigationStart } from '@kbn/navigation-
 import { Storage } from '@kbn/kibana-utils-plugin/public';
 import { FormattedRelative } from '@kbn/i18n-react';
 import { Start as InspectorPublicPluginStart } from '@kbn/inspector-plugin/public';
-import { TableListViewKibanaProvider } from '@kbn/content-management-table-list';
+import { TableListViewKibanaProvider } from '@kbn/content-management-table-list-view-table';
 
 import './index.scss';
 import('./font_awesome');
diff --git a/x-pack/plugins/graph/public/apps/listing_route.tsx b/x-pack/plugins/graph/public/apps/listing_route.tsx
index a34a4bc5b591b..d5e7d2be00967 100644
--- a/x-pack/plugins/graph/public/apps/listing_route.tsx
+++ b/x-pack/plugins/graph/public/apps/listing_route.tsx
@@ -11,8 +11,8 @@ import { FormattedMessage, I18nProvider } from '@kbn/i18n-react';
 import { EuiEmptyPrompt, EuiLink, EuiButton } from '@elastic/eui';
 import { ApplicationStart } from '@kbn/core/public';
 import { useHistory, useLocation } from 'react-router-dom';
-import { TableListView } from '@kbn/content-management-table-list';
-import type { UserContentCommonSchema } from '@kbn/content-management-table-list';
+import { TableListView } from '@kbn/content-management-table-list-view';
+import type { UserContentCommonSchema } from '@kbn/content-management-table-list-view';
 import { deleteSavedWorkspace, findSavedWorkspace } from '../helpers/saved_workspace_utils';
 import { getEditPath, getEditUrl, getNewPath, setBreadcrumbs } from '../services/url';
 import { GraphWorkspaceSavedObject } from '../types';
@@ -111,7 +111,7 @@ export function ListingRoute({
         entityNamePlural={i18n.translate('xpack.graph.listing.table.entityNamePlural', {
           defaultMessage: 'graphs',
         })}
-        tableListTitle={i18n.translate('xpack.graph.listing.graphsTitle', {
+        title={i18n.translate('xpack.graph.listing.graphsTitle', {
           defaultMessage: 'Graphs',
         })}
         getDetailViewLink={({ id }) => getEditUrl(addBasePath, { id })}
diff --git a/x-pack/plugins/graph/tsconfig.json b/x-pack/plugins/graph/tsconfig.json
index b91a6913f07ae..4ee1639a2d4f8 100644
--- a/x-pack/plugins/graph/tsconfig.json
+++ b/x-pack/plugins/graph/tsconfig.json
@@ -28,7 +28,6 @@
     "@kbn/config-schema",
     "@kbn/i18n-react",
     "@kbn/inspector-plugin",
-    "@kbn/content-management-table-list",
     "@kbn/test-jest-helpers",
     "@kbn/data-views-plugin",
     "@kbn/es-query",
@@ -44,6 +43,8 @@
     "@kbn/content-management-plugin",
     "@kbn/core-saved-objects-api-server",
     "@kbn/object-versioning",
+    "@kbn/content-management-table-list-view-table",
+    "@kbn/content-management-table-list-view",
   ],
   "exclude": [
     "target/**/*",
diff --git a/x-pack/plugins/lens/common/constants.ts b/x-pack/plugins/lens/common/constants.ts
index 8838b974ac4dc..5909dbcb48f4b 100644
--- a/x-pack/plugins/lens/common/constants.ts
+++ b/x-pack/plugins/lens/common/constants.ts
@@ -8,10 +8,10 @@
 import rison from '@kbn/rison';
 import type { RefreshInterval, TimeRange } from '@kbn/data-plugin/common/query';
 import type { Filter } from '@kbn/es-query';
-import { i18n } from '@kbn/i18n';
 
 export const PLUGIN_ID = 'lens';
 export const APP_ID = 'lens';
+export const LENS_APP_NAME = 'lens';
 export const LENS_EMBEDDABLE_TYPE = 'lens';
 export const DOC_TYPE = 'lens';
 export const NOT_INTERNATIONALIZED_PRODUCT_NAME = 'Lens Visualizations';
@@ -89,7 +89,3 @@ export function getEditPath(
 export function getFullPath(id?: string) {
   return `/app/${PLUGIN_ID}${id ? getEditPath(id) : getBasePath()}`;
 }
-
-export const LENS_APP_NAME = i18n.translate('xpack.lens.queryInput.appName', {
-  defaultMessage: 'Lens',
-});
diff --git a/x-pack/plugins/lens/common/types.ts b/x-pack/plugins/lens/common/types.ts
index 3c6830ba50b0d..ee89551dfc36a 100644
--- a/x-pack/plugins/lens/common/types.ts
+++ b/x-pack/plugins/lens/common/types.ts
@@ -9,7 +9,6 @@ import type { Filter, FilterMeta } from '@kbn/es-query';
 import type { Position } from '@elastic/charts';
 import type { $Values } from '@kbn/utility-types';
 import type { CustomPaletteParams, PaletteOutput } from '@kbn/coloring';
-import type { IFieldFormat, SerializedFieldFormat } from '@kbn/field-formats-plugin/common';
 import type { ColorMode } from '@kbn/charts-plugin/common';
 import type { LegendSize } from '@kbn/visualizations-plugin/common';
 import { CategoryDisplay, LegendDisplay, NumberDisplay, PieChartTypes } from './constants';
@@ -21,8 +20,7 @@ export type { AllowedPartitionOverrides } from '@kbn/expression-partition-vis-pl
 export type { AllowedSettingsOverrides } from '@kbn/charts-plugin/common';
 export type { AllowedGaugeOverrides } from '@kbn/expression-gauge-plugin/common';
 export type { AllowedXYOverrides } from '@kbn/expression-xy-plugin/common';
-
-export type FormatFactory = (mapping?: SerializedFieldFormat) => IFieldFormat;
+export type { FormatFactory } from '@kbn/visualization-ui-components/public';
 
 export interface DateRange {
   fromDate: string;
diff --git a/x-pack/plugins/lens/public/app_plugin/lens_top_nav.tsx b/x-pack/plugins/lens/public/app_plugin/lens_top_nav.tsx
index 41b0fa567d567..ca5efceaf48bd 100644
--- a/x-pack/plugins/lens/public/app_plugin/lens_top_nav.tsx
+++ b/x-pack/plugins/lens/public/app_plugin/lens_top_nav.tsx
@@ -17,7 +17,7 @@ import { useKibana } from '@kbn/kibana-react-plugin/public';
 import { DataViewPickerProps } from '@kbn/unified-search-plugin/public';
 import moment from 'moment';
 import { LENS_APP_LOCATOR } from '../../common/locator/locator';
-import { ENABLE_SQL } from '../../common/constants';
+import { ENABLE_SQL, LENS_APP_NAME } from '../../common/constants';
 import { LensAppServices, LensTopNavActions, LensTopNavMenuProps } from './types';
 import { toggleSettingsMenuOpen } from './settings_menu';
 import {
@@ -1096,7 +1096,7 @@ export const LensTopNavMenu = ({
       showFilterBar={true}
       data-test-subj="lnsApp_topNav"
       screenTitle={'lens'}
-      appName={'lens'}
+      appName={LENS_APP_NAME}
       displayStyle="detached"
       className="hide-for-sharing"
     />
diff --git a/x-pack/plugins/lens/public/data_views_service/loader.ts b/x-pack/plugins/lens/public/data_views_service/loader.ts
index abd8a48815122..ef48761d39a6e 100644
--- a/x-pack/plugins/lens/public/data_views_service/loader.ts
+++ b/x-pack/plugins/lens/public/data_views_service/loader.ts
@@ -5,7 +5,7 @@
  * 2.0.
  */
 
-import { isNestedField } from '@kbn/data-views-plugin/common';
+import { isFieldLensCompatible } from '@kbn/visualization-ui-components/public';
 import type { DataViewsContract, DataView, DataViewSpec } from '@kbn/data-views-plugin/public';
 import { keyBy } from 'lodash';
 import { IndexPattern, IndexPatternField, IndexPatternMap, IndexPatternRef } from '../types';
@@ -30,7 +30,7 @@ export function convertDataViewIntoLensIndexPattern(
   restrictionRemapper: (name: string) => string = onRestrictionMapping
 ): IndexPattern {
   const newFields = dataView.fields
-    .filter((field) => !isNestedField(field) && (!!field.aggregatable || !!field.scripted))
+    .filter(isFieldLensCompatible)
     .map((field): IndexPatternField => {
       // Convert the getters on the index pattern service into plain JSON
       const base = {
diff --git a/x-pack/plugins/lens/public/datasources/form_based/dimension_panel/dimension_panel.test.tsx b/x-pack/plugins/lens/public/datasources/form_based/dimension_panel/dimension_panel.test.tsx
index 234bc93e4798b..e13fc22464649 100644
--- a/x-pack/plugins/lens/public/datasources/form_based/dimension_panel/dimension_panel.test.tsx
+++ b/x-pack/plugins/lens/public/datasources/form_based/dimension_panel/dimension_panel.test.tsx
@@ -669,7 +669,7 @@ describe('FormBasedDimensionEditor', () => {
 
     act(() => {
       wrapper
-        .find('input[data-test-subj="column-label-edit"]')
+        .find('input[data-test-subj="name-input"]')
         .simulate('change', { target: { value: 'New Label' } });
     });
 
@@ -773,7 +773,7 @@ describe('FormBasedDimensionEditor', () => {
 
     act(() => {
       wrapper
-        .find('input[data-test-subj="column-label-edit"]')
+        .find('input[data-test-subj="name-input"]')
         .simulate('change', { target: { value: 'Sum of bytes' } });
     });
 
diff --git a/x-pack/plugins/lens/public/datasources/form_based/form_based.tsx b/x-pack/plugins/lens/public/datasources/form_based/form_based.tsx
index 59c8096c7d269..d5779ef4ac81a 100644
--- a/x-pack/plugins/lens/public/datasources/form_based/form_based.tsx
+++ b/x-pack/plugins/lens/public/datasources/form_based/form_based.tsx
@@ -26,6 +26,7 @@ import type { UnifiedSearchPublicPluginStart } from '@kbn/unified-search-plugin/
 import { EuiButton } from '@elastic/eui';
 import type { SharePluginStart } from '@kbn/share-plugin/public';
 import type { DraggingIdentifier } from '@kbn/dom-drag-drop';
+import { DimensionTrigger } from '@kbn/visualization-ui-components/public';
 import type {
   DatasourceDimensionEditorProps,
   DatasourceDimensionTriggerProps,
@@ -99,7 +100,6 @@ import { DOCUMENT_FIELD_NAME } from '../../../common/constants';
 import { isColumnOfType } from './operations/definitions/helpers';
 import { LayerSettingsPanel } from './layer_settings';
 import { FormBasedLayer } from '../..';
-import { DimensionTrigger } from '../../shared_components/dimension_trigger';
 import { filterAndSortUserMessages } from '../../app_plugin/get_application_user_messages';
 export type { OperationType, GenericIndexPatternColumn } from './operations';
 export { deleteColumn } from './operations';
diff --git a/x-pack/plugins/lens/public/datasources/text_based/text_based_languages.tsx b/x-pack/plugins/lens/public/datasources/text_based/text_based_languages.tsx
index dab66cac44fa5..90ce505b06700 100644
--- a/x-pack/plugins/lens/public/datasources/text_based/text_based_languages.tsx
+++ b/x-pack/plugins/lens/public/datasources/text_based/text_based_languages.tsx
@@ -19,6 +19,7 @@ import type { ExpressionsStart, DatatableColumnType } from '@kbn/expressions-plu
 import type { DataViewsPublicPluginStart, DataView } from '@kbn/data-views-plugin/public';
 import type { DataPublicPluginStart } from '@kbn/data-plugin/public';
 import { euiThemeVars } from '@kbn/ui-theme';
+import { DimensionTrigger } from '@kbn/visualization-ui-components/public';
 import {
   DatasourceDimensionEditorProps,
   DatasourceDataPanelProps,
@@ -42,7 +43,6 @@ import type {
 import { FieldSelect } from './field_select';
 import type { Datasource, IndexPatternMap } from '../../types';
 import { LayerPanel } from './layerpanel';
-import { DimensionTrigger } from '../../shared_components/dimension_trigger';
 
 function getLayerReferenceName(layerId: string) {
   return `textBasedLanguages-datasource-layer-${layerId}`;
diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/buttons/empty_dimension_button.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/buttons/empty_dimension_button.tsx
index f0ef1508c5076..1e2b3ff4b4c05 100644
--- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/buttons/empty_dimension_button.tsx
+++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/buttons/empty_dimension_button.tsx
@@ -6,7 +6,6 @@
  */
 
 import React, { useMemo, useState, useEffect, useContext } from 'react';
-import { EuiButtonEmpty } from '@elastic/eui';
 import { FormattedMessage } from '@kbn/i18n-react';
 import { i18n } from '@kbn/i18n';
 import {
@@ -16,6 +15,9 @@ import {
   DropType,
   DropTargetSwapDuplicateCombine,
 } from '@kbn/dom-drag-drop';
+import { EmptyDimensionButton as EmptyDimensionButtonInner } from '@kbn/visualization-ui-components/public';
+import { css } from '@emotion/react';
+import { euiThemeVars } from '@kbn/ui-theme';
 import { isDraggedField } from '../../../../utils';
 import { generateId } from '../../../../id_generator';
 
@@ -56,49 +58,32 @@ const defaultButtonLabels = {
 const DefaultEmptyButton = ({ columnId, group, onClick }: EmptyButtonProps) => {
   const { buttonAriaLabel, buttonLabel } = group.labels || {};
   return (
-    <EuiButtonEmpty
-      className="lnsLayerPanel__triggerText"
-      color="text"
-      size="s"
-      iconType="plus"
-      contentProps={{
-        className: 'lnsLayerPanel__triggerTextContent',
-      }}
-      aria-label={buttonAriaLabel || defaultButtonLabels.ariaLabel(group.groupLabel)}
-      data-test-subj="lns-empty-dimension"
-      onClick={() => {
-        onClick(columnId);
-      }}
-    >
-      {buttonLabel || defaultButtonLabels.label}
-    </EuiButtonEmpty>
+    <EmptyDimensionButtonInner
+      label={buttonLabel || defaultButtonLabels.label}
+      ariaLabel={buttonAriaLabel || defaultButtonLabels.ariaLabel(group.groupLabel)}
+      dataTestSubj="lns-empty-dimension"
+      onClick={() => onClick(columnId)}
+    />
   );
 };
 
 const SuggestedValueButton = ({ columnId, group, onClick }: EmptyButtonProps) => (
-  <EuiButtonEmpty
-    className="lnsLayerPanel__triggerText"
-    color="text"
-    size="s"
-    iconType="plusInCircleFilled"
-    contentProps={{
-      className: 'lnsLayerPanel__triggerTextContent',
-    }}
-    aria-label={i18n.translate('xpack.lens.indexPattern.suggestedValueAriaLabel', {
+  <EmptyDimensionButtonInner
+    label={
+      <FormattedMessage
+        id="xpack.lens.configure.suggestedValuee"
+        defaultMessage="Suggested value: {value}"
+        values={{ value: group.suggestedValue?.() }}
+      />
+    }
+    ariaLabel={i18n.translate('xpack.lens.indexPattern.suggestedValueAriaLabel', {
       defaultMessage: 'Suggested value: {value} for {groupLabel}',
       values: { value: group.suggestedValue?.(), groupLabel: group.groupLabel },
     })}
-    data-test-subj="lns-empty-dimension-suggested-value"
-    onClick={() => {
-      onClick(columnId);
-    }}
-  >
-    <FormattedMessage
-      id="xpack.lens.configure.suggestedValuee"
-      defaultMessage="Suggested value: {value}"
-      values={{ value: group.suggestedValue?.() }}
-    />
-  </EuiButtonEmpty>
+    dataTestSubj="lns-empty-dimension-suggested-value"
+    iconType="plusInCircleFilled"
+    onClick={() => onClick(columnId)}
+  />
 );
 
 export function EmptyDimensionButton({
@@ -205,7 +190,11 @@ export function EmptyDimensionButton({
         onDrop={handleOnDrop}
         dropTypes={dropTypes}
       >
-        <div className="lnsLayerPanel__dimension lnsLayerPanel__dimension--empty">
+        <div
+          css={css`
+            border-radius: ${euiThemeVars.euiBorderRadius};
+          `}
+        >
           {typeof group.suggestedValue?.() === 'number' ? (
             <SuggestedValueButton {...buttonProps} />
           ) : (
diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.scss b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.scss
index 0efd82bc31063..3afcc0173ca2e 100644
--- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.scss
+++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.scss
@@ -41,19 +41,19 @@
   }
 
   // Add border to the top of the next same panel
-  & + & {
+  &+& {
     border-top: $euiBorderThin;
     margin-top: 0;
   }
 
-  & > * {
+  &>* {
     margin-bottom: 0;
   }
 
   // Targeting EUI class as we are unable to apply a class to this element in component
   &,
   .euiFormRow__fieldWrapper {
-    & > * + * {
+    &>*+* {
       margin-top: $euiSizeS;
     }
   }
@@ -64,121 +64,31 @@
   padding: $euiSizeS $euiSize;
 }
 
-.lnsLayerPanel__dimensionRemove {
-  margin-right: $euiSizeS;
-  opacity: 0;
-
-  &:focus {
-    opacity: 1;
-  }
-}
-
-.lnsLayerPanel__dimension {
-  @include euiFontSizeS;
-  border-radius: $euiBorderRadius;
-  display: flex;
-  align-items: center;
-  overflow: hidden;
-  min-height: $euiSizeXL;
-  position: relative;
-
-  // NativeRenderer is messing this up
-  > div {
-    flex-grow: 1;
-  }
-
-  &:hover,
-  &:focus {
-    .lnsLayerPanel__dimensionRemove {
-      visibility: visible;
-      opacity: 1;
-      transition: opacity $euiAnimSpeedFast ease-in-out;
-    }
-  }
+.lnsLayerPanel__styleEditor {
+  padding: $euiSize;
 }
 
-.lnsLayerPanel__dimension--empty {
-  border: $euiBorderWidthThin dashed $euiBorderColor !important;
-
-  &:focus,
-  &:focus-within {
-    @include euiFocusRing;
-  }
-}
+// Start dimension style overrides
 
 .lnsLayerPanel__dimensionContainer {
   position: relative;
 
-  & + & {
+  &+& {
     margin-top: $euiSizeS;
   }
 }
 
-.lnsLayerPanel__triggerText {
-  width: 100%;
-  padding: $euiSizeXS $euiSizeS;
-  word-break: break-word;
-  font-weight: $euiFontWeightRegular;
-}
-
-.lnsLayerPanel__dimensionLink {
-  &:hover {
-    text-decoration: none;
-  }
-}
-
-.lnsLayerPanel__triggerTextLabel {
-  transition: background-color $euiAnimSpeedFast ease-in-out;
-
-  &:hover {
-    text-decoration: underline;
-  }
-}
-
 .domDragDrop-isReplacing {
-  .lnsLayerPanel__triggerText {
+  .dimensionTrigger__textLabel {
     text-decoration: line-through;
   }
 }
 
-.lnsLayerPanel__triggerTextContent {
-  // Make EUI button content not centered
-  justify-content: flex-start;
-  padding: 0 !important; // sass-lint:disable-line no-important
-  color: $euiTextSubduedColor;
-}
-
-.lnsLayerPanel__styleEditor {
-  padding: $euiSize;
-}
-
-.lnsLayerPanel__colorIndicator {
-  margin-left: $euiSizeS;
-}
-
-.lnsLayerPanel__paletteContainer {
-  position: absolute;
-  bottom: 0;
-  left: 0;
-  right: 0;
-}
-
-.lnsLayerPanel__palette {
-  height: $euiSizeXS / 2;
-  border-radius: 0 0 ($euiBorderRadius - 1px) ($euiBorderRadius - 1px);
-
-  &::after {
-    border: none;
-  }
-}
-
 // Added .lnsLayerPanel__dimension specificity required for animation style override
 .lnsLayerPanel__dimension .lnsLayerPanel__dimensionLink {
-  width: 100%;
-
   &:focus {
-    @include passDownFocusRing('.lnsLayerPanel__triggerTextLabel');
+    @include passDownFocusRing('.dimensionTrigger__textLabel');
     background-color: transparent;
     text-decoration-thickness: $euiBorderWidthThin !important;
   }
-}
+}
\ No newline at end of file
diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.test.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.test.tsx
index 18b5acd160aaf..eae473567922d 100644
--- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.test.tsx
+++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.test.tsx
@@ -21,6 +21,7 @@ import {
   mountWithProvider,
 } from '../../../mocks';
 import { createIndexPatternServiceMock } from '../../../mocks/data_views_service_mock';
+import { DimensionButton } from '@kbn/visualization-ui-components/public';
 
 jest.mock('../../../id_generator');
 
@@ -714,9 +715,7 @@ describe('LayerPanel', () => {
 
       expect(instance.exists('[data-test-subj="lns-fakeDimension"]')).toBeTruthy();
       expect(
-        instance
-          .find('[data-test-subj="lns-fakeDimension"] .lnsLayerPanel__triggerTextLabel')
-          .text()
+        instance.find('[data-test-subj="lns-fakeDimension"] .dimensionTrigger__textLabel').text()
       ).toBe(fakeAccessorLabel);
     });
 
@@ -826,7 +825,7 @@ describe('LayerPanel', () => {
       const dragDropElement = instance
         .find('[data-test-subj="lnsGroup"] DragDrop')
         .first()
-        .find('.lnsLayerPanel__dimension')
+        .find(DimensionButton)
         .first();
 
       dragDropElement.simulate('dragOver');
diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.tsx
index a7314f2c1b6b4..736a46a38630f 100644
--- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.tsx
+++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.tsx
@@ -21,7 +21,7 @@ import { i18n } from '@kbn/i18n';
 import { css } from '@emotion/react';
 import { euiThemeVars } from '@kbn/ui-theme';
 import { DragDropIdentifier, ReorderProvider, DropType } from '@kbn/dom-drag-drop';
-import { DimensionButton } from '@kbn/visualization-ui-components/public';
+import { DimensionButton, DimensionTrigger } from '@kbn/visualization-ui-components/public';
 import { LayerActions } from './layer_actions';
 import { IndexPatternServiceAPI } from '../../../data_views_service/service';
 import { NativeRenderer } from '../../../native_renderer';
@@ -572,7 +572,6 @@ export function LayerPanel(
                             indexPatterns={dataViews.indexPatterns}
                           >
                             <DimensionButton
-                              className="lnsLayerPanel__dimension"
                               accessorConfig={accessorConfig}
                               label={columnLabelMap?.[accessorConfig.columnId] ?? ''}
                               groupLabel={group.groupLabel}
@@ -621,25 +620,24 @@ export function LayerPanel(
 
                   {group.fakeFinalAccessor && (
                     <div
-                      className="lnsLayerPanel__dimension domDragDrop-isDraggable"
+                      className="domDragDrop-isDraggable"
                       css={css`
+                        display: flex;
+                        align-items: center;
+                        border-radius: ${euiThemeVars.euiBorderRadius};
+                        min-height: ${euiThemeVars.euiSizeXL};
+
                         cursor: default !important;
-                        border-color: transparent !important;
-                        margin-top: ${group.accessors.length ? 8 : 0}px !important;
                         background-color: ${euiThemeVars.euiColorLightShade} !important;
+                        border-color: transparent !important;
                         box-shadow: none !important;
                       `}
                     >
-                      <EuiText
-                        size="s"
-                        className="lnsLayerPanel__triggerText"
+                      <DimensionTrigger
+                        label={group.fakeFinalAccessor.label}
+                        id="lns-fakeDimension"
                         data-test-subj="lns-fakeDimension"
-                        color={'subdued'}
-                      >
-                        <span className="lnsLayerPanel__triggerTextLabel">
-                          {group.fakeFinalAccessor.label}
-                        </span>
-                      </EuiText>
+                      />
                     </div>
                   )}
 
diff --git a/x-pack/plugins/lens/public/shared_components/dimension_trigger/index.tsx b/x-pack/plugins/lens/public/shared_components/dimension_trigger/index.tsx
deleted file mode 100644
index d705c50017a8d..0000000000000
--- a/x-pack/plugins/lens/public/shared_components/dimension_trigger/index.tsx
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * 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; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-import { EuiText, EuiFlexItem } from '@elastic/eui';
-import { i18n } from '@kbn/i18n';
-import React from 'react';
-import { EuiTextProps } from '@elastic/eui/src/components/text/text';
-
-export const defaultDimensionTriggerTooltip = (
-  <p>
-    {i18n.translate('xpack.lens.configure.invalidConfigTooltip', {
-      defaultMessage: 'Invalid configuration.',
-    })}
-    <br />
-    {i18n.translate('xpack.lens.configure.invalidConfigTooltipClick', {
-      defaultMessage: 'Click for more details.',
-    })}
-  </p>
-);
-
-export const DimensionTrigger = ({
-  id,
-  label,
-  color,
-  dataTestSubj,
-}: {
-  label: string;
-  id: string;
-  color?: EuiTextProps['color'];
-  dataTestSubj?: string;
-}) => {
-  return (
-    <EuiText
-      size="s"
-      id={id}
-      color={color}
-      className="lnsLayerPanel__triggerText"
-      data-test-subj={dataTestSubj || 'lns-dimensionTrigger'}
-    >
-      <EuiFlexItem grow={true}>
-        <span>
-          <span className="lnsLayerPanel__triggerTextLabel">{label}</span>
-        </span>
-      </EuiFlexItem>
-    </EuiText>
-  );
-};
diff --git a/x-pack/plugins/lens/public/visualizations/xy/annotations/actions/index.ts b/x-pack/plugins/lens/public/visualizations/xy/annotations/actions/index.ts
index b7bc603fae05f..b6e3f9e82d785 100644
--- a/x-pack/plugins/lens/public/visualizations/xy/annotations/actions/index.ts
+++ b/x-pack/plugins/lens/public/visualizations/xy/annotations/actions/index.ts
@@ -9,6 +9,8 @@ import type { CoreStart } from '@kbn/core/public';
 import { EventAnnotationServiceType } from '@kbn/event-annotation-plugin/public';
 import { SavedObjectTaggingPluginStart } from '@kbn/saved-objects-tagging-plugin/public';
 import { DataViewsContract } from '@kbn/data-views-plugin/public';
+import { VISUALIZE_APP_NAME } from '@kbn/visualizations-plugin/common/constants';
+import { ANNOTATIONS_LISTING_VIEW_ID } from '@kbn/event-annotation-plugin/common';
 import type { LayerAction, StateSetter } from '../../../../types';
 import { XYState, XYAnnotationLayerConfig } from '../../types';
 import { getUnlinkLayerAction } from './unlink_action';
@@ -51,6 +53,10 @@ export const createAnnotationActions = ({
         toasts: core.notifications.toasts,
         savedObjectsTagging,
         dataViews,
+        goToAnnotationLibrary: () =>
+          core.application.navigateToApp(VISUALIZE_APP_NAME, {
+            path: `#/${ANNOTATIONS_LISTING_VIEW_ID}`,
+          }),
       })
     );
   }
diff --git a/x-pack/plugins/lens/public/visualizations/xy/annotations/actions/save_action.test.tsx b/x-pack/plugins/lens/public/visualizations/xy/annotations/actions/save_action.test.tsx
index e5256ec49b78a..8505f9811749a 100644
--- a/x-pack/plugins/lens/public/visualizations/xy/annotations/actions/save_action.test.tsx
+++ b/x-pack/plugins/lens/public/visualizations/xy/annotations/actions/save_action.test.tsx
@@ -153,7 +153,7 @@ describe('annotation group save action', () => {
           toExpression: jest.fn(),
           toFetchExpression: jest.fn(),
           renderEventAnnotationGroupSavedObjectFinder: jest.fn(),
-        } as EventAnnotationServiceType,
+        } as Partial<EventAnnotationServiceType> as EventAnnotationServiceType,
         toasts: toastsServiceMock.createStartContract(),
         modalOnSaveProps: {
           newTitle: 'my title',
@@ -165,6 +165,7 @@ describe('annotation group save action', () => {
           onTitleDuplicate: () => {},
         },
         dataViews,
+        goToAnnotationLibrary: () => Promise.resolve(),
       };
     };
 
diff --git a/x-pack/plugins/lens/public/visualizations/xy/annotations/actions/save_action.tsx b/x-pack/plugins/lens/public/visualizations/xy/annotations/actions/save_action.tsx
index 0e66654af48c1..1b4ae5fd4958d 100644
--- a/x-pack/plugins/lens/public/visualizations/xy/annotations/actions/save_action.tsx
+++ b/x-pack/plugins/lens/public/visualizations/xy/annotations/actions/save_action.tsx
@@ -17,7 +17,7 @@ import {
   SavedObjectSaveModal,
 } from '@kbn/saved-objects-plugin/public';
 import { EventAnnotationGroupConfig } from '@kbn/event-annotation-plugin/common';
-import { EuiIcon } from '@elastic/eui';
+import { EuiIcon, EuiLink } from '@elastic/eui';
 import { type SavedObjectTaggingPluginStart } from '@kbn/saved-objects-tagging-plugin/public';
 import { DataViewsContract } from '@kbn/data-views-plugin/public';
 import type { LayerAction, StateSetter } from '../../../../types';
@@ -116,7 +116,7 @@ const saveAnnotationGroupToLibrary = async (
     title: newTitle,
     description: newDescription,
     tags: newTags,
-    dataViewSpec: dataView.isPersisted() ? undefined : dataView.toSpec(),
+    dataViewSpec: dataView.isPersisted() ? undefined : dataView.toSpec(false),
   };
 
   if (saveAsNew) {
@@ -140,6 +140,7 @@ export const onSave = async ({
   toasts,
   modalOnSaveProps: { newTitle, newDescription, newTags, closeModal, newCopyOnSave },
   dataViews,
+  goToAnnotationLibrary,
 }: {
   state: XYState;
   layer: XYAnnotationLayerConfig;
@@ -148,6 +149,7 @@ export const onSave = async ({
   toasts: ToastsStart;
   modalOnSaveProps: ModalOnSaveProps;
   dataViews: DataViewsContract;
+  goToAnnotationLibrary: () => Promise<void>;
 }) => {
   let savedInfo: Awaited<ReturnType<typeof saveAnnotationGroupToLibrary>>;
   try {
@@ -203,9 +205,21 @@ export const onSave = async ({
         <p>
           <FormattedMessage
             id="xpack.lens.xyChart.annotations.saveAnnotationGroupToLibrary.successToastBody"
-            defaultMessage="View or manage in the {link}"
+            defaultMessage="View or manage in the {link}."
             values={{
-              link: <a href="#">annotation library</a>,
+              link: (
+                <EuiLink
+                  data-test-subj="lnsAnnotationLibraryLink"
+                  onClick={() => goToAnnotationLibrary()}
+                >
+                  {i18n.translate(
+                    'xpack.lens.xyChart.annotations.saveAnnotationGroupToLibrary.annotationLibrary',
+                    {
+                      defaultMessage: 'annotation library',
+                    }
+                  )}
+                </EuiLink>
+              ),
             }}
           />
         </p>,
@@ -222,6 +236,7 @@ export const getSaveLayerAction = ({
   toasts,
   savedObjectsTagging,
   dataViews,
+  goToAnnotationLibrary,
 }: {
   state: XYState;
   layer: XYAnnotationLayerConfig;
@@ -230,6 +245,7 @@ export const getSaveLayerAction = ({
   toasts: ToastsStart;
   savedObjectsTagging?: SavedObjectTaggingPluginStart;
   dataViews: DataViewsContract;
+  goToAnnotationLibrary: () => Promise<void>;
 }): LayerAction => {
   const neverSaved = !isByReferenceAnnotationsLayer(layer);
 
@@ -261,6 +277,7 @@ export const getSaveLayerAction = ({
                 toasts,
                 modalOnSaveProps: props,
                 dataViews,
+                goToAnnotationLibrary,
               });
             }}
             title={neverSaved ? '' : layer.__lastSaved.title}
diff --git a/x-pack/plugins/lens/public/visualizations/xy/annotations/helpers.tsx b/x-pack/plugins/lens/public/visualizations/xy/annotations/helpers.tsx
index 6174017f3054b..3bf619cc76129 100644
--- a/x-pack/plugins/lens/public/visualizations/xy/annotations/helpers.tsx
+++ b/x-pack/plugins/lens/public/visualizations/xy/annotations/helpers.tsx
@@ -8,19 +8,19 @@
 import { i18n } from '@kbn/i18n';
 import moment from 'moment';
 import {
-  defaultAnnotationColor,
-  defaultAnnotationRangeColor,
+  getAnnotationAccessor,
   isQueryAnnotationConfig,
-  isRangeAnnotationConfig,
 } from '@kbn/event-annotation-plugin/public';
-import { EventAnnotationConfig } from '@kbn/event-annotation-plugin/common';
+import {
+  createCopiedAnnotation,
+  EventAnnotationConfig,
+  getDefaultQueryAnnotation,
+} from '@kbn/event-annotation-plugin/common';
 import { IconChartBarAnnotations } from '@kbn/chart-icons';
 import { LayerTypes } from '@kbn/expression-xy-plugin/public';
-import type { AccessorConfig } from '@kbn/visualization-ui-components/public';
 import { isDraggedDataViewField } from '../../../utils';
 import type { FramePublicAPI, Visualization } from '../../../types';
 import { isHorizontalChart } from '../state_helpers';
-import { annotationsIconSet } from '../xy_config_panel/annotations_config_panel/icon_set';
 import type { XYState, XYDataLayerConfig, XYAnnotationLayerConfig, XYLayerConfig } from '../types';
 import {
   checkScaleOperation,
@@ -129,50 +129,6 @@ export const getAnnotationsSupportedLayer = (
   };
 };
 
-const getDefaultManualAnnotation = (id: string, timestamp: string): EventAnnotationConfig => ({
-  label: defaultAnnotationLabel,
-  type: 'manual',
-  key: {
-    type: 'point_in_time',
-    timestamp,
-  },
-  icon: 'triangle',
-  id,
-});
-
-const getDefaultQueryAnnotation = (
-  id: string,
-  fieldName: string,
-  timeField: string
-): EventAnnotationConfig => ({
-  filter: {
-    type: 'kibana_query',
-    query: `${fieldName}: *`,
-    language: 'kuery',
-  },
-  timeField,
-  type: 'query',
-  key: {
-    type: 'point_in_time',
-  },
-  id,
-  label: `${fieldName}: *`,
-});
-
-const createCopiedAnnotation = (
-  newId: string,
-  timestamp: string,
-  source?: EventAnnotationConfig
-): EventAnnotationConfig => {
-  if (!source) {
-    return getDefaultManualAnnotation(newId, timestamp);
-  }
-  return {
-    ...source,
-    id: newId,
-  };
-};
-
 export const onAnnotationDrop: Visualization<XYState>['onDrop'] = ({
   prevState,
   frame,
@@ -446,26 +402,8 @@ export const setAnnotationsDimension: Visualization<XYState>['setDimension'] = (
   };
 };
 
-export const getSingleColorAnnotationConfig = (
-  annotation: EventAnnotationConfig
-): AccessorConfig => {
-  const annotationIcon = !isRangeAnnotationConfig(annotation)
-    ? annotationsIconSet.find((option) => option.value === annotation?.icon) ||
-      annotationsIconSet.find((option) => option.value === 'triangle')
-    : undefined;
-  const icon = annotationIcon?.icon ?? annotationIcon?.value;
-  return {
-    columnId: annotation.id,
-    triggerIconType: annotation.isHidden ? 'invisible' : icon ? 'custom' : 'color',
-    customIcon: icon,
-    color:
-      annotation?.color ||
-      (isRangeAnnotationConfig(annotation) ? defaultAnnotationRangeColor : defaultAnnotationColor),
-  };
-};
-
 export const getAnnotationsAccessorColorConfig = (layer: XYAnnotationLayerConfig) =>
-  layer.annotations.map((annotation) => getSingleColorAnnotationConfig(annotation));
+  layer.annotations.map((annotation) => getAnnotationAccessor(annotation));
 
 export const getAnnotationsConfiguration = ({
   state,
diff --git a/x-pack/plugins/lens/public/visualizations/xy/index.ts b/x-pack/plugins/lens/public/visualizations/xy/index.ts
index 244d47cd5b114..6f590000d512a 100644
--- a/x-pack/plugins/lens/public/visualizations/xy/index.ts
+++ b/x-pack/plugins/lens/public/visualizations/xy/index.ts
@@ -30,7 +30,15 @@ export class XyVisualization {
       const { getXyVisualization } = await import('../../async_services');
       const [
         coreStart,
-        { charts, data, fieldFormats, eventAnnotation, unifiedSearch, savedObjectsTagging },
+        {
+          charts,
+          data,
+          fieldFormats,
+          eventAnnotation,
+          unifiedSearch,
+          savedObjectsTagging,
+          dataViews,
+        },
       ] = await core.getStartServices();
       const [palettes, eventAnnotationService] = await Promise.all([
         charts.palettes.getPalettes(),
@@ -47,6 +55,7 @@ export class XyVisualization {
         useLegacyTimeAxis,
         kibanaTheme: core.theme,
         unifiedSearch,
+        dataViewsService: dataViews,
         savedObjectsTagging,
       });
     });
diff --git a/x-pack/plugins/lens/public/visualizations/xy/to_expression.test.ts b/x-pack/plugins/lens/public/visualizations/xy/to_expression.test.ts
index a1378a1442698..9cdf33c134c8c 100644
--- a/x-pack/plugins/lens/public/visualizations/xy/to_expression.test.ts
+++ b/x-pack/plugins/lens/public/visualizations/xy/to_expression.test.ts
@@ -20,6 +20,7 @@ import { LegendSize } from '@kbn/visualizations-plugin/common';
 import { dataPluginMock } from '@kbn/data-plugin/public/mocks';
 import { IStorageWrapper } from '@kbn/kibana-utils-plugin/public';
 import { unifiedSearchPluginMock } from '@kbn/unified-search-plugin/public/mocks';
+import { DataViewsServicePublic } from '@kbn/data-views-plugin/public';
 
 describe('#toExpression', () => {
   const xyVisualization = getXyVisualization({
@@ -32,6 +33,7 @@ describe('#toExpression', () => {
     storage: {} as IStorageWrapper,
     data: dataPluginMock.createStartContract(),
     unifiedSearch: unifiedSearchPluginMock.createStartContract(),
+    dataViewsService: {} as DataViewsServicePublic,
   });
   let mockDatasource: ReturnType<typeof createMockDatasource>;
   let frame: ReturnType<typeof createMockFramePublicAPI>;
diff --git a/x-pack/plugins/lens/public/visualizations/xy/visualization.test.tsx b/x-pack/plugins/lens/public/visualizations/xy/visualization.test.tsx
index 6f720f0e21f2f..bcd5ed68aa38c 100644
--- a/x-pack/plugins/lens/public/visualizations/xy/visualization.test.tsx
+++ b/x-pack/plugins/lens/public/visualizations/xy/visualization.test.tsx
@@ -52,6 +52,7 @@ import { set } from '@kbn/safer-lodash-set';
 import { SavedObjectReference } from '@kbn/core-saved-objects-api-server';
 import { getAnnotationsLayers } from './visualization_helpers';
 import { cloneDeep } from 'lodash';
+import { DataViewsServicePublic } from '@kbn/data-views-plugin/public';
 
 const DATE_HISTORGRAM_COLUMN_ID = 'date_histogram_column';
 const exampleAnnotation: EventAnnotationConfig = {
@@ -108,6 +109,7 @@ const xyVisualization = getXyVisualization({
   storage: {} as IStorageWrapper,
   data: dataPluginMock.createStartContract(),
   unifiedSearch: unifiedSearchPluginMock.createStartContract(),
+  dataViewsService: {} as DataViewsServicePublic,
 });
 
 describe('xy_visualization', () => {
diff --git a/x-pack/plugins/lens/public/visualizations/xy/visualization.tsx b/x-pack/plugins/lens/public/visualizations/xy/visualization.tsx
index d4f6d4411b5a7..cdbd189790e07 100644
--- a/x-pack/plugins/lens/public/visualizations/xy/visualization.tsx
+++ b/x-pack/plugins/lens/public/visualizations/xy/visualization.tsx
@@ -14,7 +14,10 @@ import type { PaletteRegistry } from '@kbn/coloring';
 import { IconChartBarReferenceLine, IconChartBarAnnotations } from '@kbn/chart-icons';
 import { FieldFormatsStart } from '@kbn/field-formats-plugin/public';
 import { CoreStart, SavedObjectReference, ThemeServiceStart } from '@kbn/core/public';
-import type { EventAnnotationServiceType } from '@kbn/event-annotation-plugin/public';
+import {
+  EventAnnotationServiceType,
+  getAnnotationAccessor,
+} from '@kbn/event-annotation-plugin/public';
 import { KibanaContextProvider, KibanaThemeProvider } from '@kbn/kibana-react-plugin/public';
 import { VIS_EVENT_TO_TRIGGER } from '@kbn/visualizations-plugin/public';
 import type { DataPublicPluginStart } from '@kbn/data-plugin/public';
@@ -24,7 +27,8 @@ import { LayerTypes } from '@kbn/expression-xy-plugin/public';
 import { SavedObjectTaggingPluginStart } from '@kbn/saved-objects-tagging-plugin/public';
 import { EventAnnotationGroupConfig } from '@kbn/event-annotation-plugin/common';
 import { isEqual } from 'lodash';
-import type { AccessorConfig } from '@kbn/visualization-ui-components/public';
+import { type AccessorConfig, DimensionTrigger } from '@kbn/visualization-ui-components/public';
+import { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public';
 import { generateId } from '../../id_generator';
 import {
   isDraggedDataViewField,
@@ -36,8 +40,8 @@ import {
 import { getSuggestions } from './xy_suggestions';
 import { XyToolbar } from './xy_config_panel';
 import {
+  DataDimensionEditor,
   DataDimensionEditorDataSectionExtra,
-  DimensionEditor,
 } from './xy_config_panel/dimension_editor';
 import { LayerHeader, LayerHeaderContent } from './xy_config_panel/layer_header';
 import type {
@@ -79,7 +83,6 @@ import {
   getUniqueLabels,
   onAnnotationDrop,
   isDateHistogram,
-  getSingleColorAnnotationConfig,
 } from './annotations/helpers';
 import {
   checkXAccessorCompatibility,
@@ -105,7 +108,6 @@ import { groupAxesByType } from './axes_configuration';
 import type { XYState } from './types';
 import { ReferenceLinePanel } from './xy_config_panel/reference_line_config_panel';
 import { AnnotationsPanel } from './xy_config_panel/annotations_config_panel';
-import { DimensionTrigger } from '../../shared_components/dimension_trigger';
 import { defaultAnnotationLabel } from './annotations/helpers';
 import { onDropForVisualization } from '../../editor_frame_service/editor_frame/config_panel/buttons/drop_targets_utils';
 import { createAnnotationActions } from './annotations/actions';
@@ -127,6 +129,7 @@ export const getXyVisualization = ({
   kibanaTheme,
   eventAnnotationService,
   unifiedSearch,
+  dataViewsService,
   savedObjectsTagging,
 }: {
   core: CoreStart;
@@ -138,6 +141,7 @@ export const getXyVisualization = ({
   useLegacyTimeAxis: boolean;
   kibanaTheme: ThemeServiceStart;
   unifiedSearch: UnifiedSearchPublicPluginStart;
+  dataViewsService: DataViewsPublicPluginStart;
   savedObjectsTagging?: SavedObjectTaggingPluginStart;
 }): Visualization<State, PersistedState, ExtraAppendLayerArg> => ({
   id: XY_ID,
@@ -651,9 +655,9 @@ export const getXyVisualization = ({
     const dimensionEditor = isReferenceLayer(layer) ? (
       <ReferenceLinePanel {...allProps} />
     ) : isAnnotationsLayer(layer) ? (
-      <AnnotationsPanel {...allProps} />
+      <AnnotationsPanel {...allProps} dataViewsService={dataViewsService} />
     ) : (
-      <DimensionEditor {...allProps} />
+      <DataDimensionEditor {...allProps} />
     );
 
     render(
@@ -1142,7 +1146,7 @@ function getVisualizationInfo(
       palette.push(
         ...layer.annotations
           .filter(({ isHidden }) => !isHidden)
-          .map((annotation) => getSingleColorAnnotationConfig(annotation).color)
+          .map((annotation) => getAnnotationAccessor(annotation).color)
       );
     }
 
diff --git a/x-pack/plugins/lens/public/visualizations/xy/xy_config_panel/annotations_config_panel/annotations_panel.tsx b/x-pack/plugins/lens/public/visualizations/xy/xy_config_panel/annotations_config_panel/annotations_panel.tsx
index 9cb5f4d64079e..19bbcc7f4bfc8 100644
--- a/x-pack/plugins/lens/public/visualizations/xy/xy_config_panel/annotations_config_panel/annotations_panel.tsx
+++ b/x-pack/plugins/lens/public/visualizations/xy/xy_config_panel/annotations_config_panel/annotations_panel.tsx
@@ -5,59 +5,30 @@
  * 2.0.
  */
 
-import './index.scss';
-import React, { useCallback, useEffect } from 'react';
-import { i18n } from '@kbn/i18n';
-import { EuiFormRow, EuiSwitch, EuiSwitchEvent, EuiButtonGroup, EuiSpacer } from '@elastic/eui';
-import type { PaletteRegistry } from '@kbn/coloring';
+import React, { useCallback, useEffect, useState } from 'react';
 import type { DatatableUtilitiesService } from '@kbn/data-plugin/common';
-import {
-  defaultAnnotationColor,
-  defaultAnnotationRangeColor,
-  isQueryAnnotationConfig,
-  isRangeAnnotationConfig,
-} from '@kbn/event-annotation-plugin/public';
-import {
-  EventAnnotationConfig,
-  PointInTimeEventAnnotationConfig,
-  QueryPointEventAnnotationConfig,
-} from '@kbn/event-annotation-plugin/common';
+import { AnnotationEditorControls } from '@kbn/event-annotation-plugin/public';
+import { EventAnnotationConfig } from '@kbn/event-annotation-plugin/common';
+import { useKibana } from '@kbn/kibana-react-plugin/public';
+import { useDebouncedValue } from '@kbn/visualization-ui-components/public';
+import { DataViewsPublicPluginStart, DataView } from '@kbn/data-views-plugin/public';
 import moment from 'moment';
-import { useExistingFieldsReader } from '@kbn/unified-field-list-plugin/public';
-import {
-  IconSelectSetting,
-  FieldOption,
-  FieldOptionValue,
-  FieldPicker,
-  NameInput,
-  useDebouncedValue,
-  DimensionEditorSection,
-  ColorPicker,
-} from '@kbn/visualization-ui-components/public';
-import { FormatFactory } from '../../../../../common/types';
-import { isHorizontalChart } from '../../state_helpers';
-import { defaultAnnotationLabel, defaultRangeAnnotationLabel } from '../../annotations/helpers';
-import { TextDecorationSetting } from '../shared/marker_decoration_settings';
-import { LineStyleSettings } from '../shared/line_style_settings';
+import { search } from '@kbn/data-plugin/public';
+import { LENS_APP_NAME } from '../../../../../common/constants';
+import { DONT_CLOSE_DIMENSION_CONTAINER_ON_CLICK_CLASS } from '../../../../utils';
+import { LensAppServices } from '../../../../app_plugin/types';
 import { updateLayer } from '..';
-import { annotationsIconSet } from './icon_set';
-import type { VisualizationDimensionEditorProps } from '../../../../types';
-import type { State, XYState, XYAnnotationLayerConfig } from '../../types';
-import { ConfigPanelManualAnnotation } from './manual_annotation_panel';
-import { ConfigPanelQueryAnnotation } from './query_annotation_panel';
-import { TooltipSection } from './tooltip_annotation_panel';
-import { sanitizeProperties, toLineAnnotationColor } from './helpers';
+import type { FramePublicAPI, VisualizationDimensionEditorProps } from '../../../../types';
+import type { State, XYState, XYAnnotationLayerConfig, XYDataLayerConfig } from '../../types';
+import { isDataLayer } from '../../visualization_helpers';
 
 export const AnnotationsPanel = (
   props: VisualizationDimensionEditorProps<State> & {
     datatableUtilities: DatatableUtilitiesService;
-    formatFactory: FormatFactory;
-    paletteService: PaletteRegistry;
+    dataViewsService: DataViewsPublicPluginStart;
   }
 ) => {
   const { state, setState, layerId, accessor, frame } = props;
-  const isHorizontal = isHorizontalChart(state.layers);
-  const { hasFieldData } = useExistingFieldsReader();
 
   const { inputValue: localState, handleInputChange: setLocalState } = useDebouncedValue<XYState>({
     value: state,
@@ -71,26 +42,15 @@ export const AnnotationsPanel = (
 
   const currentAnnotation = localLayer.annotations?.find((c) => c.id === accessor);
 
-  const isQueryBased = isQueryAnnotationConfig(currentAnnotation);
-  const isRange = isRangeAnnotationConfig(currentAnnotation);
-  const [queryInputShouldOpen, setQueryInputShouldOpen] = React.useState(false);
-  useEffect(() => {
-    setQueryInputShouldOpen(!isQueryBased);
-  }, [isQueryBased]);
-
-  const setAnnotations = useCallback(
-    <T extends EventAnnotationConfig>(annotation: Partial<T> | undefined) => {
+  const setAnnotation = useCallback(
+    (annotation: EventAnnotationConfig) => {
       if (annotation == null) {
         return;
       }
       const newConfigs = [...(localLayer.annotations || [])];
       const existingIndex = newConfigs.findIndex((c) => c.id === accessor);
       if (existingIndex !== -1) {
-        const existingConfig = newConfigs[existingIndex];
-        newConfigs[existingIndex] = sanitizeProperties({
-          ...existingConfig,
-          ...annotation,
-        });
+        newConfigs[existingIndex] = annotation;
       } else {
         throw new Error(
           'should never happen because annotation is created before config panel is opened'
@@ -101,305 +61,81 @@ export const AnnotationsPanel = (
     [accessor, index, localState, localLayer, setLocalState]
   );
 
-  return (
-    <>
-      <DimensionEditorSection
-        title={i18n.translate('xpack.lens.xyChart.placement', {
-          defaultMessage: 'Placement',
-        })}
-      >
-        <EuiFormRow
-          label={i18n.translate('xpack.lens.xyChart.annotationDate.placementType', {
-            defaultMessage: 'Placement type',
-          })}
-          display="rowCompressed"
-          fullWidth
-        >
-          <EuiButtonGroup
-            legend={i18n.translate('xpack.lens.xyChart.annotationDate.placementType', {
-              defaultMessage: 'Placement type',
-            })}
-            data-test-subj="lns-xyAnnotation-placementType"
-            name="placementType"
-            buttonSize="compressed"
-            options={[
-              {
-                id: `lens_xyChart_annotation_manual`,
-                label: i18n.translate('xpack.lens.xyChart.annotation.manual', {
-                  defaultMessage: 'Static date',
-                }),
-                'data-test-subj': 'lnsXY_annotation_manual',
-              },
-              {
-                id: `lens_xyChart_annotation_query`,
-                label: i18n.translate('xpack.lens.xyChart.annotation.query', {
-                  defaultMessage: 'Custom query',
-                }),
-                'data-test-subj': 'lnsXY_annotation_query',
-              },
-            ]}
-            idSelected={`lens_xyChart_annotation_${currentAnnotation?.type}`}
-            onChange={(id) => {
-              const typeFromId = id.replace(
-                'lens_xyChart_annotation_',
-                ''
-              ) as EventAnnotationConfig['type'];
-              if (currentAnnotation?.type === typeFromId) {
-                return;
-              }
-              if (typeFromId === 'query') {
-                const currentIndexPattern =
-                  frame.dataViews.indexPatterns[localLayer.indexPatternId];
-                // If coming from a range type, it requires some additional resets
-                const additionalRangeResets = isRangeAnnotationConfig(currentAnnotation)
-                  ? {
-                      label:
-                        currentAnnotation.label === defaultRangeAnnotationLabel
-                          ? defaultAnnotationLabel
-                          : currentAnnotation.label,
-                      color: toLineAnnotationColor(currentAnnotation.color),
-                    }
-                  : {};
-                return setAnnotations({
-                  type: typeFromId,
-                  timeField:
-                    (currentIndexPattern.timeFieldName ||
-                      // fallback to the first avaiable date field in the dataView
-                      currentIndexPattern.fields.find(({ type: fieldType }) => fieldType === 'date')
-                        ?.displayName) ??
-                    '',
-                  key: { type: 'point_in_time' },
-                  ...additionalRangeResets,
-                });
-              }
-              // From query to manual annotation
-              return setAnnotations<PointInTimeEventAnnotationConfig>({
-                type: typeFromId,
-                key: { type: 'point_in_time', timestamp: moment().toISOString() },
-              });
-            }}
-            isFullWidth
-          />
-        </EuiFormRow>
-        {isQueryBased ? (
-          <ConfigPanelQueryAnnotation
-            annotation={currentAnnotation}
-            onChange={setAnnotations}
-            frame={frame}
-            state={state}
-            layer={localLayer}
-            queryInputShouldOpen={queryInputShouldOpen}
-          />
-        ) : (
-          <ConfigPanelManualAnnotation
-            annotation={currentAnnotation}
-            onChange={setAnnotations}
-            datatableUtilities={props.datatableUtilities}
-            frame={frame}
-            state={state}
-          />
-        )}
-      </DimensionEditorSection>
-      <DimensionEditorSection
-        title={i18n.translate('xpack.lens.xyChart.appearance', {
-          defaultMessage: 'Appearance',
-        })}
-      >
-        <NameInput
-          value={currentAnnotation?.label || defaultAnnotationLabel}
-          defaultValue={defaultAnnotationLabel}
-          onChange={(value) => {
-            setAnnotations({ label: value });
-          }}
-        />
-        {!isRange && (
-          <>
-            <IconSelectSetting
-              setIcon={(icon) => setAnnotations({ icon })}
-              defaultIcon="triangle"
-              currentIcon={currentAnnotation?.icon}
-              customIconSet={annotationsIconSet}
-            />
-            <TextDecorationSetting
-              setConfig={setAnnotations}
-              currentConfig={{
-                axisMode: 'bottom',
-                ...currentAnnotation,
-              }}
-              isQueryBased={isQueryBased}
-            >
-              {(textDecorationSelected) => {
-                if (textDecorationSelected !== 'field') {
-                  return null;
-                }
-                const currentIndexPattern =
-                  frame.dataViews.indexPatterns[localLayer.indexPatternId];
-                const options = currentIndexPattern.fields
-                  .filter(({ displayName, type }) => displayName && type !== 'document')
-                  .map(
-                    (field) =>
-                      ({
-                        label: field.displayName,
-                        value: {
-                          type: 'field',
-                          field: field.name,
-                          dataType: field.type,
-                        },
-                        exists: hasFieldData(currentIndexPattern.id, field.name),
-                        compatible: true,
-                        'data-test-subj': `lnsXY-annotation-fieldOption-${field.name}`,
-                      } as FieldOption<FieldOptionValue>)
-                  );
-                const selectedField = (currentAnnotation as QueryPointEventAnnotationConfig)
-                  .textField;
+  const [currentDataView, setCurrentDataView] = useState<DataView>();
+
+  useEffect(() => {
+    const updateDataView = async () => {
+      let dataView: DataView;
+      const availableIds = await props.dataViewsService.getIds();
+      if (availableIds.includes(localLayer.indexPatternId)) {
+        dataView = await props.dataViewsService.get(localLayer.indexPatternId);
+      } else {
+        dataView = await props.dataViewsService.create(
+          frame.dataViews.indexPatterns[localLayer.indexPatternId].spec
+        );
+      }
+      setCurrentDataView(dataView);
+    };
+
+    updateDataView();
+  }, [frame.dataViews.indexPatterns, localLayer.indexPatternId, props.dataViewsService]);
 
-                const fieldIsValid = selectedField
-                  ? Boolean(currentIndexPattern.getFieldByName(selectedField))
-                  : true;
-                return (
-                  <>
-                    <EuiSpacer size="xs" />
-                    <FieldPicker
-                      selectedOptions={
-                        selectedField
-                          ? [
-                              {
-                                label: selectedField,
-                                value: { type: 'field', field: selectedField },
-                              },
-                            ]
-                          : []
-                      }
-                      options={options}
-                      onChoose={function (choice: FieldOptionValue | undefined): void {
-                        if (choice) {
-                          setAnnotations({ textField: choice.field, textVisibility: true });
-                        }
-                      }}
-                      fieldIsInvalid={!fieldIsValid}
-                      data-test-subj="lnsXY-annotation-query-based-text-decoration-field-picker"
-                      autoFocus={!selectedField}
-                    />
-                  </>
-                );
-              }}
-            </TextDecorationSetting>
-            <LineStyleSettings
-              isHorizontal={isHorizontal}
-              setConfig={setAnnotations}
-              currentConfig={currentAnnotation}
-            />
-          </>
-        )}
-        {isRange && (
-          <EuiFormRow
-            label={i18n.translate('xpack.lens.xyChart.fillStyle', {
-              defaultMessage: 'Fill',
-            })}
-            display="columnCompressed"
-            fullWidth
-          >
-            <EuiButtonGroup
-              legend={i18n.translate('xpack.lens.xyChart.fillStyle', {
-                defaultMessage: 'Fill',
-              })}
-              data-test-subj="lns-xyAnnotation-fillStyle"
-              name="fillStyle"
-              buttonSize="compressed"
-              options={[
-                {
-                  id: `lens_xyChart_fillStyle_inside`,
-                  label: i18n.translate('xpack.lens.xyChart.fillStyle.inside', {
-                    defaultMessage: 'Inside',
-                  }),
-                  'data-test-subj': 'lnsXY_fillStyle_inside',
-                },
-                {
-                  id: `lens_xyChart_fillStyle_outside`,
-                  label: i18n.translate('xpack.lens.xyChart.fillStyle.outside', {
-                    defaultMessage: 'Outside',
-                  }),
-                  'data-test-subj': 'lnsXY_fillStyle_inside',
-                },
-              ]}
-              idSelected={`lens_xyChart_fillStyle_${
-                Boolean(currentAnnotation?.outside) ? 'outside' : 'inside'
-              }`}
-              onChange={(id) => {
-                setAnnotations({
-                  outside: id === `lens_xyChart_fillStyle_outside`,
-                });
-              }}
-              isFullWidth
-            />
-          </EuiFormRow>
-        )}
+  const queryInputServices = useKibana<LensAppServices>().services;
 
-        <ColorPicker
-          {...props}
-          overwriteColor={currentAnnotation?.color}
-          defaultColor={isRange ? defaultAnnotationRangeColor : defaultAnnotationColor}
-          showAlpha={isRange}
-          setConfig={setAnnotations}
-          disableHelpTooltip
-          label={i18n.translate('xpack.lens.xyChart.lineColor.label', {
-            defaultMessage: 'Color',
-          })}
-        />
-        <ConfigPanelGenericSwitch
-          label={i18n.translate('xpack.lens.xyChart.annotation.hide', {
-            defaultMessage: 'Hide annotation',
-          })}
-          data-test-subj="lns-annotations-hide-annotation"
-          value={Boolean(currentAnnotation?.isHidden)}
-          onChange={(ev) => setAnnotations({ isHidden: ev.target.checked })}
-        />
-      </DimensionEditorSection>
-      {isQueryBased && currentAnnotation && (
-        <DimensionEditorSection
-          title={i18n.translate('xpack.lens.xyChart.tooltip', {
-            defaultMessage: 'Tooltip',
-          })}
-        >
-          <EuiFormRow
-            display="rowCompressed"
-            className="lnsRowCompressedMargin"
-            fullWidth
-            label={i18n.translate('xpack.lens.xyChart.annotation.tooltip', {
-              defaultMessage: 'Show additional fields',
-            })}
-          >
-            <TooltipSection
-              currentConfig={currentAnnotation}
-              setConfig={setAnnotations}
-              indexPattern={frame.dataViews.indexPatterns[localLayer.indexPatternId]}
-            />
-          </EuiFormRow>
-        </DimensionEditorSection>
-      )}
-    </>
-  );
-};
+  if (!currentAnnotation) {
+    throw new Error('Annotation not found... this should never happen!');
+  }
 
-const ConfigPanelGenericSwitch = ({
-  label,
-  ['data-test-subj']: dataTestSubj,
-  value,
-  onChange,
-}: {
-  label: string;
-  'data-test-subj': string;
-  value: boolean;
-  onChange: (event: EuiSwitchEvent) => void;
-}) => (
-  <EuiFormRow label={label} display="columnCompressedSwitch" fullWidth>
-    <EuiSwitch
-      compressed
-      label={label}
-      showLabel={false}
-      data-test-subj={dataTestSubj}
-      checked={value}
-      onChange={onChange}
+  return currentDataView ? (
+    <AnnotationEditorControls
+      annotation={currentAnnotation}
+      onAnnotationChange={(newAnnotation) => setAnnotation(newAnnotation)}
+      dataView={currentDataView}
+      getDefaultRangeEnd={(rangeStart) =>
+        getEndTimestamp(
+          props.datatableUtilities,
+          rangeStart,
+          frame,
+          localState.layers.filter(isDataLayer)
+        )
+      }
+      queryInputServices={queryInputServices}
+      calendarClassName={DONT_CLOSE_DIMENSION_CONTAINER_ON_CLICK_CLASS}
+      appName={LENS_APP_NAME}
     />
-  </EuiFormRow>
-);
+  ) : null;
+};
+
+const getEndTimestamp = (
+  datatableUtilities: DatatableUtilitiesService,
+  startTime: string,
+  { activeData, dateRange }: FramePublicAPI,
+  dataLayers: XYDataLayerConfig[]
+) => {
+  const startTimeNumber = moment(startTime).valueOf();
+  const dateRangeFraction =
+    (moment(dateRange.toDate).valueOf() - moment(dateRange.fromDate).valueOf()) * 0.1;
+  const fallbackValue = moment(startTimeNumber + dateRangeFraction).toISOString();
+  const dataLayersId = dataLayers.map(({ layerId }) => layerId);
+  if (
+    !dataLayersId.length ||
+    !activeData ||
+    Object.entries(activeData)
+      .filter(([key]) => dataLayersId.includes(key))
+      .every(([, { rows }]) => !rows || !rows.length)
+  ) {
+    return fallbackValue;
+  }
+  const xColumn = activeData?.[dataLayersId[0]].columns.find(
+    (column) => column.id === dataLayers[0].xAccessor
+  );
+  if (!xColumn) {
+    return fallbackValue;
+  }
+
+  const dateInterval = datatableUtilities.getDateHistogramMeta(xColumn)?.interval;
+  if (!dateInterval) return fallbackValue;
+  const intervalDuration = search.aggs.parseInterval(dateInterval);
+  if (!intervalDuration) return fallbackValue;
+  return moment(startTimeNumber + 3 * intervalDuration.as('milliseconds')).toISOString();
+};
diff --git a/x-pack/plugins/lens/public/visualizations/xy/xy_config_panel/annotations_config_panel/helpers.ts b/x-pack/plugins/lens/public/visualizations/xy/xy_config_panel/annotations_config_panel/helpers.ts
deleted file mode 100644
index 0eb80c1018dc2..0000000000000
--- a/x-pack/plugins/lens/public/visualizations/xy/xy_config_panel/annotations_config_panel/helpers.ts
+++ /dev/null
@@ -1,116 +0,0 @@
-/*
- * 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; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import { search } from '@kbn/data-plugin/public';
-import { transparentize } from '@elastic/eui';
-import type { DatatableUtilitiesService } from '@kbn/data-plugin/common';
-import type {
-  EventAnnotationConfig,
-  RangeEventAnnotationConfig,
-  PointInTimeEventAnnotationConfig,
-  QueryPointEventAnnotationConfig,
-} from '@kbn/event-annotation-plugin/common';
-import {
-  defaultAnnotationColor,
-  defaultAnnotationRangeColor,
-  isQueryAnnotationConfig,
-  isRangeAnnotationConfig,
-} from '@kbn/event-annotation-plugin/public';
-import chroma from 'chroma-js';
-import { pick } from 'lodash';
-import moment from 'moment';
-import type { FramePublicAPI } from '../../../../types';
-import type { XYDataLayerConfig } from '../../types';
-
-export const toRangeAnnotationColor = (color = defaultAnnotationColor) => {
-  return chroma(transparentize(color, 0.1)).hex().toUpperCase();
-};
-
-export const toLineAnnotationColor = (color = defaultAnnotationRangeColor) => {
-  return chroma(transparentize(color, 1)).hex().toUpperCase();
-};
-
-export const getEndTimestamp = (
-  datatableUtilities: DatatableUtilitiesService,
-  startTime: string,
-  { activeData, dateRange }: FramePublicAPI,
-  dataLayers: XYDataLayerConfig[]
-) => {
-  const startTimeNumber = moment(startTime).valueOf();
-  const dateRangeFraction =
-    (moment(dateRange.toDate).valueOf() - moment(dateRange.fromDate).valueOf()) * 0.1;
-  const fallbackValue = moment(startTimeNumber + dateRangeFraction).toISOString();
-  const dataLayersId = dataLayers.map(({ layerId }) => layerId);
-  if (
-    !dataLayersId.length ||
-    !activeData ||
-    Object.entries(activeData)
-      .filter(([key]) => dataLayersId.includes(key))
-      .every(([, { rows }]) => !rows || !rows.length)
-  ) {
-    return fallbackValue;
-  }
-  const xColumn = activeData?.[dataLayersId[0]].columns.find(
-    (column) => column.id === dataLayers[0].xAccessor
-  );
-  if (!xColumn) {
-    return fallbackValue;
-  }
-
-  const dateInterval = datatableUtilities.getDateHistogramMeta(xColumn)?.interval;
-  if (!dateInterval) return fallbackValue;
-  const intervalDuration = search.aggs.parseInterval(dateInterval);
-  if (!intervalDuration) return fallbackValue;
-  return moment(startTimeNumber + 3 * intervalDuration.as('milliseconds')).toISOString();
-};
-
-export const sanitizeProperties = (annotation: EventAnnotationConfig) => {
-  if (isRangeAnnotationConfig(annotation)) {
-    const rangeAnnotation: RangeEventAnnotationConfig = pick(annotation, [
-      'type',
-      'label',
-      'key',
-      'id',
-      'isHidden',
-      'color',
-      'outside',
-    ]);
-    return rangeAnnotation;
-  }
-  if (isQueryAnnotationConfig(annotation)) {
-    const lineAnnotation: QueryPointEventAnnotationConfig = pick(annotation, [
-      'type',
-      'id',
-      'label',
-      'key',
-      'timeField',
-      'isHidden',
-      'lineStyle',
-      'lineWidth',
-      'color',
-      'icon',
-      'textVisibility',
-      'textField',
-      'filter',
-      'extraFields',
-    ]);
-    return lineAnnotation;
-  }
-  const lineAnnotation: PointInTimeEventAnnotationConfig = pick(annotation, [
-    'type',
-    'id',
-    'label',
-    'key',
-    'isHidden',
-    'lineStyle',
-    'lineWidth',
-    'color',
-    'icon',
-    'textVisibility',
-  ]);
-  return lineAnnotation;
-};
diff --git a/x-pack/plugins/lens/public/visualizations/xy/xy_config_panel/annotations_config_panel/icon_set.ts b/x-pack/plugins/lens/public/visualizations/xy/xy_config_panel/annotations_config_panel/icon_set.ts
deleted file mode 100644
index 3e67cd8e5c14e..0000000000000
--- a/x-pack/plugins/lens/public/visualizations/xy/xy_config_panel/annotations_config_panel/icon_set.ts
+++ /dev/null
@@ -1,106 +0,0 @@
-/*
- * 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; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import { i18n } from '@kbn/i18n';
-import { AvailableAnnotationIcon } from '@kbn/event-annotation-plugin/common';
-import { IconTriangle, IconCircle } from '@kbn/chart-icons';
-import { type IconSet } from '@kbn/visualization-ui-components/public';
-
-export const annotationsIconSet: IconSet<AvailableAnnotationIcon> = [
-  {
-    value: 'asterisk',
-    label: i18n.translate('xpack.lens.xyChart.iconSelect.asteriskIconLabel', {
-      defaultMessage: 'Asterisk',
-    }),
-  },
-  {
-    value: 'alert',
-    label: i18n.translate('xpack.lens.xyChart.iconSelect.alertIconLabel', {
-      defaultMessage: 'Alert',
-    }),
-  },
-  {
-    value: 'bell',
-    label: i18n.translate('xpack.lens.xyChart.iconSelect.bellIconLabel', {
-      defaultMessage: 'Bell',
-    }),
-  },
-  {
-    value: 'bolt',
-    label: i18n.translate('xpack.lens.xyChart.iconSelect.boltIconLabel', {
-      defaultMessage: 'Bolt',
-    }),
-  },
-  {
-    value: 'bug',
-    label: i18n.translate('xpack.lens.xyChart.iconSelect.bugIconLabel', {
-      defaultMessage: 'Bug',
-    }),
-  },
-  {
-    value: 'circle',
-    label: i18n.translate('xpack.lens.xyChart.iconSelect.circleIconLabel', {
-      defaultMessage: 'Circle',
-    }),
-    icon: IconCircle,
-    canFill: true,
-  },
-
-  {
-    value: 'editorComment',
-    label: i18n.translate('xpack.lens.xyChart.iconSelect.commentIconLabel', {
-      defaultMessage: 'Comment',
-    }),
-  },
-  {
-    value: 'flag',
-    label: i18n.translate('xpack.lens.xyChart.iconSelect.flagIconLabel', {
-      defaultMessage: 'Flag',
-    }),
-  },
-  {
-    value: 'heart',
-    label: i18n.translate('xpack.lens.xyChart.iconSelect.heartLabel', { defaultMessage: 'Heart' }),
-  },
-  {
-    value: 'mapMarker',
-    label: i18n.translate('xpack.lens.xyChart.iconSelect.mapMarkerLabel', {
-      defaultMessage: 'Map Marker',
-    }),
-  },
-  {
-    value: 'pinFilled',
-    label: i18n.translate('xpack.lens.xyChart.iconSelect.mapPinLabel', {
-      defaultMessage: 'Map Pin',
-    }),
-  },
-  {
-    value: 'starEmpty',
-    label: i18n.translate('xpack.lens.xyChart.iconSelect.starLabel', { defaultMessage: 'Star' }),
-  },
-  {
-    value: 'starFilled',
-    label: i18n.translate('xpack.lens.xyChart.iconSelect.starFilledLabel', {
-      defaultMessage: 'Star filled',
-    }),
-  },
-  {
-    value: 'tag',
-    label: i18n.translate('xpack.lens.xyChart.iconSelect.tagIconLabel', {
-      defaultMessage: 'Tag',
-    }),
-  },
-  {
-    value: 'triangle',
-    label: i18n.translate('xpack.lens.xyChart.iconSelect.triangleIconLabel', {
-      defaultMessage: 'Triangle',
-    }),
-    icon: IconTriangle,
-    shouldRotate: true,
-    canFill: true,
-  },
-];
diff --git a/x-pack/plugins/lens/public/visualizations/xy/xy_config_panel/annotations_config_panel/index.test.tsx b/x-pack/plugins/lens/public/visualizations/xy/xy_config_panel/annotations_config_panel/index.test.tsx
deleted file mode 100644
index 92fa248979fc3..0000000000000
--- a/x-pack/plugins/lens/public/visualizations/xy/xy_config_panel/annotations_config_panel/index.test.tsx
+++ /dev/null
@@ -1,668 +0,0 @@
-/*
- * 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; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import React from 'react';
-import { mountWithIntl as mount } from '@kbn/test-jest-helpers';
-import { createDatatableUtilitiesMock } from '@kbn/data-plugin/common/mocks';
-import { LayerTypes } from '@kbn/expression-xy-plugin/public';
-import { AnnotationsPanel } from '.';
-import { FramePublicAPI } from '../../../../types';
-import { DatasourcePublicAPI } from '../../../..';
-import { createMockFramePublicAPI } from '../../../../mocks';
-import { State, XYState } from '../../types';
-import { Position } from '@elastic/charts';
-import { chartPluginMock } from '@kbn/charts-plugin/public/mocks';
-import moment from 'moment';
-import { EventAnnotationConfig } from '@kbn/event-annotation-plugin/common';
-import { createMockDataViewsState } from '../../../../data_views_service/mocks';
-import { createMockedIndexPattern } from '../../../../datasources/form_based/mocks';
-import { act } from 'react-dom/test-utils';
-import { EuiButtonGroup } from '@elastic/eui';
-
-jest.mock('lodash', () => {
-  const original = jest.requireActual('lodash');
-
-  return {
-    ...original,
-    debounce: (fn: unknown) => fn,
-  };
-});
-
-jest.mock('@kbn/unified-search-plugin/public', () => ({
-  QueryStringInput: () => {
-    return 'QueryStringInput';
-  },
-}));
-
-const customLineStaticAnnotation: EventAnnotationConfig = {
-  id: 'ann1',
-  type: 'manual',
-  key: { type: 'point_in_time' as const, timestamp: '2022-03-18T08:25:00.000Z' },
-  label: 'Event',
-  icon: 'triangle' as const,
-  color: 'red',
-  lineStyle: 'dashed' as const,
-  lineWidth: 3,
-};
-
-describe('AnnotationsPanel', () => {
-  const datatableUtilities = createDatatableUtilitiesMock();
-  let frame: FramePublicAPI;
-
-  function testState(): State {
-    return {
-      legend: { isVisible: true, position: Position.Right },
-      valueLabels: 'hide',
-      preferredSeriesType: 'bar',
-      layers: [
-        {
-          layerType: LayerTypes.ANNOTATIONS,
-          layerId: 'annotation',
-          indexPatternId: 'indexPattern1',
-          annotations: [customLineStaticAnnotation],
-          ignoreGlobalFilters: true,
-        },
-      ],
-    };
-  }
-
-  beforeEach(() => {
-    frame = createMockFramePublicAPI({ datasourceLayers: {} });
-  });
-
-  describe('Dimension Editor', () => {
-    test('shows correct options for line annotations', () => {
-      const state = testState();
-      const component = mount(
-        <AnnotationsPanel
-          layerId={state.layers[0].layerId}
-          frame={frame}
-          setState={jest.fn()}
-          accessor="ann1"
-          groupId="left"
-          state={state}
-          datatableUtilities={datatableUtilities}
-          formatFactory={jest.fn()}
-          paletteService={chartPluginMock.createPaletteRegistry()}
-          panelRef={React.createRef()}
-          addLayer={jest.fn()}
-          removeLayer={jest.fn()}
-          datasource={{} as DatasourcePublicAPI}
-        />
-      );
-
-      expect(
-        component.find('EuiDatePicker[data-test-subj="lns-xyAnnotation-time"]').prop('selected')
-      ).toEqual(moment('2022-03-18T08:25:00.000Z'));
-      expect(
-        component.find('EuiDatePicker[data-test-subj="lns-xyAnnotation-fromTime"]').exists()
-      ).toBeFalsy();
-      expect(
-        component.find('EuiDatePicker[data-test-subj="lns-xyAnnotation-toTime"]').exists()
-      ).toBeFalsy();
-      expect(
-        component.find('EuiSwitch[data-test-subj="lns-xyAnnotation-rangeSwitch"]').prop('checked')
-      ).toEqual(false);
-      expect(
-        component.find('EuiFieldText[data-test-subj="column-label-edit"]').prop('value')
-      ).toEqual('Event');
-      expect(
-        component.find('EuiComboBox[data-test-subj="lns-icon-select"]').prop('selectedOptions')
-      ).toEqual([{ label: 'Triangle', value: 'triangle' }]);
-      expect(component.find('TextDecorationSetting').exists()).toBeTruthy();
-      expect(component.find('LineStyleSettings').exists()).toBeTruthy();
-      expect(
-        component.find('EuiButtonGroup[data-test-subj="lns-xyAnnotation-fillStyle"]').exists()
-      ).toBeFalsy();
-    });
-    test('shows correct options for range annotations', () => {
-      const state = testState();
-      state.layers[0] = {
-        annotations: [
-          {
-            color: 'red',
-            icon: 'triangle',
-            id: 'ann1',
-            type: 'manual',
-            isHidden: undefined,
-            key: {
-              endTimestamp: '2022-03-21T10:49:00.000Z',
-              timestamp: '2022-03-18T08:25:00.000Z',
-              type: 'range',
-            },
-            label: 'Event range',
-            lineStyle: 'dashed',
-            lineWidth: 3,
-          },
-        ],
-        layerId: 'annotation',
-        layerType: 'annotations',
-        indexPatternId: 'indexPattern1',
-        ignoreGlobalFilters: true,
-      };
-      const component = mount(
-        <AnnotationsPanel
-          layerId={state.layers[0].layerId}
-          frame={frame}
-          setState={jest.fn()}
-          accessor="ann1"
-          groupId="left"
-          state={state}
-          datatableUtilities={datatableUtilities}
-          formatFactory={jest.fn()}
-          paletteService={chartPluginMock.createPaletteRegistry()}
-          panelRef={React.createRef()}
-          addLayer={jest.fn()}
-          removeLayer={jest.fn()}
-          datasource={{} as DatasourcePublicAPI}
-        />
-      );
-
-      expect(
-        component.find('EuiDatePicker[data-test-subj="lns-xyAnnotation-fromTime"]').prop('selected')
-      ).toEqual(moment('2022-03-18T08:25:00.000Z'));
-      expect(
-        component.find('EuiDatePicker[data-test-subj="lns-xyAnnotation-toTime"]').prop('selected')
-      ).toEqual(moment('2022-03-21T10:49:00.000Z'));
-      expect(
-        component.find('EuiDatePicker[data-test-subj="lns-xyAnnotation-time"]').exists()
-      ).toBeFalsy();
-      expect(
-        component.find('EuiSwitch[data-test-subj="lns-xyAnnotation-rangeSwitch"]').prop('checked')
-      ).toEqual(true);
-      expect(
-        component.find('EuiFieldText[data-test-subj="column-label-edit"]').prop('value')
-      ).toEqual('Event range');
-      expect(component.find('EuiComboBox[data-test-subj="lns-icon-select"]').exists()).toBeFalsy();
-      expect(component.find('TextDecorationSetting').exists()).toBeFalsy();
-      expect(component.find('LineStyleSettings').exists()).toBeFalsy();
-      expect(component.find('[data-test-subj="lns-xyAnnotation-fillStyle"]').exists()).toBeTruthy();
-    });
-
-    test('calculates correct endTimstamp and transparent color when switching for range annotation and back', () => {
-      const state = testState();
-      const setState = jest.fn();
-      const component = mount(
-        <AnnotationsPanel
-          layerId={state.layers[0].layerId}
-          frame={frame}
-          setState={setState}
-          accessor="ann1"
-          groupId="left"
-          state={state}
-          datatableUtilities={datatableUtilities}
-          formatFactory={jest.fn()}
-          paletteService={chartPluginMock.createPaletteRegistry()}
-          panelRef={React.createRef()}
-          addLayer={jest.fn()}
-          removeLayer={jest.fn()}
-          datasource={{} as DatasourcePublicAPI}
-        />
-      );
-
-      component.find('button[data-test-subj="lns-xyAnnotation-rangeSwitch"]').simulate('click');
-
-      expect(setState).toBeCalledWith<XYState[]>({
-        ...state,
-        layers: [
-          {
-            annotations: [
-              {
-                color: '#FF00001A',
-                id: 'ann1',
-                isHidden: undefined,
-                label: 'Event range',
-                type: 'manual',
-                key: {
-                  endTimestamp: '2022-03-21T10:49:00.000Z',
-                  timestamp: '2022-03-18T08:25:00.000Z',
-                  type: 'range',
-                },
-              },
-            ],
-            indexPatternId: 'indexPattern1',
-            layerId: 'annotation',
-            layerType: 'annotations',
-            ignoreGlobalFilters: true,
-          },
-        ],
-      });
-      component.find('button[data-test-subj="lns-xyAnnotation-rangeSwitch"]').simulate('click');
-      expect(setState).toBeCalledWith<XYState[]>({
-        ...state,
-        layers: [
-          {
-            annotations: [
-              {
-                color: '#FF0000',
-                id: 'ann1',
-                isHidden: undefined,
-                key: {
-                  timestamp: '2022-03-18T08:25:00.000Z',
-                  type: 'point_in_time',
-                },
-                label: 'Event',
-                type: 'manual',
-              },
-            ],
-            indexPatternId: 'indexPattern1',
-            layerId: 'annotation',
-            layerType: 'annotations',
-            ignoreGlobalFilters: true,
-          },
-        ],
-      });
-    });
-
-    test('shows correct options for query based', () => {
-      const state = testState();
-      const indexPattern = createMockedIndexPattern();
-      state.layers[0] = {
-        annotations: [
-          {
-            color: 'red',
-            icon: 'triangle',
-            id: 'ann1',
-            type: 'query',
-            isHidden: undefined,
-            timeField: 'timestamp',
-            key: {
-              type: 'point_in_time',
-            },
-            label: 'Query based event',
-            lineStyle: 'dashed',
-            lineWidth: 3,
-            filter: { type: 'kibana_query', query: '', language: 'kuery' },
-          },
-        ],
-        layerId: 'annotation',
-        layerType: 'annotations',
-        indexPatternId: indexPattern.id,
-        ignoreGlobalFilters: true,
-      };
-      const frameMock = createMockFramePublicAPI({
-        datasourceLayers: {},
-        dataViews: createMockDataViewsState({
-          indexPatterns: { [indexPattern.id]: indexPattern },
-        }),
-      });
-
-      const component = mount(
-        <AnnotationsPanel
-          layerId={state.layers[0].layerId}
-          frame={frameMock}
-          setState={jest.fn()}
-          accessor="ann1"
-          groupId="left"
-          state={state}
-          datatableUtilities={datatableUtilities}
-          formatFactory={jest.fn()}
-          paletteService={chartPluginMock.createPaletteRegistry()}
-          panelRef={React.createRef()}
-          addLayer={jest.fn()}
-          removeLayer={jest.fn()}
-          datasource={{} as DatasourcePublicAPI}
-        />
-      );
-
-      expect(
-        component.find('[data-test-subj="lnsXY-annotation-query-based-field-picker"]').exists()
-      ).toBeTruthy();
-      expect(
-        component.find('[data-test-subj="lnsXY-annotation-query-based-query-input"]').exists()
-      ).toBeTruthy();
-
-      // The provided indexPattern has 2 date fields
-      expect(
-        component
-          .find('[data-test-subj="lnsXY-annotation-query-based-field-picker"]')
-          .at(0)
-          .prop('options')
-      ).toHaveLength(2);
-      // When in query mode a new "field" option is added to the previous 2 ones
-      expect(
-        component.find('[data-test-subj="lns-lineMarker-text-visibility"]').at(0).prop('options')
-      ).toHaveLength(3);
-      expect(
-        component.find('[data-test-subj="lnsXY-annotation-tooltip-add_field"]').exists()
-      ).toBeTruthy();
-    });
-
-    test('should prefill timeField with the default time field when switching to query based annotations', () => {
-      const state = testState();
-      const indexPattern = createMockedIndexPattern();
-      state.layers[0] = {
-        annotations: [customLineStaticAnnotation],
-        layerId: 'annotation',
-        layerType: 'annotations',
-        ignoreGlobalFilters: true,
-        indexPatternId: indexPattern.id,
-      };
-      const frameMock = createMockFramePublicAPI({
-        datasourceLayers: {},
-        dataViews: createMockDataViewsState({
-          indexPatterns: { [indexPattern.id]: indexPattern },
-        }),
-      });
-
-      const setState = jest.fn();
-
-      const component = mount(
-        <AnnotationsPanel
-          layerId={state.layers[0].layerId}
-          frame={frameMock}
-          setState={setState}
-          accessor="ann1"
-          groupId="left"
-          state={state}
-          datatableUtilities={datatableUtilities}
-          formatFactory={jest.fn()}
-          paletteService={chartPluginMock.createPaletteRegistry()}
-          panelRef={React.createRef()}
-          addLayer={jest.fn()}
-          removeLayer={jest.fn()}
-          datasource={{} as DatasourcePublicAPI}
-        />
-      );
-
-      act(() => {
-        component
-          .find(`[data-test-subj="lns-xyAnnotation-placementType"]`)
-          .find(EuiButtonGroup)
-          .prop('onChange')!('lens_xyChart_annotation_query');
-      });
-      component.update();
-
-      expect(setState).toHaveBeenCalledWith(
-        expect.objectContaining({
-          layers: [
-            expect.objectContaining({
-              annotations: [expect.objectContaining({ timeField: 'timestamp' })],
-            }),
-          ],
-        })
-      );
-    });
-
-    test('should avoid to retain specific manual configurations when switching to query based annotations', () => {
-      const state = testState();
-      const indexPattern = createMockedIndexPattern();
-      state.layers[0] = {
-        annotations: [customLineStaticAnnotation],
-        layerId: 'annotation',
-        layerType: 'annotations',
-        ignoreGlobalFilters: true,
-        indexPatternId: indexPattern.id,
-      };
-      const frameMock = createMockFramePublicAPI({
-        datasourceLayers: {},
-        dataViews: createMockDataViewsState({
-          indexPatterns: { [indexPattern.id]: indexPattern },
-        }),
-      });
-
-      const setState = jest.fn();
-
-      const component = mount(
-        <AnnotationsPanel
-          layerId={state.layers[0].layerId}
-          frame={frameMock}
-          setState={setState}
-          accessor="ann1"
-          groupId="left"
-          state={state}
-          datatableUtilities={datatableUtilities}
-          formatFactory={jest.fn()}
-          paletteService={chartPluginMock.createPaletteRegistry()}
-          panelRef={React.createRef()}
-          addLayer={jest.fn()}
-          removeLayer={jest.fn()}
-          datasource={{} as DatasourcePublicAPI}
-        />
-      );
-
-      act(() => {
-        component
-          .find(`[data-test-subj="lns-xyAnnotation-placementType"]`)
-          .find(EuiButtonGroup)
-          .prop('onChange')!('lens_xyChart_annotation_query');
-      });
-      component.update();
-
-      expect(setState).toHaveBeenCalledWith(
-        expect.objectContaining({
-          layers: [
-            expect.objectContaining({
-              annotations: [
-                expect.objectContaining({
-                  key: expect.not.objectContaining({ timestamp: expect.any('string') }),
-                }),
-              ],
-            }),
-          ],
-        })
-      );
-    });
-
-    test('should avoid to retain range manual configurations when switching to query based annotations', () => {
-      const state = testState();
-      const indexPattern = createMockedIndexPattern();
-      state.layers[0] = {
-        annotations: [
-          {
-            color: 'red',
-            icon: 'triangle',
-            id: 'ann1',
-            type: 'manual',
-            isHidden: undefined,
-            key: {
-              endTimestamp: '2022-03-21T10:49:00.000Z',
-              timestamp: '2022-03-18T08:25:00.000Z',
-              type: 'range',
-            },
-            label: 'Event range',
-            lineStyle: 'dashed',
-            lineWidth: 3,
-          },
-        ],
-        layerId: 'annotation',
-        layerType: 'annotations',
-        ignoreGlobalFilters: true,
-        indexPatternId: indexPattern.id,
-      };
-      const frameMock = createMockFramePublicAPI({
-        datasourceLayers: {},
-        dataViews: createMockDataViewsState({
-          indexPatterns: { [indexPattern.id]: indexPattern },
-        }),
-      });
-
-      const setState = jest.fn();
-
-      const component = mount(
-        <AnnotationsPanel
-          layerId={state.layers[0].layerId}
-          frame={frameMock}
-          setState={setState}
-          accessor="ann1"
-          groupId="left"
-          state={state}
-          datatableUtilities={datatableUtilities}
-          formatFactory={jest.fn()}
-          paletteService={chartPluginMock.createPaletteRegistry()}
-          panelRef={React.createRef()}
-          addLayer={jest.fn()}
-          removeLayer={jest.fn()}
-          datasource={{} as DatasourcePublicAPI}
-        />
-      );
-
-      act(() => {
-        component
-          .find(`[data-test-subj="lns-xyAnnotation-placementType"]`)
-          .find(EuiButtonGroup)
-          .prop('onChange')!('lens_xyChart_annotation_query');
-      });
-      component.update();
-
-      expect(setState).toHaveBeenCalledWith(
-        expect.objectContaining({
-          layers: [
-            expect.objectContaining({
-              annotations: [
-                expect.objectContaining({ label: expect.not.stringContaining('Event range') }),
-              ],
-            }),
-          ],
-        })
-      );
-    });
-
-    test('should set a default tiemstamp when switching from query based to manual annotations', () => {
-      const state = testState();
-      const indexPattern = createMockedIndexPattern();
-      state.layers[0] = {
-        annotations: [
-          {
-            color: 'red',
-            icon: 'triangle',
-            id: 'ann1',
-            type: 'query',
-            isHidden: undefined,
-            timeField: 'timestamp',
-            key: {
-              type: 'point_in_time',
-            },
-            label: 'Query based event',
-            lineStyle: 'dashed',
-            lineWidth: 3,
-            filter: { type: 'kibana_query', query: '', language: 'kuery' },
-          },
-        ],
-        layerId: 'annotation',
-        layerType: 'annotations',
-        indexPatternId: indexPattern.id,
-        ignoreGlobalFilters: true,
-      };
-      const frameMock = createMockFramePublicAPI({
-        datasourceLayers: {},
-        dataViews: createMockDataViewsState({
-          indexPatterns: { [indexPattern.id]: indexPattern },
-        }),
-      });
-
-      const setState = jest.fn();
-
-      const component = mount(
-        <AnnotationsPanel
-          layerId={state.layers[0].layerId}
-          frame={frameMock}
-          setState={setState}
-          accessor="ann1"
-          groupId="left"
-          state={state}
-          datatableUtilities={datatableUtilities}
-          formatFactory={jest.fn()}
-          paletteService={chartPluginMock.createPaletteRegistry()}
-          panelRef={React.createRef()}
-          addLayer={jest.fn()}
-          removeLayer={jest.fn()}
-          datasource={{} as DatasourcePublicAPI}
-        />
-      );
-
-      act(() => {
-        component
-          .find(`[data-test-subj="lns-xyAnnotation-placementType"]`)
-          .find(EuiButtonGroup)
-          .prop('onChange')!('lens_xyChart_annotation_manual');
-      });
-      component.update();
-
-      expect(setState).toHaveBeenCalledWith(
-        expect.objectContaining({
-          layers: [
-            expect.objectContaining({
-              annotations: [
-                expect.objectContaining({
-                  key: { type: 'point_in_time', timestamp: expect.any(String) },
-                }),
-              ],
-            }),
-          ],
-        })
-      );
-
-      // also check query specific props are not carried over
-      expect(setState).toHaveBeenCalledWith(
-        expect.objectContaining({
-          layers: [
-            expect.objectContaining({
-              annotations: [expect.not.objectContaining({ timeField: 'timestamp' })],
-            }),
-          ],
-        })
-      );
-    });
-
-    test('should fallback to the first date field available in the dataView if not time-based', () => {
-      const state = testState();
-      const indexPattern = createMockedIndexPattern({ timeFieldName: '' });
-      state.layers[0] = {
-        annotations: [customLineStaticAnnotation],
-        layerId: 'annotation',
-        layerType: 'annotations',
-        ignoreGlobalFilters: true,
-        indexPatternId: indexPattern.id,
-      };
-      const frameMock = createMockFramePublicAPI({
-        datasourceLayers: {},
-        dataViews: createMockDataViewsState({
-          indexPatterns: { [indexPattern.id]: indexPattern },
-        }),
-      });
-
-      const setState = jest.fn();
-
-      const component = mount(
-        <AnnotationsPanel
-          layerId={state.layers[0].layerId}
-          frame={frameMock}
-          setState={setState}
-          accessor="ann1"
-          groupId="left"
-          state={state}
-          datatableUtilities={datatableUtilities}
-          formatFactory={jest.fn()}
-          paletteService={chartPluginMock.createPaletteRegistry()}
-          panelRef={React.createRef()}
-          addLayer={jest.fn()}
-          removeLayer={jest.fn()}
-          datasource={{} as DatasourcePublicAPI}
-        />
-      );
-
-      act(() => {
-        component
-          .find(`[data-test-subj="lns-xyAnnotation-placementType"]`)
-          .find(EuiButtonGroup)
-          .prop('onChange')!('lens_xyChart_annotation_query');
-      });
-      component.update();
-
-      expect(setState).toHaveBeenCalledWith(
-        expect.objectContaining({
-          layers: [
-            expect.objectContaining({
-              annotations: [expect.objectContaining({ timeField: 'timestampLabel' })],
-            }),
-          ],
-        })
-      );
-    });
-  });
-});
diff --git a/x-pack/plugins/lens/public/visualizations/xy/xy_config_panel/annotations_config_panel/types.ts b/x-pack/plugins/lens/public/visualizations/xy/xy_config_panel/annotations_config_panel/types.ts
deleted file mode 100644
index f446afb6be265..0000000000000
--- a/x-pack/plugins/lens/public/visualizations/xy/xy_config_panel/annotations_config_panel/types.ts
+++ /dev/null
@@ -1,15 +0,0 @@
-/*
- * 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; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import {
-  PointInTimeEventAnnotationConfig,
-  RangeEventAnnotationConfig,
-} from '@kbn/event-annotation-plugin/common';
-
-export type ManualEventAnnotationType =
-  | PointInTimeEventAnnotationConfig
-  | RangeEventAnnotationConfig;
diff --git a/x-pack/plugins/lens/public/visualizations/xy/xy_config_panel/dimension_editor.tsx b/x-pack/plugins/lens/public/visualizations/xy/xy_config_panel/dimension_editor.tsx
index f5d19edd81c5e..4ce681eedbd51 100644
--- a/x-pack/plugins/lens/public/visualizations/xy/xy_config_panel/dimension_editor.tsx
+++ b/x-pack/plugins/lens/public/visualizations/xy/xy_config_panel/dimension_editor.tsx
@@ -9,7 +9,6 @@ import React, { useCallback, useMemo } from 'react';
 import { i18n } from '@kbn/i18n';
 import { EuiButtonGroup, EuiFormRow, htmlIdGenerator } from '@elastic/eui';
 import type { PaletteRegistry } from '@kbn/coloring';
-import type { DatatableUtilitiesService } from '@kbn/data-plugin/common';
 import { useDebouncedValue } from '@kbn/visualization-ui-components/public';
 import { ColorPicker } from '@kbn/visualization-ui-components/public';
 import type { VisualizationDimensionEditorProps } from '../../../types';
@@ -17,9 +16,7 @@ import { State, XYState, XYDataLayerConfig, YConfig, YAxisMode } from '../types'
 import { FormatFactory } from '../../../../common/types';
 import { getSeriesColor, isHorizontalChart } from '../state_helpers';
 import { PalettePicker } from '../../../shared_components';
-import { getDataLayers, isAnnotationsLayer, isReferenceLayer } from '../visualization_helpers';
-import { ReferenceLinePanel } from './reference_line_config_panel';
-import { AnnotationsPanel } from './annotations_config_panel';
+import { getDataLayers } from '../visualization_helpers';
 import { CollapseSetting } from '../../../shared_components/collapse_setting';
 import { getSortedAccessors } from '../to_expression';
 import { getColorAssignments, getAssignedColorConfig } from '../color_assignment';
@@ -42,26 +39,6 @@ export function updateLayer(
 
 export const idPrefix = htmlIdGenerator()();
 
-export function DimensionEditor(
-  props: VisualizationDimensionEditorProps<State> & {
-    datatableUtilities: DatatableUtilitiesService;
-    formatFactory: FormatFactory;
-    paletteService: PaletteRegistry;
-  }
-) {
-  const { state, layerId } = props;
-  const index = state.layers.findIndex((l) => l.layerId === layerId);
-  const layer = state.layers[index];
-  if (isAnnotationsLayer(layer)) {
-    return <AnnotationsPanel {...props} />;
-  }
-
-  if (isReferenceLayer(layer)) {
-    return <ReferenceLinePanel {...props} />;
-  }
-  return <DataDimensionEditor {...props} />;
-}
-
 export function DataDimensionEditor(
   props: VisualizationDimensionEditorProps<State> & {
     formatFactory: FormatFactory;
diff --git a/x-pack/plugins/lens/public/visualizations/xy/xy_config_panel/reference_line_config_panel/reference_line_panel.tsx b/x-pack/plugins/lens/public/visualizations/xy/xy_config_panel/reference_line_config_panel/reference_line_panel.tsx
index d160777eeaa4d..6f6b4807b6f96 100644
--- a/x-pack/plugins/lens/public/visualizations/xy/xy_config_panel/reference_line_config_panel/reference_line_panel.tsx
+++ b/x-pack/plugins/lens/public/visualizations/xy/xy_config_panel/reference_line_config_panel/reference_line_panel.tsx
@@ -14,6 +14,8 @@ import {
   useDebouncedValue,
   IconSelectSetting,
   ColorPicker,
+  LineStyleSettings,
+  TextDecorationSetting,
 } from '@kbn/visualization-ui-components/public';
 import type { VisualizationDimensionEditorProps } from '../../../../types';
 import { State, XYState, XYReferenceLineLayerConfig, YConfig } from '../../types';
@@ -22,11 +24,7 @@ import { FormatFactory } from '../../../../../common/types';
 import { updateLayer } from '..';
 import { idPrefix } from '../dimension_editor';
 import { isHorizontalChart } from '../../state_helpers';
-import {
-  MarkerDecorationPosition,
-  TextDecorationSetting,
-} from '../shared/marker_decoration_settings';
-import { LineStyleSettings } from '../shared/line_style_settings';
+import { MarkerDecorationPosition } from '../shared/marker_decoration_settings';
 import { referenceLineIconsSet } from './icon_set';
 import { defaultReferenceLineColor } from '../../color_assignment';
 
@@ -77,7 +75,11 @@ export const ReferenceLinePanel = (
 
   return (
     <>
-      <TextDecorationSetting setConfig={setConfig} currentConfig={localConfig} />
+      <TextDecorationSetting
+        idPrefix={idPrefix}
+        setConfig={setConfig}
+        currentConfig={localConfig}
+      />
       <IconSelectSetting
         setIcon={(icon) => setConfig({ icon })}
         currentIcon={localConfig?.icon}
@@ -88,11 +90,7 @@ export const ReferenceLinePanel = (
         setConfig={setConfig}
         currentConfig={localConfig}
       />
-      <LineStyleSettings
-        isHorizontal={isHorizontal}
-        setConfig={setConfig}
-        currentConfig={localConfig}
-      />
+      <LineStyleSettings idPrefix={idPrefix} setConfig={setConfig} currentConfig={localConfig} />
       <FillSetting isHorizontal={isHorizontal} setConfig={setConfig} currentConfig={localConfig} />
       <ColorPicker
         {...props}
diff --git a/x-pack/plugins/lens/public/visualizations/xy/xy_config_panel/shared/marker_decoration_settings.tsx b/x-pack/plugins/lens/public/visualizations/xy/xy_config_panel/shared/marker_decoration_settings.tsx
index 2ea03f2cf9884..87528f2789d28 100644
--- a/x-pack/plugins/lens/public/visualizations/xy/xy_config_panel/shared/marker_decoration_settings.tsx
+++ b/x-pack/plugins/lens/public/visualizations/xy/xy_config_panel/shared/marker_decoration_settings.tsx
@@ -5,7 +5,7 @@
  * 2.0.
  */
 
-import React, { useState } from 'react';
+import React from 'react';
 import { i18n } from '@kbn/i18n';
 import { EuiButtonGroup, EuiFormRow } from '@elastic/eui';
 import { IconPosition } from '@kbn/expression-xy-plugin/common';
@@ -83,110 +83,6 @@ export interface MarkerDecorationConfig<T extends string = string> {
   textField?: string;
 }
 
-function getSelectedOption(
-  { textField, textVisibility }: MarkerDecorationConfig = {},
-  isQueryBased?: boolean
-) {
-  if (!textVisibility) {
-    return 'none';
-  }
-  if (isQueryBased && textField) {
-    return 'field';
-  }
-  return 'name';
-}
-
-export function TextDecorationSetting<Icon extends string = string>({
-  currentConfig,
-  setConfig,
-  isQueryBased,
-  children,
-}: {
-  currentConfig?: MarkerDecorationConfig<Icon>;
-  setConfig: (config: MarkerDecorationConfig<Icon>) => void;
-  isQueryBased?: boolean;
-  /** A children render function for custom sub fields on textDecoration change */
-  children?: (textDecoration: 'none' | 'name' | 'field') => JSX.Element | null;
-}) {
-  // To model the temporary state for label based on field when user didn't pick up the field yet,
-  // use a local state
-  const [selectedVisibleOption, setVisibleOption] = useState<'none' | 'name' | 'field'>(
-    getSelectedOption(currentConfig, isQueryBased)
-  );
-  const options = [
-    {
-      id: `${idPrefix}none`,
-      label: i18n.translate('xpack.lens.xyChart.lineMarker.textVisibility.none', {
-        defaultMessage: 'None',
-      }),
-      'data-test-subj': 'lnsXY_textVisibility_none',
-    },
-    {
-      id: `${idPrefix}name`,
-      label: i18n.translate('xpack.lens.xyChart.lineMarker.textVisibility.name', {
-        defaultMessage: 'Name',
-      }),
-      'data-test-subj': 'lnsXY_textVisibility_name',
-    },
-  ];
-  if (isQueryBased) {
-    options.push({
-      id: `${idPrefix}field`,
-      label: i18n.translate('xpack.lens.xyChart.lineMarker.textVisibility.field', {
-        defaultMessage: 'Field',
-      }),
-      'data-test-subj': 'lnsXY_textVisibility_field',
-    });
-  }
-
-  return (
-    <EuiFormRow
-      label={i18n.translate('xpack.lens.lineMarker.textVisibility', {
-        defaultMessage: 'Text decoration',
-      })}
-      display="columnCompressed"
-      fullWidth
-    >
-      <div>
-        <EuiButtonGroup
-          legend={i18n.translate('xpack.lens.lineMarker.textVisibility', {
-            defaultMessage: 'Text decoration',
-          })}
-          data-test-subj="lns-lineMarker-text-visibility"
-          name="textVisibilityStyle"
-          buttonSize="compressed"
-          options={options}
-          idSelected={
-            selectedVisibleOption ? `${idPrefix}${selectedVisibleOption}` : `${idPrefix}none`
-          }
-          onChange={(id) => {
-            const chosenOption = id.replace(idPrefix, '') as 'none' | 'name' | 'field';
-            if (chosenOption === 'none') {
-              setConfig({
-                textVisibility: false,
-                textField: undefined,
-              });
-            } else if (chosenOption === 'name') {
-              setConfig({
-                textVisibility: true,
-                textField: undefined,
-              });
-            } else if (chosenOption === 'field') {
-              setConfig({
-                textVisibility: Boolean(currentConfig?.textField),
-              });
-            }
-
-            setVisibleOption(chosenOption);
-          }}
-          isFullWidth
-        />
-        {children?.(selectedVisibleOption)}
-      </div>
-    </EuiFormRow>
-  );
-}
-
 export function MarkerDecorationPosition<Icon extends string = string>({
   currentConfig,
   setConfig,
diff --git a/x-pack/plugins/lens/public/visualizations/xy/xy_config_panel/xy_config_panel.test.tsx b/x-pack/plugins/lens/public/visualizations/xy/xy_config_panel/xy_config_panel.test.tsx
index 6caa8238808e9..252c3de6b8e57 100644
--- a/x-pack/plugins/lens/public/visualizations/xy/xy_config_panel/xy_config_panel.test.tsx
+++ b/x-pack/plugins/lens/public/visualizations/xy/xy_config_panel/xy_config_panel.test.tsx
@@ -8,9 +8,8 @@
 import React from 'react';
 import { mountWithIntl as mount, shallowWithIntl as shallow } from '@kbn/test-jest-helpers';
 import { EuiButtonGroupProps, EuiButtonGroup } from '@elastic/eui';
-import { createDatatableUtilitiesMock } from '@kbn/data-plugin/common/mocks';
 import { XyToolbar } from '.';
-import { DimensionEditor } from './dimension_editor';
+import { DataDimensionEditor } from './dimension_editor';
 import { AxisSettingsPopover } from './axis_settings_popover';
 import { FramePublicAPI, DatasourcePublicAPI } from '../../../types';
 import { State, XYState, XYDataLayerConfig } from '../types';
@@ -254,12 +253,10 @@ describe('XY Config panels', () => {
   });
 
   describe('Dimension Editor', () => {
-    const datatableUtilities = createDatatableUtilitiesMock();
-
     test('shows the correct axis side options when in horizontal mode', () => {
       const state = testState();
       const component = mount(
-        <DimensionEditor
+        <DataDimensionEditor
           layerId={state.layers[0].layerId}
           frame={frame}
           setState={jest.fn()}
@@ -269,7 +266,6 @@ describe('XY Config panels', () => {
             ...state,
             layers: [{ ...state.layers[0], seriesType: 'bar_horizontal' } as XYDataLayerConfig],
           }}
-          datatableUtilities={datatableUtilities}
           formatFactory={jest.fn()}
           paletteService={chartPluginMock.createPaletteRegistry()}
           panelRef={React.createRef()}
@@ -290,14 +286,13 @@ describe('XY Config panels', () => {
     test('shows the default axis side options when not in horizontal mode', () => {
       const state = testState();
       const component = mount(
-        <DimensionEditor
+        <DataDimensionEditor
           layerId={state.layers[0].layerId}
           frame={frame}
           setState={jest.fn()}
           accessor="bar"
           groupId="left"
           state={state}
-          datatableUtilities={datatableUtilities}
           formatFactory={jest.fn()}
           paletteService={chartPluginMock.createPaletteRegistry()}
           panelRef={React.createRef()}
@@ -330,7 +325,7 @@ describe('XY Config panels', () => {
         ],
       } as XYState;
       const component = mount(
-        <DimensionEditor
+        <DataDimensionEditor
           layerId={state.layers[0].layerId}
           frame={{
             ...frame,
@@ -346,7 +341,6 @@ describe('XY Config panels', () => {
           accessor="bar"
           groupId="left"
           state={state}
-          datatableUtilities={datatableUtilities}
           formatFactory={jest.fn()}
           paletteService={chartPluginMock.createPaletteRegistry()}
           panelRef={React.createRef()}
@@ -376,7 +370,7 @@ describe('XY Config panels', () => {
       } as XYState;
 
       const component = mount(
-        <DimensionEditor
+        <DataDimensionEditor
           layerId={state.layers[0].layerId}
           frame={{
             ...frame,
@@ -392,7 +386,6 @@ describe('XY Config panels', () => {
           accessor="bar"
           groupId="left"
           state={state}
-          datatableUtilities={datatableUtilities}
           formatFactory={jest.fn()}
           paletteService={chartPluginMock.createPaletteRegistry()}
           panelRef={React.createRef()}
@@ -422,7 +415,7 @@ describe('XY Config panels', () => {
       } as XYState;
 
       const component = mount(
-        <DimensionEditor
+        <DataDimensionEditor
           layerId={state.layers[0].layerId}
           frame={{
             ...frame,
@@ -438,7 +431,6 @@ describe('XY Config panels', () => {
           accessor="bar"
           groupId="left"
           state={state}
-          datatableUtilities={datatableUtilities}
           formatFactory={jest.fn()}
           paletteService={chartPluginMock.createPaletteRegistry()}
           panelRef={React.createRef()}
diff --git a/x-pack/plugins/lens/public/visualizations/xy/xy_suggestions.test.ts b/x-pack/plugins/lens/public/visualizations/xy/xy_suggestions.test.ts
index 0007493f3b8eb..8417d02d79995 100644
--- a/x-pack/plugins/lens/public/visualizations/xy/xy_suggestions.test.ts
+++ b/x-pack/plugins/lens/public/visualizations/xy/xy_suggestions.test.ts
@@ -25,6 +25,7 @@ import { coreMock, themeServiceMock } from '@kbn/core/public/mocks';
 import { dataPluginMock } from '@kbn/data-plugin/public/mocks';
 import { IStorageWrapper } from '@kbn/kibana-utils-plugin/public';
 import { unifiedSearchPluginMock } from '@kbn/unified-search-plugin/public/mocks';
+import { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public';
 
 jest.mock('../../id_generator');
 
@@ -38,6 +39,7 @@ const xyVisualization = getXyVisualization({
   storage: {} as IStorageWrapper,
   data: dataPluginMock.createStartContract(),
   unifiedSearch: unifiedSearchPluginMock.createStartContract(),
+  dataViewsService: {} as DataViewsPublicPluginStart,
 });
 
 describe('xy_suggestions', () => {
diff --git a/x-pack/plugins/maps/public/render_app.tsx b/x-pack/plugins/maps/public/render_app.tsx
index c4bddf5a71541..3897e38222eef 100644
--- a/x-pack/plugins/maps/public/render_app.tsx
+++ b/x-pack/plugins/maps/public/render_app.tsx
@@ -15,7 +15,7 @@ import { ExitFullScreenButtonKibanaProvider } from '@kbn/shared-ux-button-exit-f
 import { KibanaThemeProvider, toMountPoint } from '@kbn/kibana-react-plugin/public';
 import { FormattedRelative } from '@kbn/i18n-react';
 import type { SavedObjectTaggingPluginStart } from '@kbn/saved-objects-tagging-plugin/public';
-import { TableListViewKibanaProvider } from '@kbn/content-management-table-list';
+import { TableListViewKibanaProvider } from '@kbn/content-management-table-list-view-table';
 import {
   getCoreChrome,
   getCoreI18n,
diff --git a/x-pack/plugins/maps/public/routes/list_page/maps_list_view.tsx b/x-pack/plugins/maps/public/routes/list_page/maps_list_view.tsx
index d98444057097d..93fc858f1c9d8 100644
--- a/x-pack/plugins/maps/public/routes/list_page/maps_list_view.tsx
+++ b/x-pack/plugins/maps/public/routes/list_page/maps_list_view.tsx
@@ -9,8 +9,8 @@ import React, { useCallback, memo, useEffect } from 'react';
 import type { SavedObjectsFindOptionsReference, ScopedHistory } from '@kbn/core/public';
 import { METRIC_TYPE } from '@kbn/analytics';
 import { i18n } from '@kbn/i18n';
-import { TableListView } from '@kbn/content-management-table-list';
-import type { UserContentCommonSchema } from '@kbn/content-management-table-list';
+import { TableListView } from '@kbn/content-management-table-list-view';
+import type { UserContentCommonSchema } from '@kbn/content-management-table-list-view';
 
 import type { MapItem } from '../../../common/content_management';
 import { APP_ID, APP_NAME, getEditPath, MAP_PATH } from '../../../common/constants';
@@ -133,7 +133,7 @@ function MapsListViewComp({ history }: Props) {
       entityNamePlural={i18n.translate('xpack.maps.mapListing.entityNamePlural', {
         defaultMessage: 'maps',
       })}
-      tableListTitle={APP_NAME}
+      title={APP_NAME}
       onClickTitle={({ id }) => history.push(getEditPath(id))}
     />
   );
diff --git a/x-pack/plugins/maps/tsconfig.json b/x-pack/plugins/maps/tsconfig.json
index 6f24d4d7d3c5e..b0e27ee323dba 100644
--- a/x-pack/plugins/maps/tsconfig.json
+++ b/x-pack/plugins/maps/tsconfig.json
@@ -50,7 +50,6 @@
     "@kbn/field-formats-plugin",
     "@kbn/shared-ux-button-exit-full-screen",
     "@kbn/i18n-react",
-    "@kbn/content-management-table-list",
     "@kbn/react-field",
     "@kbn/analytics",
     "@kbn/mapbox-gl",
@@ -71,6 +70,8 @@
     "@kbn/core-saved-objects-server",
     "@kbn/maps-vector-tile-utils",
     "@kbn/core-http-common",
+    "@kbn/content-management-table-list-view-table",
+    "@kbn/content-management-table-list-view",
   ],
   "exclude": [
     "target/**/*",
diff --git a/x-pack/plugins/translations/translations/fr-FR.json b/x-pack/plugins/translations/translations/fr-FR.json
index 009cb044b4c3e..65e1cae7b8ec6 100644
--- a/x-pack/plugins/translations/translations/fr-FR.json
+++ b/x-pack/plugins/translations/translations/fr-FR.json
@@ -20922,7 +20922,6 @@
     "xpack.lens.legacyUrlConflict.objectNoun": "Visualisation Lens",
     "xpack.lens.lensSavedObjectLabel": "Visualisation Lens",
     "xpack.lens.lineMarker.positionRequirementTooltip": "Vous devez sélectionner une icône ou afficher le nom pour pouvoir en modifier la position.",
-    "xpack.lens.lineMarker.textVisibility": "Décoration du texte",
     "xpack.lens.metric.addLayer": "Visualisation",
     "xpack.lens.metric.breakdownBy": "Répartir par",
     "xpack.lens.metric.color": "Couleur",
@@ -21041,7 +21040,6 @@
     "xpack.lens.pieChart.visualOptionsLabel": "Options visuelles",
     "xpack.lens.primaryMetric.headingLabel": "Valeur",
     "xpack.lens.primaryMetric.label": "Indicateur principal",
-    "xpack.lens.queryInput.appName": "Lens",
     "xpack.lens.reporting.shareContextMenu.csvReportsButtonLabel": "Téléchargement CSV",
     "xpack.lens.resetLayerAriaLabel": "Effacer le calque",
     "xpack.lens.saveDuplicateRejectedDescription": "La confirmation d'enregistrement avec un doublon de titre a été rejetée.",
@@ -21157,22 +21155,7 @@
     "xpack.lens.xyChart.addDataLayerLabel": "Visualisation",
     "xpack.lens.xyChart.addReferenceLineLayerLabel": "Lignes de référence",
     "xpack.lens.xyChart.addReferenceLineLayerLabelDisabledHelp": "Ajouter des données pour activer le calque de référence",
-    "xpack.lens.xyChart.annotation.hide": "Masquer l’annotation",
-    "xpack.lens.xyChart.annotation.manual": "Date statique",
-    "xpack.lens.xyChart.annotation.query": "Requête personnalisée",
-    "xpack.lens.xyChart.annotation.queryField": "Champ de date cible",
-    "xpack.lens.xyChart.annotation.queryInput": "Requête sur les annotations",
-    "xpack.lens.xyChart.annotation.tooltip": "Afficher les champs supplémentaires",
-    "xpack.lens.xyChart.annotation.tooltip.addField": "Ajouter un champ",
-    "xpack.lens.xyChart.annotation.tooltip.deleteButtonLabel": "Supprimer",
-    "xpack.lens.xyChart.annotation.tooltip.noFields": "Aucune sélection",
-    "xpack.lens.xyChart.annotationDate": "Date de l’annotation",
-    "xpack.lens.xyChart.annotationDate.from": "De",
-    "xpack.lens.xyChart.annotationDate.placementType": "Type de placement",
-    "xpack.lens.xyChart.annotationDate.to": "À",
     "xpack.lens.xyChart.annotationError.timeFieldEmpty": "Le champ temporel est manquant",
-    "xpack.lens.xyChart.appearance": "Apparence",
-    "xpack.lens.xyChart.applyAsRange": "Appliquer en tant que plage",
     "xpack.lens.xyChart.axisOrientation.angled": "En angle",
     "xpack.lens.xyChart.axisOrientation.horizontal": "Horizontal",
     "xpack.lens.xyChart.axisOrientation.label": "Orientation",
@@ -21195,9 +21178,6 @@
     "xpack.lens.xyChart.fill.label": "Remplir",
     "xpack.lens.xyChart.fill.none": "Aucun",
     "xpack.lens.xyChart.fillOpacityLabel": "Opacité de remplissage",
-    "xpack.lens.xyChart.fillStyle": "Remplir",
-    "xpack.lens.xyChart.fillStyle.inside": "Intérieur",
-    "xpack.lens.xyChart.fillStyle.outside": "Extérieur",
     "xpack.lens.xyChart.Gridlines": "Quadrillage",
     "xpack.lens.xyChart.horizontalAxisLabel": "Axe horizontal",
     "xpack.lens.xyChart.horizontalLeftAxisLabel": "Axe supérieur horizontal",
@@ -21207,17 +21187,10 @@
     "xpack.lens.xyChart.iconSelect.bellIconLabel": "Cloche",
     "xpack.lens.xyChart.iconSelect.boltIconLabel": "Éclair",
     "xpack.lens.xyChart.iconSelect.bugIconLabel": "Bug",
-    "xpack.lens.xyChart.iconSelect.circleIconLabel": "Cercle",
     "xpack.lens.xyChart.iconSelect.commentIconLabel": "Commentaire",
     "xpack.lens.xyChart.iconSelect.flagIconLabel": "Drapeau",
-    "xpack.lens.xyChart.iconSelect.heartLabel": "Cœur",
-    "xpack.lens.xyChart.iconSelect.mapMarkerLabel": "Repère",
-    "xpack.lens.xyChart.iconSelect.mapPinLabel": "Punaise",
     "xpack.lens.xyChart.iconSelect.noIconLabel": "Aucun",
-    "xpack.lens.xyChart.iconSelect.starFilledLabel": "Étoile remplie",
-    "xpack.lens.xyChart.iconSelect.starLabel": "Étoile",
     "xpack.lens.xyChart.iconSelect.tagIconLabel": "Balise",
-    "xpack.lens.xyChart.iconSelect.triangleIconLabel": "Triangle",
     "xpack.lens.xyChart.layerAnnotation": "Annotation",
     "xpack.lens.xyChart.layerAnnotationsIgnoreTitle": "Calques ignorant les filtres globaux",
     "xpack.lens.xyChart.layerAnnotationsLabel": "Annotations",
@@ -21233,13 +21206,6 @@
     "xpack.lens.xyChart.lineColor.label": "Couleur",
     "xpack.lens.xyChart.lineMarker.auto": "Auto",
     "xpack.lens.xyChart.lineMarker.position": "Position de la décoration",
-    "xpack.lens.xyChart.lineMarker.textVisibility.field": "Champ",
-    "xpack.lens.xyChart.lineMarker.textVisibility.name": "Nom",
-    "xpack.lens.xyChart.lineMarker.textVisibility.none": "Aucun",
-    "xpack.lens.xyChart.lineStyle.dashed": "Tirets",
-    "xpack.lens.xyChart.lineStyle.dotted": "Pointillé",
-    "xpack.lens.xyChart.lineStyle.label": "Ligne",
-    "xpack.lens.xyChart.lineStyle.solid": "Uni",
     "xpack.lens.xyChart.markerPosition.above": "Haut",
     "xpack.lens.xyChart.markerPosition.below": "Bas",
     "xpack.lens.xyChart.markerPosition.left": "Gauche",
@@ -21248,7 +21214,6 @@
     "xpack.lens.xyChart.missingValuesLabelHelpText": "Par défaut, les graphiques en aires et en courbes masquent les blancs entre les données. Pour remplir le blanc, effectuez une sélection.",
     "xpack.lens.xyChart.missingValuesStyle": "Afficher sous la forme d’une ligne pointillée",
     "xpack.lens.xyChart.nestUnderRoot": "Ensemble de données entier",
-    "xpack.lens.xyChart.placement": "Placement",
     "xpack.lens.xyChart.rightAxisDisabledHelpText": "Ce paramètre s'applique uniquement lorsque l'axe de droite est activé.",
     "xpack.lens.xyChart.rightAxisLabel": "Axe de droite",
     "xpack.lens.xyChart.scaleLinear": "Linéaire",
@@ -21258,7 +21223,6 @@
     "xpack.lens.xyChart.showCurrenTimeMarker": "Afficher le repère de temps actuel",
     "xpack.lens.xyChart.showEnzones": "Afficher les marqueurs de données partielles",
     "xpack.lens.xyChart.splitSeries": "Répartition",
-    "xpack.lens.xyChart.tooltip": "Infobulle",
     "xpack.lens.xyChart.topAxisDisabledHelpText": "Ce paramètre s'applique uniquement lorsque l'axe du haut est activé.",
     "xpack.lens.xyChart.topAxisLabel": "Axe du haut",
     "xpack.lens.xyChart.valuesHistogramDisabledHelpText": "Ce paramètre ne peut pas être modifié dans les histogrammes.",
diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json
index 44eebedd74528..30ca83889d63f 100644
--- a/x-pack/plugins/translations/translations/ja-JP.json
+++ b/x-pack/plugins/translations/translations/ja-JP.json
@@ -20922,7 +20922,6 @@
     "xpack.lens.legacyUrlConflict.objectNoun": "レンズビジュアライゼーション",
     "xpack.lens.lensSavedObjectLabel": "レンズビジュアライゼーション",
     "xpack.lens.lineMarker.positionRequirementTooltip": "位置を変更するには、アイコンを選択するか、名前を表示する必要があります",
-    "xpack.lens.lineMarker.textVisibility": "テキスト装飾",
     "xpack.lens.metric.addLayer": "ビジュアライゼーション",
     "xpack.lens.metric.breakdownBy": "内訳の基準",
     "xpack.lens.metric.color": "色",
@@ -21041,7 +21040,6 @@
     "xpack.lens.pieChart.visualOptionsLabel": "視覚オプション",
     "xpack.lens.primaryMetric.headingLabel": "値",
     "xpack.lens.primaryMetric.label": "主メトリック",
-    "xpack.lens.queryInput.appName": "レンズ",
     "xpack.lens.reporting.shareContextMenu.csvReportsButtonLabel": "CSVダウンロード",
     "xpack.lens.resetLayerAriaLabel": "レイヤーをクリア",
     "xpack.lens.saveDuplicateRejectedDescription": "重複ファイルの保存確認が拒否されました",
@@ -21157,22 +21155,7 @@
     "xpack.lens.xyChart.addDataLayerLabel": "ビジュアライゼーション",
     "xpack.lens.xyChart.addReferenceLineLayerLabel": "基準線",
     "xpack.lens.xyChart.addReferenceLineLayerLabelDisabledHelp": "一部のデータを追加して、基準レイヤーを有効にする",
-    "xpack.lens.xyChart.annotation.hide": "注釈を非表示",
-    "xpack.lens.xyChart.annotation.manual": "固定日付",
-    "xpack.lens.xyChart.annotation.query": "カスタムクエリ",
-    "xpack.lens.xyChart.annotation.queryField": "ターゲット日付フィールド",
-    "xpack.lens.xyChart.annotation.queryInput": "注釈クエリ",
-    "xpack.lens.xyChart.annotation.tooltip": "追加フィールドを表示",
-    "xpack.lens.xyChart.annotation.tooltip.addField": "フィールドの追加",
-    "xpack.lens.xyChart.annotation.tooltip.deleteButtonLabel": "削除",
-    "xpack.lens.xyChart.annotation.tooltip.noFields": "選択されていません",
-    "xpack.lens.xyChart.annotationDate": "注釈日",
-    "xpack.lens.xyChart.annotationDate.from": "開始:",
-    "xpack.lens.xyChart.annotationDate.placementType": "配置タイプ",
-    "xpack.lens.xyChart.annotationDate.to": "終了:",
     "xpack.lens.xyChart.annotationError.timeFieldEmpty": "時刻フィールドがありません",
-    "xpack.lens.xyChart.appearance": "見た目",
-    "xpack.lens.xyChart.applyAsRange": "範囲として適用",
     "xpack.lens.xyChart.axisOrientation.angled": "傾斜",
     "xpack.lens.xyChart.axisOrientation.horizontal": "横",
     "xpack.lens.xyChart.axisOrientation.label": "向き",
@@ -21195,9 +21178,6 @@
     "xpack.lens.xyChart.fill.label": "塗りつぶし",
     "xpack.lens.xyChart.fill.none": "なし",
     "xpack.lens.xyChart.fillOpacityLabel": "塗りつぶしの透明度",
-    "xpack.lens.xyChart.fillStyle": "塗りつぶし",
-    "xpack.lens.xyChart.fillStyle.inside": "内部",
-    "xpack.lens.xyChart.fillStyle.outside": "外側",
     "xpack.lens.xyChart.Gridlines": "グリッド線",
     "xpack.lens.xyChart.horizontalAxisLabel": "横軸",
     "xpack.lens.xyChart.horizontalLeftAxisLabel": "横上軸",
@@ -21207,17 +21187,10 @@
     "xpack.lens.xyChart.iconSelect.bellIconLabel": "ベル",
     "xpack.lens.xyChart.iconSelect.boltIconLabel": "ボルト",
     "xpack.lens.xyChart.iconSelect.bugIconLabel": "バグ",
-    "xpack.lens.xyChart.iconSelect.circleIconLabel": "円",
     "xpack.lens.xyChart.iconSelect.commentIconLabel": "コメント",
     "xpack.lens.xyChart.iconSelect.flagIconLabel": "旗",
-    "xpack.lens.xyChart.iconSelect.heartLabel": "ハート",
-    "xpack.lens.xyChart.iconSelect.mapMarkerLabel": "マップマーカー",
-    "xpack.lens.xyChart.iconSelect.mapPinLabel": "マップピン",
     "xpack.lens.xyChart.iconSelect.noIconLabel": "なし",
-    "xpack.lens.xyChart.iconSelect.starFilledLabel": "塗りつぶされた星",
-    "xpack.lens.xyChart.iconSelect.starLabel": "星",
     "xpack.lens.xyChart.iconSelect.tagIconLabel": "タグ",
-    "xpack.lens.xyChart.iconSelect.triangleIconLabel": "三角形",
     "xpack.lens.xyChart.layerAnnotation": "注釈",
     "xpack.lens.xyChart.layerAnnotationsIgnoreTitle": "グローバルフィルターを無視するレイヤー",
     "xpack.lens.xyChart.layerAnnotationsLabel": "注釈",
@@ -21233,13 +21206,6 @@
     "xpack.lens.xyChart.lineColor.label": "色",
     "xpack.lens.xyChart.lineMarker.auto": "自動",
     "xpack.lens.xyChart.lineMarker.position": "装飾位置",
-    "xpack.lens.xyChart.lineMarker.textVisibility.field": "フィールド",
-    "xpack.lens.xyChart.lineMarker.textVisibility.name": "名前",
-    "xpack.lens.xyChart.lineMarker.textVisibility.none": "なし",
-    "xpack.lens.xyChart.lineStyle.dashed": "鎖線",
-    "xpack.lens.xyChart.lineStyle.dotted": "点線",
-    "xpack.lens.xyChart.lineStyle.label": "折れ線",
-    "xpack.lens.xyChart.lineStyle.solid": "塗りつぶし",
     "xpack.lens.xyChart.markerPosition.above": "トップ",
     "xpack.lens.xyChart.markerPosition.below": "一番下",
     "xpack.lens.xyChart.markerPosition.left": "左",
@@ -21248,7 +21214,6 @@
     "xpack.lens.xyChart.missingValuesLabelHelpText": "デフォルトでは、面グラフと折れ線グラフはデータのギャップが非表示になります。ギャップを埋めるには、選択します。",
     "xpack.lens.xyChart.missingValuesStyle": "点線として表示",
     "xpack.lens.xyChart.nestUnderRoot": "データセット全体",
-    "xpack.lens.xyChart.placement": "配置",
     "xpack.lens.xyChart.rightAxisDisabledHelpText": "この設定は、右の軸が有効であるときにのみ適用されます。",
     "xpack.lens.xyChart.rightAxisLabel": "右の軸",
     "xpack.lens.xyChart.scaleLinear": "線形",
@@ -21258,7 +21223,6 @@
     "xpack.lens.xyChart.showCurrenTimeMarker": "現在時刻マーカーを表示",
     "xpack.lens.xyChart.showEnzones": "部分データマーカーを表示",
     "xpack.lens.xyChart.splitSeries": "内訳",
-    "xpack.lens.xyChart.tooltip": "ツールチップ",
     "xpack.lens.xyChart.topAxisDisabledHelpText": "この設定は、上の軸が有効であるときにのみ適用されます。",
     "xpack.lens.xyChart.topAxisLabel": "上の軸",
     "xpack.lens.xyChart.valuesHistogramDisabledHelpText": "この設定はヒストグラムで変更できません。",
diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json
index 73eb81fb3bc38..212f1bed8ff47 100644
--- a/x-pack/plugins/translations/translations/zh-CN.json
+++ b/x-pack/plugins/translations/translations/zh-CN.json
@@ -20922,7 +20922,6 @@
     "xpack.lens.legacyUrlConflict.objectNoun": "Lens 可视化",
     "xpack.lens.lensSavedObjectLabel": "Lens 可视化",
     "xpack.lens.lineMarker.positionRequirementTooltip": "必须选择图标或显示名称才能更改其位置",
-    "xpack.lens.lineMarker.textVisibility": "文本装饰",
     "xpack.lens.metric.addLayer": "可视化",
     "xpack.lens.metric.breakdownBy": "细分方式",
     "xpack.lens.metric.color": "颜色",
@@ -21041,7 +21040,6 @@
     "xpack.lens.pieChart.visualOptionsLabel": "视觉选项",
     "xpack.lens.primaryMetric.headingLabel": "值",
     "xpack.lens.primaryMetric.label": "主要指标",
-    "xpack.lens.queryInput.appName": "Lens",
     "xpack.lens.reporting.shareContextMenu.csvReportsButtonLabel": "CSV 下载",
     "xpack.lens.resetLayerAriaLabel": "清除图层",
     "xpack.lens.saveDuplicateRejectedDescription": "已拒绝使用重复标题保存确认",
@@ -21157,22 +21155,7 @@
     "xpack.lens.xyChart.addDataLayerLabel": "可视化",
     "xpack.lens.xyChart.addReferenceLineLayerLabel": "参考线",
     "xpack.lens.xyChart.addReferenceLineLayerLabelDisabledHelp": "添加一些数据以启用参考图层",
-    "xpack.lens.xyChart.annotation.hide": "隐藏标注",
-    "xpack.lens.xyChart.annotation.manual": "静态日期",
-    "xpack.lens.xyChart.annotation.query": "定制查询",
-    "xpack.lens.xyChart.annotation.queryField": "目标日期字段",
-    "xpack.lens.xyChart.annotation.queryInput": "标注查询",
-    "xpack.lens.xyChart.annotation.tooltip": "显示其他字段",
-    "xpack.lens.xyChart.annotation.tooltip.addField": "添加字段",
-    "xpack.lens.xyChart.annotation.tooltip.deleteButtonLabel": "删除",
-    "xpack.lens.xyChart.annotation.tooltip.noFields": "未选择任何内容",
-    "xpack.lens.xyChart.annotationDate": "标注日期",
-    "xpack.lens.xyChart.annotationDate.from": "自",
-    "xpack.lens.xyChart.annotationDate.placementType": "位置类型",
-    "xpack.lens.xyChart.annotationDate.to": "至",
     "xpack.lens.xyChart.annotationError.timeFieldEmpty": "缺少时间字段",
-    "xpack.lens.xyChart.appearance": "外观",
-    "xpack.lens.xyChart.applyAsRange": "应用为范围",
     "xpack.lens.xyChart.axisOrientation.angled": "带角度",
     "xpack.lens.xyChart.axisOrientation.horizontal": "水平",
     "xpack.lens.xyChart.axisOrientation.label": "方向",
@@ -21195,9 +21178,6 @@
     "xpack.lens.xyChart.fill.label": "填充",
     "xpack.lens.xyChart.fill.none": "无",
     "xpack.lens.xyChart.fillOpacityLabel": "填充透明度",
-    "xpack.lens.xyChart.fillStyle": "填充",
-    "xpack.lens.xyChart.fillStyle.inside": "内部",
-    "xpack.lens.xyChart.fillStyle.outside": "外部",
     "xpack.lens.xyChart.Gridlines": "网格线",
     "xpack.lens.xyChart.horizontalAxisLabel": "水平轴",
     "xpack.lens.xyChart.horizontalLeftAxisLabel": "水平顶轴",
@@ -21207,17 +21187,10 @@
     "xpack.lens.xyChart.iconSelect.bellIconLabel": "钟铃",
     "xpack.lens.xyChart.iconSelect.boltIconLabel": "闪电",
     "xpack.lens.xyChart.iconSelect.bugIconLabel": "昆虫",
-    "xpack.lens.xyChart.iconSelect.circleIconLabel": "圆形",
     "xpack.lens.xyChart.iconSelect.commentIconLabel": "注释",
     "xpack.lens.xyChart.iconSelect.flagIconLabel": "旗帜",
-    "xpack.lens.xyChart.iconSelect.heartLabel": "心形",
-    "xpack.lens.xyChart.iconSelect.mapMarkerLabel": "地图标记",
-    "xpack.lens.xyChart.iconSelect.mapPinLabel": "地图图钉",
     "xpack.lens.xyChart.iconSelect.noIconLabel": "无",
-    "xpack.lens.xyChart.iconSelect.starFilledLabel": "星形填充",
-    "xpack.lens.xyChart.iconSelect.starLabel": "五角星",
     "xpack.lens.xyChart.iconSelect.tagIconLabel": "标签",
-    "xpack.lens.xyChart.iconSelect.triangleIconLabel": "三角形",
     "xpack.lens.xyChart.layerAnnotation": "标注",
     "xpack.lens.xyChart.layerAnnotationsIgnoreTitle": "忽略全局筛选的图层",
     "xpack.lens.xyChart.layerAnnotationsLabel": "标注",
@@ -21233,13 +21206,6 @@
     "xpack.lens.xyChart.lineColor.label": "颜色",
     "xpack.lens.xyChart.lineMarker.auto": "自动",
     "xpack.lens.xyChart.lineMarker.position": "装饰位置",
-    "xpack.lens.xyChart.lineMarker.textVisibility.field": "字段",
-    "xpack.lens.xyChart.lineMarker.textVisibility.name": "名称",
-    "xpack.lens.xyChart.lineMarker.textVisibility.none": "无",
-    "xpack.lens.xyChart.lineStyle.dashed": "虚线",
-    "xpack.lens.xyChart.lineStyle.dotted": "点线",
-    "xpack.lens.xyChart.lineStyle.label": "折线图",
-    "xpack.lens.xyChart.lineStyle.solid": "纯色",
     "xpack.lens.xyChart.markerPosition.above": "顶部",
     "xpack.lens.xyChart.markerPosition.below": "底部",
     "xpack.lens.xyChart.markerPosition.left": "左",
@@ -21248,7 +21214,6 @@
     "xpack.lens.xyChart.missingValuesLabelHelpText": "默认情况下,面积图和折线图会隐藏数据中的缺口。要填充缺口,请进行选择。",
     "xpack.lens.xyChart.missingValuesStyle": "显示为虚线",
     "xpack.lens.xyChart.nestUnderRoot": "整个数据集",
-    "xpack.lens.xyChart.placement": "位置",
     "xpack.lens.xyChart.rightAxisDisabledHelpText": "此设置仅在启用右轴时应用。",
     "xpack.lens.xyChart.rightAxisLabel": "右轴",
     "xpack.lens.xyChart.scaleLinear": "线性",
@@ -21258,7 +21223,6 @@
     "xpack.lens.xyChart.showCurrenTimeMarker": "显示当前时间标记",
     "xpack.lens.xyChart.showEnzones": "显示部分数据标记",
     "xpack.lens.xyChart.splitSeries": "细目",
-    "xpack.lens.xyChart.tooltip": "工具提示",
     "xpack.lens.xyChart.topAxisDisabledHelpText": "此设置仅在启用顶轴时应用。",
     "xpack.lens.xyChart.topAxisLabel": "顶轴",
     "xpack.lens.xyChart.valuesHistogramDisabledHelpText": "不能在直方图上更改此设置。",
diff --git a/x-pack/test/functional/apps/lens/group6/annotations.ts b/x-pack/test/functional/apps/lens/group6/annotations.ts
index 4bec636f52625..aa3b409d21fb0 100644
--- a/x-pack/test/functional/apps/lens/group6/annotations.ts
+++ b/x-pack/test/functional/apps/lens/group6/annotations.ts
@@ -22,6 +22,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
   const retry = getService('retry');
   const toastsService = getService('toasts');
   const testSubjects = getService('testSubjects');
+  const listingTable = getService('listingTable');
   const from = 'Sep 19, 2015 @ 06:31:44.000';
   const to = 'Sep 23, 2015 @ 18:31:44.000';
 
@@ -156,7 +157,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
         const toastContents = await toastsService.getToastContent(1);
 
         expect(toastContents).to.be(
-          `Saved "${ANNOTATION_GROUP_TITLE}"\nView or manage in the annotation library`
+          `Saved "${ANNOTATION_GROUP_TITLE}"\nView or manage in the annotation library.`
         );
 
         await PageObjects.lens.save(FIRST_VIS_TITLE);
@@ -181,15 +182,10 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
       });
 
       it('should remove layer for deleted annotation group', async () => {
-        // TODO - delete from listing page instead
-
-        await PageObjects.settings.navigateTo();
-        await PageObjects.settings.clickKibanaSavedObjects();
-        await PageObjects.savedObjects.searchForObject(ANNOTATION_GROUP_TITLE);
-        await PageObjects.savedObjects.clickCheckboxByTitle(ANNOTATION_GROUP_TITLE);
-        await PageObjects.savedObjects.clickDelete({ confirmDelete: true });
-
         await PageObjects.visualize.gotoVisualizationLandingPage();
+        await PageObjects.visualize.selectAnnotationsTab();
+        await listingTable.deleteItem(ANNOTATION_GROUP_TITLE);
+        await PageObjects.visualize.selectVisualizationsTab();
         await PageObjects.visualize.loadSavedVisualization(FIRST_VIS_TITLE, {
           navigateToVisualize: false,
         });
diff --git a/x-pack/test/functional/page_objects/lens_page.ts b/x-pack/test/functional/page_objects/lens_page.ts
index 7bbcc5dc73321..4370e7d143779 100644
--- a/x-pack/test/functional/page_objects/lens_page.ts
+++ b/x-pack/test/functional/page_objects/lens_page.ts
@@ -128,7 +128,7 @@ export function LensPageProvider({ getService, getPageObjects }: FtrProviderCont
       extraFields?: string[];
     }) {
       // type * in the query editor
-      const queryInput = await testSubjects.find('lnsXY-annotation-query-based-query-input');
+      const queryInput = await testSubjects.find('annotation-query-based-query-input');
       await queryInput.type(opts.queryString);
       await testSubjects.click('indexPattern-filters-existingFilterTrigger');
       await this.selectOptionFromComboBox(
@@ -773,7 +773,7 @@ export function LensPageProvider({ getService, getPageObjects }: FtrProviderCont
     },
 
     async editDimensionLabel(label: string) {
-      await testSubjects.setValue('column-label-edit', label, { clearWithKeyboard: true });
+      await testSubjects.setValue('name-input', label, { clearWithKeyboard: true });
     },
     async editDimensionFormat(format: string, options?: { decimals?: number; prefix?: string }) {
       await this.selectOptionFromComboBox('indexPattern-dimension-format', format);
diff --git a/yarn.lock b/yarn.lock
index 365d7ef74d20e..0d175c93031de 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -3180,7 +3180,15 @@
   version "0.0.0"
   uid ""
 
-"@kbn/content-management-table-list@link:packages/content-management/table_list":
+"@kbn/content-management-tabbed-table-list-view@link:packages/content-management/tabbed_table_list_view":
+  version "0.0.0"
+  uid ""
+
+"@kbn/content-management-table-list-view-table@link:packages/content-management/table_list_view_table":
+  version "0.0.0"
+  uid ""
+
+"@kbn/content-management-table-list-view@link:packages/content-management/table_list_view":
   version "0.0.0"
   uid ""