diff --git a/package.json b/package.json
index b3dcfb2aa3b0a..261b3ad74d9b7 100644
--- a/package.json
+++ b/package.json
@@ -314,6 +314,7 @@
     "@types/cheerio": "^0.22.10",
     "@types/chromedriver": "^2.38.0",
     "@types/classnames": "^2.2.9",
+    "@types/color": "^3.0.0",
     "@types/d3": "^3.5.43",
     "@types/dedent": "^0.7.0",
     "@types/deep-freeze-strict": "^1.1.0",
diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/vis_types/_vis_types.scss b/src/legacy/core_plugins/vis_type_timeseries/public/components/vis_types/_vis_types.scss
index 90c2007b1c94a..3db09bace079f 100644
--- a/src/legacy/core_plugins/vis_type_timeseries/public/components/vis_types/_vis_types.scss
+++ b/src/legacy/core_plugins/vis_type_timeseries/public/components/vis_types/_vis_types.scss
@@ -7,4 +7,21 @@
   .tvbVisTimeSeries {
     overflow: hidden;
   }
+  .tvbVisTimeSeriesDark {
+    .echReactiveChart_unavailable {
+      color: #DFE5EF;
+    }
+    .echLegendItem  {
+      color: #DFE5EF;
+    }
+  }
+  .tvbVisTimeSeriesLight {
+    .echReactiveChart_unavailable {
+      color: #343741;
+    }
+    .echLegendItem  {
+      color: #343741;
+    }
+  }
 }
+
diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/vis_types/timeseries/vis.js b/src/legacy/core_plugins/vis_type_timeseries/public/components/vis_types/timeseries/vis.js
index 954d3d174bb8c..356ba08ac2427 100644
--- a/src/legacy/core_plugins/vis_type_timeseries/public/components/vis_types/timeseries/vis.js
+++ b/src/legacy/core_plugins/vis_type_timeseries/public/components/vis_types/timeseries/vis.js
@@ -33,9 +33,8 @@ import { getAxisLabelString } from '../../lib/get_axis_label_string';
 import { getInterval } from '../../lib/get_interval';
 import { areFieldsDifferent } from '../../lib/charts';
 import { createXaxisFormatter } from '../../lib/create_xaxis_formatter';
-import { isBackgroundDark } from '../../../lib/set_is_reversed';
 import { STACKED_OPTIONS } from '../../../visualizations/constants';
-import { getCoreStart } from '../../../services';
+import { getCoreStart, getUISettings } from '../../../services';
 
 export class TimeseriesVisualization extends Component {
   static propTypes = {
@@ -238,6 +237,7 @@ export class TimeseriesVisualization extends Component {
       }
     });
 
