You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@superset.apache.org by jl...@apache.org on 2026/04/09 23:40:43 UTC

(superset) 01/01: fix(embedded): prevent OS dark mode from applying dark borders in light-mode embedded dashboards

This is an automated email from the ASF dual-hosted git repository.

jli pushed a commit to branch sc-100647-dark-border-embedded-dark-mode
in repository https://gitbox.apache.org/repos/asf/superset.git

commit 442b3fb0f74983aafe6252b5772e2093ebd53253
Author: sadpandajoe <jc...@gmail.com>
AuthorDate: Thu Apr 9 23:40:30 2026 +0000

    fix(embedded): prevent OS dark mode from applying dark borders in light-mode embedded dashboards
    
    When the OS is in dark mode, browsers apply dark native styling (scrollbars,
    form control borders, etc.) to iframes unless the page explicitly declares its
    color-scheme. Without this declaration, embedded dashboards using Superset's
    default light theme still rendered with dark borders/lines on macOS dark mode.
    
    Fix: set `color-scheme: light` (or `dark` for dark themes) on the `html`
    element via GlobalStyles, using the existing `isThemeDark` utility to
    determine the correct value from the active Ant Design theme tokens.
    
    Closes sc-100647
    
    Co-Authored-By: Claude Sonnet 4.6 <no...@anthropic.com>
---
 .../superset-core/src/theme/GlobalStyles.test.tsx  | 87 ++++++++++++++++++++++
 .../superset-core/src/theme/GlobalStyles.tsx       | 13 +++-
 2 files changed, 99 insertions(+), 1 deletion(-)

diff --git a/superset-frontend/packages/superset-core/src/theme/GlobalStyles.test.tsx b/superset-frontend/packages/superset-core/src/theme/GlobalStyles.test.tsx
new file mode 100644
index 00000000000..d8a0822bc88
--- /dev/null
+++ b/superset-frontend/packages/superset-core/src/theme/GlobalStyles.test.tsx
@@ -0,0 +1,87 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF 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 React from 'react';
+import { render } from '@testing-library/react';
+import { ThemeProvider } from '@emotion/react';
+import { Theme } from './Theme';
+import { GlobalStyles } from './GlobalStyles';
+import { ThemeAlgorithm } from './types';
+import * as themeUtils from './utils/themeUtils';
+
+// Mock emotion's cache to avoid actual DOM operations
+jest.mock('@emotion/cache', () => ({
+  __esModule: true,
+  default: jest.fn().mockReturnValue({}),
+}));
+
+beforeEach(() => {
+  jest.clearAllMocks();
+});
+
+test('GlobalStyles calls isThemeDark with the current theme to determine color-scheme', () => {
+  const isThemeDarkSpy = jest.spyOn(themeUtils, 'isThemeDark');
+  const lightTheme = Theme.fromConfig({});
+
+  render(
+    <ThemeProvider theme={lightTheme.theme}>
+      <GlobalStyles />
+    </ThemeProvider>,
+  );
+
+  expect(isThemeDarkSpy).toHaveBeenCalledWith(lightTheme.theme);
+  expect(isThemeDarkSpy).toHaveReturnedWith(false);
+});
+
+test('GlobalStyles detects dark theme via isThemeDark', () => {
+  const isThemeDarkSpy = jest.spyOn(themeUtils, 'isThemeDark');
+  const darkTheme = Theme.fromConfig({ algorithm: ThemeAlgorithm.DARK });
+
+  render(
+    <ThemeProvider theme={darkTheme.theme}>
+      <GlobalStyles />
+    </ThemeProvider>,
+  );
+
+  expect(isThemeDarkSpy).toHaveBeenCalledWith(darkTheme.theme);
+  expect(isThemeDarkSpy).toHaveReturnedWith(true);
+});
+
+test('GlobalStyles renders without errors for light theme', () => {
+  const lightTheme = Theme.fromConfig({});
+
+  expect(() => {
+    render(
+      <ThemeProvider theme={lightTheme.theme}>
+        <GlobalStyles />
+      </ThemeProvider>,
+    );
+  }).not.toThrow();
+});
+
+test('GlobalStyles renders without errors for dark theme', () => {
+  const darkTheme = Theme.fromConfig({ algorithm: ThemeAlgorithm.DARK });
+
+  expect(() => {
+    render(
+      <ThemeProvider theme={darkTheme.theme}>
+        <GlobalStyles />
+      </ThemeProvider>,
+    );
+  }).not.toThrow();
+});
diff --git a/superset-frontend/packages/superset-core/src/theme/GlobalStyles.tsx b/superset-frontend/packages/superset-core/src/theme/GlobalStyles.tsx
index 33a30a9fa61..bcdcc8a8697 100644
--- a/superset-frontend/packages/superset-core/src/theme/GlobalStyles.tsx
+++ b/superset-frontend/packages/superset-core/src/theme/GlobalStyles.tsx
@@ -29,13 +29,24 @@ import '@fontsource/ibm-plex-mono/600.css';
 /* eslint-enable import/extensions */
 
 import { css, useTheme, Global } from '@emotion/react';
+import type { SupersetTheme } from './types';
+import { isThemeDark } from './utils/themeUtils';
 
 export const GlobalStyles = () => {
-  const theme = useTheme();
+  const theme = useTheme() as SupersetTheme;
+  const isDark = isThemeDark(theme);
   return (
     <Global
       key={`global-${theme.colorLink}`}
       styles={css`
+        // Explicitly declare color-scheme so the browser uses matching native
+        // styling (scrollbars, form controls, etc.) rather than inheriting the
+        // OS dark-mode preference. Without this, an embedded iframe renders
+        // dark borders/scrollbars even when Superset's theme is light.
+        html {
+          color-scheme: ${isDark ? 'dark' : 'light'};
+        }
+
         // SPA
         html,
         body,