+    const darkMode = getUISettings().get('theme:darkMode');
     return (
       <div className="tvbVis" style={styles.tvbVis}>
         <TimeSeries
@@ -245,7 +245,8 @@ export class TimeseriesVisualization extends Component {
           yAxis={yAxis}
           onBrush={onBrush}
           enableHistogramMode={enableHistogramMode}
-          isDarkMode={isBackgroundDark(model.background_color)}
+          backgroundColor={model.background_color}
+          darkMode={darkMode}
           showGrid={Boolean(model.show_grid)}
           legend={Boolean(model.show_legend)}
           legendPosition={model.legend_position}
diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/timeseries/__mocks__/@elastic/charts.js b/src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/timeseries/__mocks__/@elastic/charts.js
index d7f96cf4354b5..19bfe685cac90 100644
--- a/src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/timeseries/__mocks__/@elastic/charts.js
+++ b/src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/timeseries/__mocks__/@elastic/charts.js
@@ -40,3 +40,5 @@ export const ScaleType = {
 
 export const BarSeries = () => null;
 export const AreaSeries = () => null;
+
+export { LIGHT_THEME, DARK_THEME } from '@elastic/charts';
diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/timeseries/index.js b/src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/timeseries/index.js
index 986111b462b35..75554a476bdea 100644
--- a/src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/timeseries/index.js
+++ b/src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/timeseries/index.js
@@ -19,14 +19,13 @@
 
 import React, { useEffect, useRef } from 'react';
 import PropTypes from 'prop-types';
+import classNames from 'classnames';
 
 import {
   Axis,
   Chart,
   Position,
   Settings,
-  DARK_THEME,
-  LIGHT_THEME,
   AnnotationDomainTypes,
   LineAnnotation,
   TooltipType,
@@ -40,6 +39,7 @@ import { GRID_LINE_CONFIG, ICON_TYPES_MAP, STACKED_OPTIONS } from '../../constan
 import { AreaSeriesDecorator } from './decorators/area_decorator';
 import { BarSeriesDecorator } from './decorators/bar_decorator';
 import { getStackAccessors } from './utils/stack_format';
+import { getTheme, getChartClasses } from './utils/theme';
 
 const generateAnnotationData = (values, formatter) =>
   values.map(({ key, docs }) => ({
@@ -57,7 +57,8 @@ const handleCursorUpdate = cursor => {
 };
 
 export const TimeSeries = ({
-  isDarkMode,
+  darkMode,
+  backgroundColor,
   showGrid,
   legend,
   legendPosition,
@@ -89,8 +90,13 @@ export const TimeSeries = ({
   const timeZone = timezoneProvider(uiSettings)();
   const hasBarChart = series.some(({ bars }) => bars.show);
 
+  // compute the theme based on the bg color
+  const theme = getTheme(darkMode, backgroundColor);
+  // apply legend style change if bgColor is configured
+  const classes = classNames('tvbVisTimeSeries', getChartClasses(backgroundColor));
+
   return (
-    <Chart ref={chartRef} renderer="canvas" className="tvbVisTimeSeries">
+    <Chart ref={chartRef} renderer="canvas" className={classes}>
       <Settings
         showLegend={legend}
         legendPosition={legendPosition}
@@ -108,7 +114,7 @@ export const TimeSeries = ({
                 },
               }
         }
-        baseTheme={isDarkMode ? DARK_THEME : LIGHT_THEME}
+        baseTheme={theme}
         tooltip={{
           snap: true,
           type: TooltipType.VerticalCursor,
@@ -240,7 +246,8 @@ TimeSeries.defaultProps = {
 };
 
 TimeSeries.propTypes = {
-  isDarkMode: PropTypes.bool,
+  darkMode: PropTypes.bool,
+  backgroundColor: PropTypes.string,
   showGrid: PropTypes.bool,
   legend: PropTypes.bool,
   legendPosition: PropTypes.string,
diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/timeseries/utils/theme.test.ts b/src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/timeseries/utils/theme.test.ts
new file mode 100644
index 0000000000000..57ca38168ac27
--- /dev/null
+++ b/src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/timeseries/utils/theme.test.ts
@@ -0,0 +1,44 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import { getTheme } from './theme';
+import { LIGHT_THEME, DARK_THEME } from '@elastic/charts';
+
+describe('TSVB theme', () => {
+  it('should return the basic themes if no bg color is specified', () => {
+    // use original dark/light theme
+    expect(getTheme(false)).toEqual(LIGHT_THEME);
+    expect(getTheme(true)).toEqual(DARK_THEME);
+
+    // discard any wrong/missing bg color
+    expect(getTheme(true, null)).toEqual(DARK_THEME);
+    expect(getTheme(true, '')).toEqual(DARK_THEME);
+    expect(getTheme(true, undefined)).toEqual(DARK_THEME);
+  });
+  it('should return a highcontrast color theme for a different background', () => {
+    // red use a near full-black color
+    expect(getTheme(false, 'red').axes.axisTitleStyle.fill).toEqual('rgb(23,23,23)');
+
+    // violet increased the text color to full white for higer contrast
+    expect(getTheme(false, '#ba26ff').axes.axisTitleStyle.fill).toEqual('rgb(255,255,255)');
+
+    // light yellow, prefer the LIGHT_THEME fill color because already with a good contrast
+    expect(getTheme(false, '#fff49f').axes.axisTitleStyle.fill).toEqual('#333');
+  });
+});
diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/timeseries/utils/theme.ts b/src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/timeseries/utils/theme.ts
new file mode 100644
index 0000000000000..a25d5e1ce1d35
--- /dev/null
+++ b/src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/timeseries/utils/theme.ts
@@ -0,0 +1,139 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+import colorJS from 'color';
+import { Theme, LIGHT_THEME, DARK_THEME } from '@elastic/charts';
+
+function computeRelativeLuminosity(rgb: string) {
+  return colorJS(rgb).luminosity();
+}
+
+function computeContrast(rgb1: string, rgb2: string) {
+  return colorJS(rgb1).contrast(colorJS(rgb2));
+}
+
+function getAAARelativeLum(bgColor: string, fgColor: string, ratio = 7) {
+  const relLum1 = computeRelativeLuminosity(bgColor);
+  const relLum2 = computeRelativeLuminosity(fgColor);
+  if (relLum1 > relLum2) {
+    // relLum1 is brighter, relLum2 is darker
+    return (relLum1 + 0.05 - ratio * 0.05) / ratio;
+  } else {
+    // relLum1 is darker, relLum2 is brighter
+    return Math.min(ratio * (relLum1 + 0.05) - 0.05, 1);
+  }
+}
+
+function getGrayFromRelLum(relLum: number) {
+  if (relLum <= 0.0031308) {
+    return relLum * 12.92;
+  } else {
+    return (1.0 + 0.055) * Math.pow(relLum, 1.0 / 2.4) - 0.055;
+  }
+}
+
+function getGrayRGBfromGray(gray: number) {
+  const g = Math.round(gray * 255);
+  return `rgb(${g},${g},${g})`;
+}
+
+function getAAAGray(bgColor: string, fgColor: string, ratio = 7) {
+  const relLum = getAAARelativeLum(bgColor, fgColor, ratio);
+  const gray = getGrayFromRelLum(relLum);
+  return getGrayRGBfromGray(gray);
+}
+
+function findBestContrastColor(
+  bgColor: string,
+  lightFgColor: string,
+  darkFgColor: string,
+  ratio = 4.5
+) {
+  const lc = computeContrast(bgColor, lightFgColor);
+  const dc = computeContrast(bgColor, darkFgColor);
+  if (lc >= dc) {
+    if (lc >= ratio) {
+      return lightFgColor;
+    }
+    return getAAAGray(bgColor, lightFgColor, ratio);
+  }
+  if (dc >= ratio) {
+    return darkFgColor;
+  }
+  return getAAAGray(bgColor, darkFgColor, ratio);
+}
+
+function isValidColor(color: string | null | undefined): color is string {
+  if (typeof color !== 'string') {
+    return false;
+  }
+  if (color.length === 0) {
+    return false;
+  }
+  try {
+    colorJS(color);
+    return true;
+  } catch {
+    return false;
+  }
+}
+
+export function getTheme(darkMode: boolean, bgColor?: string | null): Theme {
+  if (!isValidColor(bgColor)) {
+    return darkMode ? DARK_THEME : LIGHT_THEME;
+  }
+
+  const bgLuminosity = computeRelativeLuminosity(bgColor);
+  const mainTheme = bgLuminosity <= 0.179 ? DARK_THEME : LIGHT_THEME;
+  const color = findBestContrastColor(
+    bgColor,
+    LIGHT_THEME.axes.axisTitleStyle.fill,
+    DARK_THEME.axes.axisTitleStyle.fill
+  );
+  return {
+    ...mainTheme,
+    axes: {
+      ...mainTheme.axes,
+      axisTitleStyle: {
+        ...mainTheme.axes.axisTitleStyle,
+        fill: color,
+      },
+      tickLabelStyle: {
+        ...mainTheme.axes.tickLabelStyle,
+        fill: color,
+      },
+      axisLineStyle: {
+        ...mainTheme.axes.axisLineStyle,
+        stroke: color,
+      },
+      tickLineStyle: {
+        ...mainTheme.axes.tickLineStyle,
+        stroke: color,
+      },
+    },
+  };
+}
+
+export function getChartClasses(bgColor?: string) {
+  // keep the original theme color if no bg color is specified
+  if (typeof bgColor !== 'string') {
+    return;
+  }
+  const bgLuminosity = computeRelativeLuminosity(bgColor);
+  return bgLuminosity <= 0.179 ? 'tvbVisTimeSeriesDark' : 'tvbVisTimeSeriesLight';
+}