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

chanholee pushed a commit to branch branch-0.12
in repository https://gitbox.apache.org/repos/asf/zeppelin.git


The following commit(s) were added to refs/heads/branch-0.12 by this push:
     new 26eb10ab88 [ZEPPELIN-6323] Apply dark mode to the new UI
26eb10ab88 is described below

commit 26eb10ab88fe5ab79ca64a1151938eb629f44d92
Author: YONGJAE LEE(이용재) <[email protected]>
AuthorDate: Sun Oct 5 17:03:06 2025 +0900

    [ZEPPELIN-6323] Apply dark mode to the new UI
    
    ### What is this PR for?
    This PR adds dark mode support and system theme integration for the 
Zeppelin New UI. There were multiple demands such as 
[ZEPPELIN-5062](https://issues.apache.org/jira/browse/ZEPPELIN-5602) and 
[ZEPPELIN-4024](https://issues.apache.org/jira/browse/ZEPPELIN-4024).
    
    #### Example: Follow system theme + other parts
    
    
https://github.com/user-attachments/assets/2159d54e-6403-4f80-91f0-3f66a93881e1
    
    #### Example: Change with button + notebook
    
    
https://github.com/user-attachments/assets/59bdf1bc-86a3-42e1-a3d0-1d89d955ed7d
    
    #### Automatic System Theme Detection & Sync
    - Automatically detect OS-level dark/light mode settings
    - Real-time detection and application of system theme changes
    - Theme cycle pattern: `auto(system) → opposite theme → original theme → 
auto`
    
    #### Comprehensive Dark Mode UI Support
    - Applied dark mode styles across all major components
    - Added dark mode overrides for Ant Design components
    - Full Monaco Editor dark theme support
    - Consistent color scheme and visual hierarchy
    
    #### Enhanced User Experience
    - Eliminated FOUC (Flash of Unstyled Content) with logic handled in 
`index.html`
    - Easy theme switching via a toggle button
    - Persisted user preferences in local storage
    - Theme state maintained after page reloads
    
    With dark mode support and system theme integration, Zeppelin delivers a 
modern, user-friendly experience. Users can either rely on system theme 
settings for automatic adaptation or manually select their preferred theme.
    
    ### What type of PR is it?
    Improvement
    
    ### Todos
    * [ ] I created a dark mode [background 
image](https://github.com/dididy/zeppelin/blob/5d078a40c17e0561202e809da0761016516b86f2/zeppelin-web-angular/src/assets/images/bg-dark.png)
 used for login and loading with ChatGPT, but I need to verify whether there 
are any copyright issues.
    * [ ] Due to the limited environment setup, I wasn’t able to check all the 
cases where graphs are rendered. I think we can leave this as a follow-up issue 
to work on later.
    
    ### What is the Jira issue?
    * [[ZEPPELIN-6323](https://issues.apache.org/jira/browse/ZEPPELIN-6323)]
    
    ### How should this be tested?
    
    ### Screenshots (if appropriate)
    
    ### Questions:
    * Does the license files need to update? No
    * Is there breaking changes for older versions? No
    * Does this needs documentation? No
    
    Closes #5078 from dididy/feat/darkmode.
    
    Signed-off-by: ChanHo Lee <[email protected]>
    (cherry picked from commit eee7ebb6887ed6d1e1881396222628a7d2ca9bf8)
    Signed-off-by: ChanHo Lee <[email protected]>
---
 LICENSE                                            |   1 +
 pom.xml                                            |   1 +
 zeppelin-web-angular/angular.json                  |   1 +
 zeppelin-web-angular/e2e/models/theme.page.ts      |  61 ++++
 .../e2e/tests/theme/dark-mode.spec.ts              | 130 ++++++++
 zeppelin-web-angular/e2e/utils.ts                  |   3 +-
 zeppelin-web-angular/src/app/app.component.ts      |  12 +-
 zeppelin-web-angular/src/app/languages/load.ts     |  12 +-
 .../src/app/pages/login/login.component.less       |  17 +-
 .../interpreter/interpreter.component.less         |  24 +-
 .../add-paragraph/add-paragraph.component.less     |  15 +-
 .../pages/workspace/notebook/notebook.component.ts |   5 +-
 .../src/app/services/public-api.ts                 |   1 +
 .../src/app/services/theme.service.ts              | 161 +++++++++
 .../src/app/share/header/header.component.html     |   1 +
 .../src/app/share/header/header.component.less     |   5 +
 zeppelin-web-angular/src/app/share/share.module.ts |   2 +
 .../share/theme-toggle/theme-toggle.component.html |  19 ++
 .../share/theme-toggle/theme-toggle.component.less |  77 +++++
 .../share/theme-toggle/theme-toggle.component.ts   |  63 ++++
 .../src/assets/fonts/MaterialSymbolsOutlined.woff2 | Bin 0 -> 286056 bytes
 .../src/assets/fonts/material-symbols-outlined.css |  36 ++
 zeppelin-web-angular/src/assets/images/bg-dark.png | Bin 0 -> 1841188 bytes
 zeppelin-web-angular/src/index.html                |  14 +
 zeppelin-web-angular/src/styles/spin.less          |   8 +
 .../src/styles/theme/dark-theme-overrides.css      | 363 +++++++++++++++++++++
 .../src/styles/theme/dark/theme-dark.less          |  94 +++---
 .../src/styles/theme/markdown.less                 |  16 +-
 28 files changed, 1063 insertions(+), 79 deletions(-)

diff --git a/LICENSE b/LICENSE
index 4668af7ee2..3c3f246917 100644
--- a/LICENSE
+++ b/LICENSE
@@ -243,6 +243,7 @@ The text of each license is also included at 
licenses/LICENSE-[project]-[version
     (Apache 2.0) Embedded MongoDB 
(https://github.com/flapdoodle-oss/de.flapdoodle.embed.mongo)
     (Apache 2.0) s3proxy (https://github.com/gaul/s3proxy)
     (Apache 2.0) kubernetes-client 
(https://github.com/fabric8io/kubernetes-client)
+    (Apache 2.0) Material Symbols Outlined (https://fonts.google.com/icons)
 
 ========================================================================
 BSD 3-Clause licenses
diff --git a/pom.xml b/pom.xml
index a992a32cc0..411a12e133 100644
--- a/pom.xml
+++ b/pom.xml
@@ -1028,6 +1028,7 @@
               <exclude>**/src/fonts/simple-line*</exclude>
               <exclude>**/src/fonts/Source-Code-Pro*</exclude>
               <exclude>**/src/fonts/source-code-pro*</exclude>
+              
<exclude>**/src/assets/fonts/MaterialSymbolsOutlined.woff2</exclude>
               <exclude>**/src/**/**.test.js</exclude>
               <exclude>**/e2e/**/**.spec.js</exclude>
               <exclude>package-lock.json</exclude>
diff --git a/zeppelin-web-angular/angular.json 
b/zeppelin-web-angular/angular.json
index 83f0a378ce..1f669e329d 100644
--- a/zeppelin-web-angular/angular.json
+++ b/zeppelin-web-angular/angular.json
@@ -68,6 +68,7 @@
               }
             ],
             "styles": [
+              "src/styles/theme/dark-theme-overrides.css",
               "src/styles/theme/dark/antd-dark.less",
               "src/styles/theme/light/antd-light.less",
               "src/styles.less",
diff --git a/zeppelin-web-angular/e2e/models/theme.page.ts 
b/zeppelin-web-angular/e2e/models/theme.page.ts
new file mode 100644
index 0000000000..5285ac4590
--- /dev/null
+++ b/zeppelin-web-angular/e2e/models/theme.page.ts
@@ -0,0 +1,61 @@
+/*
+ * Licensed 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 { expect, Locator, Page } from '@playwright/test';
+
+export class ThemePage {
+  readonly page: Page;
+  readonly themeToggleButton: Locator;
+  readonly rootElement: Locator;
+
+  constructor(page: Page) {
+    this.page = page;
+    this.themeToggleButton = page.locator('zeppelin-theme-toggle button');
+    this.rootElement = page.locator('html');
+  }
+
+  async toggleTheme() {
+    await this.themeToggleButton.click();
+  }
+
+  async assertDarkTheme() {
+    await expect(this.rootElement).toHaveClass(/dark/);
+    await expect(this.rootElement).toHaveAttribute('data-theme', 'dark');
+    await expect(this.themeToggleButton).toHaveText('dark_mode');
+  }
+
+  async assertLightTheme() {
+    await expect(this.rootElement).toHaveClass(/light/);
+    await expect(this.rootElement).toHaveAttribute('data-theme', 'light');
+    await expect(this.themeToggleButton).toHaveText('light_mode');
+  }
+
+  async assertSystemTheme() {
+    await expect(this.themeToggleButton).toHaveText('smart_toy');
+  }
+
+  async setThemeInLocalStorage(theme: 'light' | 'dark' | 'system') {
+    await this.page.evaluate(themeValue => {
+      if (typeof window !== 'undefined' && window.localStorage) {
+        window.localStorage.setItem('zeppelin-theme', themeValue);
+      }
+    }, theme);
+  }
+
+  async clearLocalStorage() {
+    await this.page.evaluate(() => {
+      if (typeof window !== 'undefined' && window.localStorage) {
+        window.localStorage.clear();
+      }
+    });
+  }
+}
diff --git a/zeppelin-web-angular/e2e/tests/theme/dark-mode.spec.ts 
b/zeppelin-web-angular/e2e/tests/theme/dark-mode.spec.ts
new file mode 100644
index 0000000000..4dedf66218
--- /dev/null
+++ b/zeppelin-web-angular/e2e/tests/theme/dark-mode.spec.ts
@@ -0,0 +1,130 @@
+/*
+ * Licensed 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 { expect, test } from '@playwright/test';
+import { ZeppelinHelper } from '../../helper';
+import { ThemePage } from '../../models/theme.page';
+import { addPageAnnotationBeforeEach, PAGES } from '../../utils';
+
+test.describe('Dark Mode Theme Switching', () => {
+  addPageAnnotationBeforeEach(PAGES.SHARE.THEME_TOGGLE);
+  let zeppelinHelper: ZeppelinHelper;
+  let themePage: ThemePage;
+
+  test.beforeEach(async ({ page }) => {
+    zeppelinHelper = new ZeppelinHelper(page);
+    themePage = new ThemePage(page);
+    await page.goto('/', { waitUntil: 'load' });
+    await zeppelinHelper.waitForZeppelinReady();
+    // Ensure a clean localStorage for each test
+    await themePage.clearLocalStorage();
+  });
+
+  test('Scenario: User can switch to dark mode and persistence is maintained', 
async ({ page }) => {
+    // GIVEN: User is on the main page, which starts in 'system' mode by 
default (localStorage cleared).
+    await test.step('GIVEN the page starts in system mode', async () => {
+      await themePage.assertSystemTheme(); // Robot icon for system theme
+    });
+
+    // WHEN: Explicitly set theme to light mode for the rest of the test.
+    await test.step('WHEN the user explicitly sets theme to light mode', async 
() => {
+      await themePage.setThemeInLocalStorage('light');
+      await page.reload();
+      await zeppelinHelper.waitForZeppelinReady();
+      await themePage.assertLightTheme(); // Now it should be light mode with 
sun icon
+    });
+
+    // WHEN: User switches to dark mode by setting localStorage and reloading.
+    await test.step('WHEN the user switches to dark mode', async () => {
+      await themePage.setThemeInLocalStorage('dark');
+      await page.reload();
+      await zeppelinHelper.waitForZeppelinReady();
+    });
+
+    // THEN: The theme changes to dark mode.
+    await test.step('THEN the page switches to dark mode', async () => {
+      await themePage.assertDarkTheme();
+    });
+
+    // AND: User refreshes the page.
+    await test.step('AND the user refreshes the page', async () => {
+      await page.reload();
+      await zeppelinHelper.waitForZeppelinReady();
+    });
+
+    // THEN: Dark mode is maintained after refresh.
+    await test.step('THEN dark mode is maintained after refresh', async () => {
+      await themePage.assertDarkTheme();
+    });
+
+    // AND: User clicks the toggle again to switch back to light mode.
+    await test.step('AND the user clicks the toggle to switch back to light 
mode', async () => {
+      await themePage.toggleTheme();
+    });
+
+    // THEN: The theme switches to system mode.
+    await test.step('THEN the theme switches to system mode', async () => {
+      await themePage.assertSystemTheme();
+    });
+  });
+
+  test('Scenario: System Theme and Local Storage Interaction', async ({ page, 
context }) => {
+    // Ensure localStorage is clear for each sub-scenario
+    await themePage.clearLocalStorage();
+
+    await test.step('GIVEN: No localStorage, System preference is Light', 
async () => {
+      await page.emulateMedia({ colorScheme: 'light' });
+      await page.goto('/', { waitUntil: 'load' });
+      await zeppelinHelper.waitForZeppelinReady();
+      // When no explicit theme is set, it defaults to 'system' mode
+      // Even in system mode with light preference, the icon should be robot
+      await expect(themePage.rootElement).toHaveClass(/light/);
+      await expect(themePage.rootElement).toHaveAttribute('data-theme', 
'light');
+      await themePage.assertSystemTheme(); // Should show robot icon
+    });
+
+    await test.step('GIVEN: No localStorage, System preference is Dark 
(initial system state)', async () => {
+      await themePage.setThemeInLocalStorage('system');
+      await page.goto('/', { waitUntil: 'load' });
+      await zeppelinHelper.waitForZeppelinReady();
+      await themePage.assertSystemTheme(); // Robot icon for system theme
+    });
+
+    await test.step("GIVEN: localStorage is 'dark', System preference is 
Light", async () => {
+      await themePage.setThemeInLocalStorage('dark');
+      await page.emulateMedia({ colorScheme: 'light' });
+      await page.goto('/', { waitUntil: 'load' });
+      await zeppelinHelper.waitForZeppelinReady();
+      await themePage.assertDarkTheme(); // localStorage should override system
+    });
+
+    await test.step("GIVEN: localStorage is 'system', THEN: Emulate system 
preference change to Light", async () => {
+      await themePage.setThemeInLocalStorage('system');
+      await page.emulateMedia({ colorScheme: 'light' });
+      await page.goto('/', { waitUntil: 'load' });
+      await zeppelinHelper.waitForZeppelinReady();
+      await expect(themePage.rootElement).toHaveClass(/light/);
+      await expect(themePage.rootElement).toHaveAttribute('data-theme', 
'light');
+      await themePage.assertSystemTheme(); // Robot icon for system theme
+    });
+
+    await test.step("GIVEN: localStorage is 'system', THEN: Emulate system 
preference change to Dark", async () => {
+      await themePage.setThemeInLocalStorage('system');
+      await page.emulateMedia({ colorScheme: 'dark' });
+      await page.goto('/', { waitUntil: 'load' });
+      await zeppelinHelper.waitForZeppelinReady();
+      await expect(themePage.rootElement).toHaveClass(/dark/);
+      await expect(themePage.rootElement).toHaveAttribute('data-theme', 
'dark');
+      await themePage.assertSystemTheme(); // Robot icon for system theme
+    });
+  });
+});
diff --git a/zeppelin-web-angular/e2e/utils.ts 
b/zeppelin-web-angular/e2e/utils.ts
index c5ceec8442..652cedc53f 100644
--- a/zeppelin-web-angular/e2e/utils.ts
+++ b/zeppelin-web-angular/e2e/utils.ts
@@ -76,7 +76,8 @@ export const PAGES = {
     PAGE_HEADER: 'src/app/share/page-header/page-header.component',
     RESIZE_HANDLE: 'src/app/share/resize-handle/resize-handle.component',
     SHORTCUT: 'src/app/share/shortcut/shortcut.component',
-    SPIN: 'src/app/share/spin/spin.component'
+    SPIN: 'src/app/share/spin/spin.component',
+    THEME_TOGGLE: 'src/app/share/theme-toggle/theme-toggle.component'
   },
 
   // Visualizations
diff --git a/zeppelin-web-angular/src/app/app.component.ts 
b/zeppelin-web-angular/src/app/app.component.ts
index dc9fcb3afb..c5044b6a76 100644
--- a/zeppelin-web-angular/src/app/app.component.ts
+++ b/zeppelin-web-angular/src/app/app.component.ts
@@ -10,18 +10,18 @@
  * limitations under the License.
  */
 
-import { Component } from '@angular/core';
+import { Component, OnInit } from '@angular/core';
 import { NavigationEnd, NavigationStart, Router } from '@angular/router';
 import { filter, map } from 'rxjs/operators';
 
-import { TicketService } from '@zeppelin/services';
+import { ThemeService, TicketService } from '@zeppelin/services';
 
 @Component({
   selector: 'zeppelin-root',
   templateUrl: './app.component.html',
   styleUrls: ['./app.component.less']
 })
-export class AppComponent {
+export class AppComponent implements OnInit {
   logout$ = this.ticketService.logout$;
   loading$ = this.router.events.pipe(
     filter(data => data instanceof NavigationEnd || data instanceof 
NavigationStart),
@@ -35,5 +35,9 @@ export class AppComponent {
     })
   );
 
-  constructor(private router: Router, private ticketService: TicketService) {}
+  constructor(private router: Router, private ticketService: TicketService, 
private themeService: ThemeService) {}
+
+  ngOnInit(): void {
+    this.themeService.updateMonacoTheme();
+  }
 }
diff --git a/zeppelin-web-angular/src/app/languages/load.ts 
b/zeppelin-web-angular/src/app/languages/load.ts
index 64c617acd6..f945f608e1 100644
--- a/zeppelin-web-angular/src/app/languages/load.ts
+++ b/zeppelin-web-angular/src/app/languages/load.ts
@@ -14,15 +14,9 @@ import { editor, languages } from 'monaco-editor';
 import { conf as ScalaConf, language as ScalaLanguage } from './scala';
 
 export const loadMonacoBefore = () => {
-  editor.defineTheme('zeppelin-theme', {
-    base: 'vs',
-    inherit: true,
-    rules: [],
-    colors: {
-      'editor.lineHighlightBackground': '#0000FF10'
-    }
-  });
-  editor.setTheme('zeppelin-theme');
+  const savedTheme = localStorage.getItem('zeppelin-theme') || 'light';
+  const monacoTheme = savedTheme === 'dark' ? 'vs-dark' : 'vs';
+  editor.setTheme(monacoTheme);
   languages.register({ id: 'scala' });
   languages.setMonarchTokensProvider('scala', ScalaLanguage);
   languages.setLanguageConfiguration('scala', ScalaConf);
diff --git a/zeppelin-web-angular/src/app/pages/login/login.component.less 
b/zeppelin-web-angular/src/app/pages/login/login.component.less
index 7aac210300..c4e89d0211 100644
--- a/zeppelin-web-angular/src/app/pages/login/login.component.less
+++ b/zeppelin-web-angular/src/app/pages/login/login.component.less
@@ -25,7 +25,6 @@
       left: 0;
       width: 100%;
       height: 100%;
-      background-image: url("../../../assets/images/bg.jpg");
       background-size: cover;
       filter: blur(4px);
       background-repeat: no-repeat;
@@ -67,3 +66,19 @@
     }
   }
 });
+
+:host-context(.light) {
+  .content {
+    &:after {
+      background-image: url("../../../assets/images/bg.jpg");
+    }
+  }
+}
+
+:host-context(.dark) {
+  .content {
+    &:after {
+      background-image: url("../../../assets/images/bg-dark.png");
+    }
+  }
+}
diff --git 
a/zeppelin-web-angular/src/app/pages/workspace/interpreter/interpreter.component.less
 
b/zeppelin-web-angular/src/app/pages/workspace/interpreter/interpreter.component.less
index ea488728b3..0132935731 100644
--- 
a/zeppelin-web-angular/src/app/pages/workspace/interpreter/interpreter.component.less
+++ 
b/zeppelin-web-angular/src/app/pages/workspace/interpreter/interpreter.component.less
@@ -29,11 +29,6 @@
     }
   }
 
-  .editable-tag {
-    background: @white;
-    border-style: dashed;
-  }
-
   .content {
     padding: @card-padding-base / 2;
 
@@ -43,3 +38,22 @@
     }
   }
 });
+
+:host-context(.light) {
+  background: #f0f0f0;
+  
+  .editable-tag {
+    background: #fff;
+    border-style: dashed;
+  }
+}
+
+:host-context(.dark) {
+  background: #141414;
+  
+  .editable-tag {
+    background: #1f1f1f;
+    border-style: dashed;
+    border-color: #434343;
+  }
+}
diff --git 
a/zeppelin-web-angular/src/app/pages/workspace/notebook/add-paragraph/add-paragraph.component.less
 
b/zeppelin-web-angular/src/app/pages/workspace/notebook/add-paragraph/add-paragraph.component.less
index 0ca115b8fe..e76e332e5c 100644
--- 
a/zeppelin-web-angular/src/app/pages/workspace/notebook/add-paragraph/add-paragraph.component.less
+++ 
b/zeppelin-web-angular/src/app/pages/workspace/notebook/add-paragraph/add-paragraph.component.less
@@ -18,7 +18,7 @@
     text-align: center;
     color: @primary-color;
     font-weight: 500;
-    position: relative;;
+    position: relative;
 
     .inner {
       position: absolute;
@@ -29,7 +29,6 @@
       z-index: 10;
       display: none;
       line-height: 30px;
-      background: @blue-1;
       border: 1px solid @border-color-split;
       box-shadow: @btn-shadow;
       padding: 0 12px;
@@ -47,3 +46,15 @@
     }
   }
 });
+
+:host-context(.light) {
+  .add-paragraph .inner {
+    background: @blue-1;
+  }
+}
+
+:host-context(.dark) {
+  .add-paragraph .inner {
+    background: #141414;
+  }
+}
diff --git 
a/zeppelin-web-angular/src/app/pages/workspace/notebook/notebook.component.ts 
b/zeppelin-web-angular/src/app/pages/workspace/notebook/notebook.component.ts
index a9e7d6d968..33e8e9385d 100644
--- 
a/zeppelin-web-angular/src/app/pages/workspace/notebook/notebook.component.ts
+++ 
b/zeppelin-web-angular/src/app/pages/workspace/notebook/notebook.component.ts
@@ -43,6 +43,7 @@ import {
   NoteStatusService,
   NoteVarShareService,
   SecurityService,
+  ThemeService,
   TicketService
 } from '@zeppelin/services';
 
@@ -105,6 +106,7 @@ export class NotebookComponent extends 
MessageListenersManager implements OnInit
         });
       }
       this.titleService.setTitle(this.note?.name + ' - Zeppelin');
+      this.themeService.updateMonacoTheme();
       this.cdr.markForCheck();
     }
   }
@@ -408,7 +410,8 @@ export class NotebookComponent extends 
MessageListenersManager implements OnInit
     private ticketService: TicketService,
     private securityService: SecurityService,
     private router: Router,
-    private titleService: Title
+    private titleService: Title,
+    private themeService: ThemeService
   ) {
     super(messageService);
   }
diff --git a/zeppelin-web-angular/src/app/services/public-api.ts 
b/zeppelin-web-angular/src/app/services/public-api.ts
index 48b5f40849..a554dc9cf4 100644
--- a/zeppelin-web-angular/src/app/services/public-api.ts
+++ b/zeppelin-web-angular/src/app/services/public-api.ts
@@ -31,4 +31,5 @@ export * from './runtime-compiler.service';
 export * from './save-as.service';
 export * from './security.service';
 export * from './shortcut.service';
+export * from './theme.service';
 export * from './ticket.service';
diff --git a/zeppelin-web-angular/src/app/services/theme.service.ts 
b/zeppelin-web-angular/src/app/services/theme.service.ts
new file mode 100644
index 0000000000..ae147412cd
--- /dev/null
+++ b/zeppelin-web-angular/src/app/services/theme.service.ts
@@ -0,0 +1,161 @@
+/*
+ * Licensed 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 { Injectable, OnDestroy } from '@angular/core';
+import { combineLatest, fromEvent, BehaviorSubject, Observable, Subscription } 
from 'rxjs';
+import { distinctUntilChanged, map, startWith } from 'rxjs/operators';
+
+export type ThemeMode = 'light' | 'dark' | 'system';
+
+const THEME_STORAGE_KEY = 'zeppelin-theme';
+const MONACO_THEMES = {
+  light: 'vs',
+  dark: 'vs-dark'
+} as const;
+
+@Injectable({
+  providedIn: 'root'
+})
+export class ThemeService implements OnDestroy {
+  private themeSubject: BehaviorSubject<ThemeMode>;
+  private effectiveThemeSubject: BehaviorSubject<'light' | 'dark'>;
+  private systemStartedWith: 'light' | 'dark' | null = null;
+  private subscriptions = new Subscription();
+
+  public theme$: Observable<ThemeMode>;
+  public effectiveTheme$: Observable<'light' | 'dark'>;
+
+  ngOnDestroy() {
+    this.subscriptions.unsubscribe();
+    this.themeSubject.complete();
+    this.effectiveThemeSubject.complete();
+  }
+
+  constructor() {
+    const initialTheme = this.detectInitialTheme();
+    this.themeSubject = new BehaviorSubject<ThemeMode>(initialTheme);
+    this.theme$ = this.themeSubject.asObservable();
+
+    // Create observable for system theme changes
+    const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
+    const systemTheme$ = fromEvent<MediaQueryListEvent>(mediaQuery, 
'change').pipe(
+      map(e => (e.matches ? ('dark' as const) : ('light' as const))),
+      startWith(mediaQuery.matches ? ('dark' as const) : ('light' as const)),
+      distinctUntilChanged()
+    );
+
+    // Calculate initial effective theme
+    const initialEffectiveTheme = this.resolveEffectiveTheme(initialTheme, 
mediaQuery.matches);
+    this.effectiveThemeSubject = new BehaviorSubject<'light' | 
'dark'>(initialEffectiveTheme);
+    this.effectiveTheme$ = this.effectiveThemeSubject.asObservable();
+
+    // Reactively update effective theme when either theme or system theme 
changes
+    const subscription = combineLatest([this.theme$, systemTheme$])
+      .pipe(
+        map(([theme, systemTheme]) => (theme === 'system' ? systemTheme : 
theme)),
+        distinctUntilChanged()
+      )
+      .subscribe(effectiveTheme => {
+        this.effectiveThemeSubject.next(effectiveTheme);
+        this.applyTheme(effectiveTheme);
+      });
+
+    this.subscriptions.add(subscription);
+  }
+
+  private detectInitialTheme(): ThemeMode {
+    try {
+      const savedTheme = localStorage.getItem(THEME_STORAGE_KEY);
+      if (savedTheme && this.isValidTheme(savedTheme)) {
+        return savedTheme;
+      }
+      return 'system';
+    } catch {
+      return 'system';
+    }
+  }
+
+  private isValidTheme(theme: string): theme is ThemeMode {
+    return theme === 'light' || theme === 'dark' || theme === 'system';
+  }
+
+  private resolveEffectiveTheme(theme: ThemeMode, isDarkMode: boolean): 
'light' | 'dark' {
+    return theme === 'system' ? (isDarkMode ? 'dark' : 'light') : theme;
+  }
+
+  getCurrentTheme(): ThemeMode {
+    return this.themeSubject.value;
+  }
+
+  private getEffectiveTheme(): 'light' | 'dark' {
+    return this.effectiveThemeSubject.value;
+  }
+
+  private setTheme(theme: ThemeMode, save: boolean = true) {
+    if (this.themeSubject.value === theme) {
+      return;
+    }
+
+    this.themeSubject.next(theme);
+
+    if (save) {
+      localStorage.setItem(THEME_STORAGE_KEY, theme);
+    }
+  }
+
+  toggleTheme() {
+    const currentTheme = this.getCurrentTheme();
+    let nextTheme: ThemeMode;
+
+    if (currentTheme === 'light') {
+      nextTheme = 'dark';
+    } else if (currentTheme === 'dark') {
+      nextTheme = 'system';
+    } else {
+      nextTheme = 'light';
+    }
+
+    this.setTheme(nextTheme);
+  }
+
+  private applyTheme(effectiveTheme: 'light' | 'dark') {
+    const html = document.documentElement;
+    const body = document.body;
+
+    [html, body].forEach(el => {
+      el.classList.toggle('dark', effectiveTheme === 'dark');
+      el.classList.toggle('light', effectiveTheme === 'light');
+      el.setAttribute('data-theme', effectiveTheme);
+    });
+
+    html.style.setProperty('color-scheme', effectiveTheme);
+
+    this.updateMonacoTheme();
+  }
+
+  updateMonacoTheme() {
+    if (!monaco?.editor) {
+      return;
+    }
+
+    const effectiveTheme = this.getEffectiveTheme();
+
+    try {
+      // Fix editor not applying dark mode on first load when theme is set to 
"system"
+      requestAnimationFrame(() => {
+        monaco.editor.setTheme(MONACO_THEMES[effectiveTheme]);
+      });
+    } catch (error) {
+      console.error('Monaco theme setting failed:', error);
+    }
+  }
+}
diff --git a/zeppelin-web-angular/src/app/share/header/header.component.html 
b/zeppelin-web-angular/src/app/share/header/header.component.html
index f22da904a1..f0dd608aff 100644
--- a/zeppelin-web-angular/src/app/share/header/header.component.html
+++ b/zeppelin-web-angular/src/app/share/header/header.component.html
@@ -81,4 +81,5 @@
       <input type="text" nz-input placeholder="Search" 
(keyup.enter)="onSearch()" [(ngModel)]="queryStr" />
     </nz-input-group>
   </div>
+  <zeppelin-theme-toggle class="theme-toggle"></zeppelin-theme-toggle>
 </div>
diff --git a/zeppelin-web-angular/src/app/share/header/header.component.less 
b/zeppelin-web-angular/src/app/share/header/header.component.less
index e4754b15ec..73865211e3 100644
--- a/zeppelin-web-angular/src/app/share/header/header.component.less
+++ b/zeppelin-web-angular/src/app/share/header/header.component.less
@@ -75,6 +75,11 @@
     width: 300px;
     margin-right: 24px;
   }
+  .theme-toggle {
+    height: 100%;
+    display: flex;
+    float: right;
+  }
   .user {
     float: right;
 
diff --git a/zeppelin-web-angular/src/app/share/share.module.ts 
b/zeppelin-web-angular/src/app/share/share.module.ts
index f514a950eb..d60f60844f 100644
--- a/zeppelin-web-angular/src/app/share/share.module.ts
+++ b/zeppelin-web-angular/src/app/share/share.module.ts
@@ -54,6 +54,7 @@ import { ResizeHandleComponent } from './resize-handle';
 import { RunScriptsDirective } from './run-scripts/run-scripts.directive';
 import { ShortcutComponent } from './shortcut/shortcut.component';
 import { SpinComponent } from './spin/spin.component';
+import { ThemeToggleComponent } from './theme-toggle/theme-toggle.component';
 
 const MODAL_LIST = [
   AboutZeppelinComponent,
@@ -69,6 +70,7 @@ const EXPORT_LIST = [
   NoteTocComponent,
   PageHeaderComponent,
   SpinComponent,
+  ThemeToggleComponent,
   ResizeHandleComponent
 ];
 const PIPES = [HumanizeBytesPipe];
diff --git 
a/zeppelin-web-angular/src/app/share/theme-toggle/theme-toggle.component.html 
b/zeppelin-web-angular/src/app/share/theme-toggle/theme-toggle.component.html
new file mode 100644
index 0000000000..69a97fa813
--- /dev/null
+++ 
b/zeppelin-web-angular/src/app/share/theme-toggle/theme-toggle.component.html
@@ -0,0 +1,19 @@
+<!--
+  ~ Licensed 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.
+  -->
+
+<div class="theme-toggle-container">
+  <button nz-button nzType="link" nzSize="small" class="theme-toggle-button" 
(click)="toggleTheme()">
+    <span class="material-symbols-outlined theme-text">
+      {{ themeIconName }}
+    </span>
+  </button>
+</div>
diff --git 
a/zeppelin-web-angular/src/app/share/theme-toggle/theme-toggle.component.less 
b/zeppelin-web-angular/src/app/share/theme-toggle/theme-toggle.component.less
new file mode 100644
index 0000000000..32a24b0f50
--- /dev/null
+++ 
b/zeppelin-web-angular/src/app/share/theme-toggle/theme-toggle.component.less
@@ -0,0 +1,77 @@
+/*
+ * Licensed 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.
+ */
+
+.theme-toggle-container {
+  display: flex;
+  align-items: center;
+  height: 100%;
+  justify-content: center;
+
+  .theme-toggle-button {
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    border-radius: 4px;
+    transition: none;
+    border: 1px solid #d9d9d9;
+    position: relative;
+    overflow: hidden;
+    cursor: pointer;
+    height: 31px;
+    width: 31px;
+
+    &:hover {
+      background-color: #f5f5f5;
+      border-color: #1890ff;
+    }
+
+    &:active {
+      transform: scale(0.98);
+    }
+
+    .theme-icon {
+      font-size: 20px;
+      transition: none;
+    }
+
+    .theme-text {
+      font-size: 18px;
+    }
+
+    &:hover .theme-icon {
+      transform: none;
+    }
+  }
+}
+
+:host-context(.dark) .theme-toggle-container {
+  .theme-toggle-button {
+    color: #fff;
+    border-color: #434343;
+    background-color: #262626;
+
+    &:hover {
+      background-color: rgba(255, 255, 255, 0.08);
+      border-color: #1890ff;
+    }
+
+    &:focus {
+      background-color: rgba(255, 255, 255, 0.08);
+      border-color: #1890ff;
+      box-shadow: 0 0 0 2px rgba(24, 144, 255, 0.2);
+    }
+
+    &:active {
+      transform: scale(0.98);
+    }
+  }
+}
diff --git 
a/zeppelin-web-angular/src/app/share/theme-toggle/theme-toggle.component.ts 
b/zeppelin-web-angular/src/app/share/theme-toggle/theme-toggle.component.ts
new file mode 100644
index 0000000000..35991d05e2
--- /dev/null
+++ b/zeppelin-web-angular/src/app/share/theme-toggle/theme-toggle.component.ts
@@ -0,0 +1,63 @@
+/*
+ * Licensed 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 { ChangeDetectionStrategy, ChangeDetectorRef, Component, OnDestroy, 
OnInit } from '@angular/core';
+import { Subject } from 'rxjs';
+import { takeUntil } from 'rxjs/operators';
+
+import { ThemeMode, ThemeService } from '../../services/theme.service';
+
+@Component({
+  selector: 'zeppelin-theme-toggle',
+  templateUrl: './theme-toggle.component.html',
+  styleUrls: ['./theme-toggle.component.less'],
+  changeDetection: ChangeDetectionStrategy.OnPush
+})
+export class ThemeToggleComponent implements OnInit, OnDestroy {
+  private destroy$ = new Subject();
+  currentTheme: ThemeMode = 'light';
+  isDarkMode = false;
+
+  constructor(private themeService: ThemeService, private cdr: 
ChangeDetectorRef) {}
+
+  ngOnInit() {
+    this.currentTheme = this.themeService.getCurrentTheme();
+    this.isDarkMode = this.currentTheme === 'dark';
+
+    this.themeService.theme$.pipe(takeUntil(this.destroy$)).subscribe(theme => 
{
+      if (this.currentTheme !== theme) {
+        this.currentTheme = theme;
+        this.isDarkMode = theme === 'dark';
+        this.cdr.markForCheck();
+      }
+    });
+  }
+
+  ngOnDestroy() {
+    this.destroy$.next();
+    this.destroy$.complete();
+  }
+
+  toggleTheme() {
+    this.themeService.toggleTheme();
+  }
+
+  get themeIconName(): 'light_mode' | 'dark_mode' | 'smart_toy' {
+    if (this.currentTheme === 'light') {
+      return 'light_mode';
+    }
+    if (this.currentTheme === 'dark') {
+      return 'dark_mode';
+    }
+    return 'smart_toy';
+  }
+}
diff --git 
a/zeppelin-web-angular/src/assets/fonts/MaterialSymbolsOutlined.woff2 
b/zeppelin-web-angular/src/assets/fonts/MaterialSymbolsOutlined.woff2
new file mode 100644
index 0000000000..1c71ab04fb
Binary files /dev/null and 
b/zeppelin-web-angular/src/assets/fonts/MaterialSymbolsOutlined.woff2 differ
diff --git 
a/zeppelin-web-angular/src/assets/fonts/material-symbols-outlined.css 
b/zeppelin-web-angular/src/assets/fonts/material-symbols-outlined.css
new file mode 100644
index 0000000000..4b148cd0bc
--- /dev/null
+++ b/zeppelin-web-angular/src/assets/fonts/material-symbols-outlined.css
@@ -0,0 +1,36 @@
+/*
+ * Licensed 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.
+ */
+
+@font-face {
+  font-family: 'Material Symbols Outlined';
+  font-style: normal;
+  font-weight: 100 700;
+  font-display: block;
+  src: url('./MaterialSymbolsOutlined.woff2') format('woff2');
+}
+
+.material-symbols-outlined {
+  font-family: 'Material Symbols Outlined';
+  font-weight: normal;
+  font-style: normal;
+  font-size: 24px;
+  line-height: 1;
+  letter-spacing: normal;
+  text-transform: none;
+  display: inline-block;
+  white-space: nowrap;
+  word-wrap: normal;
+  direction: ltr;
+  -webkit-font-feature-settings: 'liga';
+  font-feature-settings: 'liga';
+  -webkit-font-smoothing: antialiased;
+}
diff --git a/zeppelin-web-angular/src/assets/images/bg-dark.png 
b/zeppelin-web-angular/src/assets/images/bg-dark.png
new file mode 100644
index 0000000000..53e428ae8a
Binary files /dev/null and b/zeppelin-web-angular/src/assets/images/bg-dark.png 
differ
diff --git a/zeppelin-web-angular/src/index.html 
b/zeppelin-web-angular/src/index.html
index 0e2b2a1604..3a9ee02fbc 100644
--- a/zeppelin-web-angular/src/index.html
+++ b/zeppelin-web-angular/src/index.html
@@ -16,7 +16,21 @@
     <meta charset="utf-8" />
     <meta name="viewport" content="width=device-width, initial-scale=1" />
     <link rel="icon" href="./assets/images/zeppelin.png" type="image/x-icon" />
+    <link rel="stylesheet" href="./assets/fonts/material-symbols-outlined.css" 
/>
     <title>Zeppelin</title>
+    <script>
+      // Prevent FOUC (Flash of Unstyled Content)
+      (function() {
+        const savedTheme = localStorage.getItem('zeppelin-theme');
+        const systemPrefersDark = window.matchMedia && 
window.matchMedia('(prefers-color-scheme: dark)').matches;
+        const isDark =
+          savedTheme === 'dark' || (savedTheme === 'system' && 
systemPrefersDark) || (!savedTheme && systemPrefersDark);
+
+        if (isDark) {
+          document.documentElement.classList.add('dark');
+        }
+      })();
+    </script>
   </head>
   <body>
     <zeppelin-root>
diff --git a/zeppelin-web-angular/src/styles/spin.less 
b/zeppelin-web-angular/src/styles/spin.less
index ec8c0a5a88..b0957c3813 100644
--- a/zeppelin-web-angular/src/styles/spin.less
+++ b/zeppelin-web-angular/src/styles/spin.less
@@ -31,6 +31,10 @@
     filter: blur(4px);
     background-repeat: no-repeat;
     background-position: center;
+
+    html.dark & {
+      background-image: url("../assets/images/bg-dark.png");
+    }
   }
 
   &.transparent {
@@ -40,6 +44,10 @@
   }
 
   & > div {
+    html.dark & {
+      background: unset;
+    }
+
     background: rgba(255, 255, 255, 0.5);
     text-align: center;
     position: absolute;
diff --git a/zeppelin-web-angular/src/styles/theme/dark-theme-overrides.css 
b/zeppelin-web-angular/src/styles/theme/dark-theme-overrides.css
new file mode 100644
index 0000000000..6b762b2315
--- /dev/null
+++ b/zeppelin-web-angular/src/styles/theme/dark-theme-overrides.css
@@ -0,0 +1,363 @@
+/*
+ * Licensed 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.
+ */
+
+/* Reset built-in transition */
+.ant-btn,
+.ant-input,
+.ant-input-affix-wrapper,
+.ant-input-group,
+.ant-input-group-addon,
+.ant-input-prefix,
+.ant-input-suffix,
+.ant-select,
+.ant-dropdown,
+.ant-menu,
+.ant-card,
+.ant-table,
+.ant-modal,
+.ant-drawer,
+.ant-tooltip,
+.ant-popover,
+.ant-notification,
+.ant-message,
+.ant-switch,
+.ant-radio,
+.ant-checkbox,
+.ant-tabs,
+.ant-collapse,
+.ant-slider,
+.ant-progress,
+.ant-badge,
+.ant-avatar,
+.ant-tag,
+.ant-alert,
+.ant-spin,
+.ant-pagination,
+.ant-breadcrumb,
+.ant-steps,
+.ant-form,
+.ant-upload,
+.ant-tree,
+.ant-list,
+.ant-calendar,
+.ant-date-picker,
+.ant-time-picker,
+.ant-cascader,
+.ant-transfer,
+.ant-rate,
+.ant-affix,
+.ant-anchor,
+.ant-back-top,
+.ant-divider,
+.ant-layout,
+.ant-grid,
+.ant-space,
+.search {
+  transition: none !important;
+  animation: none !important;
+}
+
+.ant-btn *,
+.ant-input *,
+.ant-input-affix-wrapper *,
+.ant-input-group *,
+.ant-input-group-addon *,
+.ant-input-prefix *,
+.ant-input-suffix *,
+.ant-select *,
+.ant-dropdown *,
+.ant-menu *,
+.ant-card *,
+.ant-table *,
+.ant-modal *,
+.ant-drawer *,
+.ant-tooltip *,
+.ant-popover *,
+.ant-notification *,
+.ant-message *,
+.ant-switch *,
+.ant-radio *,
+.ant-checkbox *,
+.ant-tabs *,
+.ant-collapse *,
+.ant-slider *,
+.ant-progress *,
+.ant-badge *,
+.ant-avatar *,
+.ant-tag *,
+.ant-alert *,
+.ant-spin *,
+.ant-pagination *,
+.ant-breadcrumb *,
+.ant-steps *,
+.ant-form *,
+.ant-upload *,
+.ant-tree *,
+.ant-list *,
+.ant-calendar *,
+.ant-date-picker *,
+.ant-time-picker *,
+.ant-cascader *,
+.ant-transfer *,
+.ant-rate *,
+.ant-affix *,
+.ant-anchor *,
+.ant-back-top *,
+.ant-divider *,
+.ant-layout *,
+.ant-grid *,
+.ant-space *,
+.search * {
+  transition: none !important;
+  animation: none !important;
+}
+
+/* Special handling for search area */
+.search,
+.search .ant-input-affix-wrapper,
+.search .ant-input,
+.search input {
+  transition: none !important;
+  animation: none !important;
+}
+
+.search:hover,
+.search:focus,
+.search:active,
+.search .ant-input-affix-wrapper:hover,
+.search .ant-input-affix-wrapper:focus,
+.search .ant-input-affix-wrapper:active,
+.search .ant-input:hover,
+.search .ant-input:focus,
+.search .ant-input:active,
+.search input:hover,
+.search input:focus,
+.search input:active {
+  transition: none !important;
+  animation: none !important;
+}
+
+/* Prevent dark mode dropdown menu flickering */
+html.dark .ant-dropdown-menu,
+html.dark .ant-dropdown-menu-item,
+html.dark .ant-dropdown-menu-submenu-title,
+html.dark .ant-menu-item,
+html.dark .ant-menu-submenu-title {
+  background-color: #1f1f1f !important;
+  color: rgba(255, 255, 255, 0.85) !important;
+  transition: none !important;
+  animation: none !important;
+}
+
+html.dark .ant-dropdown-menu-item:hover,
+html.dark .ant-dropdown-menu-submenu-title:hover,
+html.dark .ant-menu-item:hover,
+html.dark .ant-menu-submenu-title:hover {
+  background-color: #262626 !important;
+  color: rgba(255, 255, 255, 0.95) !important;
+}
+
+html.dark .ant-dropdown-menu-item-selected,
+html.dark .ant-dropdown-menu-submenu-title-selected,
+html.dark .ant-dropdown-menu-item-selected > a,
+html.dark .ant-dropdown-menu-submenu-title-selected > a,
+html.dark .ant-menu-item-selected {
+  background-color: #262626 !important;
+  color: #1890ff !important;
+}
+
+html.dark .ant-menu-item-divider,
+html.dark .ant-dropdown-menu-item-divider {
+  background-color: #434343 !important;
+}
+
+/* Dark mode alert background color adjustments */
+html.dark .ant-alert-info {
+  background-color: #111b26 !important;
+  border-color: #153450 !important;
+}
+
+html.dark .ant-alert-success {
+  background-color: #162312 !important;
+  border-color: #274916 !important;
+}
+
+html.dark .ant-alert-warning {
+  background-color: #2b1d11 !important;
+  border-color: #594214 !important;
+}
+
+html.dark .ant-alert-error {
+  background-color: #2a1215 !important;
+  border-color: #58181c !important;
+}
+
+/* Dark mode upload drag area background color adjustments */
+html.dark .ant-upload.ant-upload-drag {
+  background-color: #1f1f1f !important;
+  border-color: #434343 !important;
+}
+
+html.dark .ant-upload.ant-upload-drag:hover {
+  border-color: #1890ff !important;
+}
+
+html.dark .ant-upload.ant-upload-drag.ant-upload-drag-hover {
+  border-color: #1890ff !important;
+}
+
+html.dark .ant-upload.ant-upload-drag p.ant-upload-text,
+html.dark .ant-upload.ant-upload-drag p.ant-upload-hint {
+  color: rgba(255, 255, 255, 0.85) !important;
+}
+
+/* Tree node hover color adjustments */
+html.dark .ant-tree .ant-tree-node-content-wrapper:hover {
+  background-color: rgba(255, 255, 255, 0.06) !important;
+}
+
+/* Tree selected node background color adjustments */
+html.dark .ant-tree .ant-tree-node-content-wrapper.ant-tree-node-selected {
+  background-color: rgba(24, 144, 255, 0.15) !important;
+}
+
+/* Monaco editor background color matching */
+html.dark .ant-code-editor {
+  background-color: #1e1e1e !important;
+}
+
+/* Markdown code tag background color adjustments */
+html.dark .markdown-body code {
+  background-color: #2d2d2d !important;
+  color: rgba(255, 255, 255, 0.85) !important;
+}
+
+html.dark .markdown-body pre {
+  background-color: #1e1e1e !important;
+  color: rgba(255, 255, 255, 0.85) !important;
+  border: 1px solid #434343 !important;
+}
+
+html.dark .markdown-body pre code {
+  background-color: transparent !important;
+  color: inherit !important;
+}
+
+/* Table row hover color fixes */
+html.dark .ant-table-tbody > tr.ant-table-row:hover > td {
+  background-color: #262626 !important;
+}
+
+/* Table placeholder row hover color fix */
+html.dark .ant-table-tbody > tr.ant-table-placeholder:hover > td {
+  background-color: transparent !important;
+}
+
+/* Input field dark mode styling */
+html.dark .ant-input,
+html.dark .ant-input-affix-wrapper,
+html.dark .ant-input-affix-wrapper > input.ant-input {
+  background-color: #262626 !important;
+  border-color: #434343 !important;
+  color: rgba(255, 255, 255, 0.85) !important;
+}
+
+html.dark .ant-input::placeholder,
+html.dark .ant-input-affix-wrapper > input.ant-input::placeholder {
+  color: rgba(255, 255, 255, 0.45) !important;
+}
+
+html.dark .ant-input:hover,
+html.dark .ant-input-affix-wrapper:hover,
+html.dark .ant-input-affix-wrapper:hover > input.ant-input {
+  border-color: #177ddc !important;
+  background-color: #262626 !important;
+}
+
+html.dark .ant-input:focus,
+html.dark .ant-input-affix-wrapper-focused,
+html.dark .ant-input-affix-wrapper:focus,
+html.dark .ant-input-affix-wrapper-focused > input.ant-input,
+html.dark .ant-input-affix-wrapper:focus > input.ant-input {
+  border-color: #177ddc !important;
+  background-color: #262626 !important;
+  box-shadow: 0 0 0 2px rgba(23, 125, 220, 0.2) !important;
+}
+
+html.dark .ant-input-prefix,
+html.dark .ant-input-suffix {
+  color: rgba(255, 255, 255, 0.65) !important;
+}
+
+/* Highlight.js (hljs) dark mode styling */
+html.dark .hljs,
+html.dark pre code.hljs {
+  background-color: #1e1e1e !important;
+  color: rgba(255, 255, 255, 0.85) !important;
+}
+
+html.dark .hljs-comment,
+html.dark .hljs-quote {
+  color: #6a9955 !important;
+}
+
+html.dark .hljs-keyword,
+html.dark .hljs-selector-tag,
+html.dark .hljs-subst {
+  color: #569cd6 !important;
+}
+
+html.dark .hljs-string,
+html.dark .hljs-attr {
+  color: #ce9178 !important;
+}
+
+html.dark .hljs-number,
+html.dark .hljs-literal,
+html.dark .hljs-variable,
+html.dark .hljs-template-variable,
+html.dark .hljs-tag .hljs-attr {
+  color: #b5cea8 !important;
+}
+
+html.dark .hljs-built_in,
+html.dark .hljs-builtin-name {
+  color: #dcdcaa !important;
+}
+
+html.dark .hljs-type,
+html.dark .hljs-class .hljs-title {
+  color: #4ec9b0 !important;
+}
+
+html.dark .hljs-function .hljs-title,
+html.dark .hljs-title.function_ {
+  color: #dcdcaa !important;
+}
+
+html.dark .hljs-meta {
+  color: #9cdcfe !important;
+}
+
+/* Scala specific hljs styling */
+html.dark .hljs-scala .hljs-keyword {
+  color: #569cd6 !important;
+}
+
+html.dark .hljs-scala .hljs-class,
+html.dark .hljs-scala .hljs-type {
+  color: #4ec9b0 !important;
+}
+
+html.dark .hljs-scala .hljs-function {
+  color: #dcdcaa !important;
+}
diff --git a/zeppelin-web-angular/src/styles/theme/dark/theme-dark.less 
b/zeppelin-web-angular/src/styles/theme/dark/theme-dark.less
index 26e9186728..64ddf2f9cd 100644
--- a/zeppelin-web-angular/src/styles/theme/dark/theme-dark.less
+++ b/zeppelin-web-angular/src/styles/theme/dark/theme-dark.less
@@ -47,20 +47,20 @@
 // ---
 
 // Background color for `<body>`
-@body-background: #fff;
+@body-background: #141414;
 // Base background color for most components
-@component-background: #fff;
+@component-background: #1f1f1f;
 @font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'PingFang SC', 
'Hiragino Sans GB', 'Microsoft YaHei',
   'Helvetica Neue', Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe 
UI Emoji', 'Segoe UI Symbol';
 @code-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, Courier, 
monospace;
-@text-color: fade(@black, 65%);
-@text-color-secondary: fade(@black, 45%);
+@text-color: fade(@white, 85%);
+@text-color-secondary: fade(@white, 65%);
 @text-color-warning: @gold-7;
 @text-color-danger: @red-7;
-@text-color-inverse: @white;
-@icon-color: inherit;
-@icon-color-hover: fade(@black, 75%);
-@heading-color: fade(#000, 85%);
+@text-color-inverse: @black;
+@icon-color: fade(@white, 65%);
+@icon-color-hover: fade(@white, 85%);
+@heading-color: fade(@white, 95%);
 @heading-color-dark: fade(@white, 100%);
 @text-color-dark: fade(@white, 85%);
 @text-color-secondary-dark: fade(@white, 65%);
@@ -119,9 +119,9 @@
 @ease-in-out-quint: cubic-bezier(0.86, 0, 0.07, 1);
 
 // Border color
-@border-color-base: hsv(0, 0, 85%); // base border outline a component
-@border-color-split: hsv(0, 0, 91%); // split border inside a component
-@border-color-inverse: @white;
+@border-color-base: #434343; // base border outline a component
+@border-color-split: #303030; // split border inside a component
+@border-color-inverse: @black;
 @border-width-base: 1px; // width of the border for a component
 @border-style-base: solid; // style of a components border
 
@@ -130,16 +130,16 @@
 @outline-width: 2px;
 @outline-color: @primary-color;
 
-@background-color-light: hsv(0, 0, 98%); // background of header and selected 
item
-@background-color-base: hsv(0, 0, 96%); // Default grey background color
+@background-color-light: #262626; // background of header and selected item
+@background-color-base: #1f1f1f; // Default grey background color
 
 // Disabled states
-@disabled-color: fade(#000, 25%);
+@disabled-color: fade(@white, 25%);
 @disabled-bg: @background-color-base;
-@disabled-color-dark: fade(#fff, 35%);
+@disabled-color-dark: fade(@white, 35%);
 
 // Shadow
-@shadow-color: rgba(0, 0, 0, 0.15);
+@shadow-color: rgba(0, 0, 0, 0.45);
 @shadow-color-inverse: @component-background;
 @box-shadow-base: @shadow-1-down;
 @shadow-1-up: 0 -2px 8px @shadow-color;
@@ -158,11 +158,11 @@
 @btn-primary-shadow: 0 2px 0 rgba(0, 0, 0, 0.045);
 @btn-text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.12);
 
-@btn-primary-color: #fff;
+@btn-primary-color: @white;
 @btn-primary-bg: @primary-color;
 
 @btn-default-color: @text-color;
-@btn-default-bg: #fff;
+@btn-default-bg: @component-background;
 @btn-default-border: @border-color-base;
 
 @btn-danger-color: @error-color;
@@ -196,7 +196,7 @@
 // Checkbox
 @checkbox-size: 16px;
 @checkbox-color: @primary-color;
-@checkbox-check-color: #fff;
+@checkbox-check-color: @white;
 @checkbox-border-width: @border-width-base;
 
 // Descriptions
@@ -253,7 +253,7 @@
 @grid-gutter-width: 0;
 
 // Layout
-@layout-body-background: #f0f2f5;
+@layout-body-background: @body-background;
 @layout-header-background: #001529;
 @layout-footer-background: @layout-body-background;
 @layout-header-height: 64px;
@@ -262,12 +262,12 @@
 @layout-sider-background: @layout-header-background;
 @layout-trigger-height: 48px;
 @layout-trigger-background: #002140;
-@layout-trigger-color: #fff;
+@layout-trigger-color: @white;
 @layout-zero-trigger-width: 36px;
 @layout-zero-trigger-height: 42px;
 // Layout light theme
-@layout-sider-background-light: #fff;
-@layout-trigger-background-light: #fff;
+@layout-sider-background-light: @white;
+@layout-trigger-background-light: @white;
 @layout-trigger-color-light: @text-color;
 
 // z-index list, order by `z-index`
@@ -312,11 +312,11 @@
 @input-padding-vertical-base: 4px;
 @input-padding-vertical-sm: 1px;
 @input-padding-vertical-lg: 6px;
-@input-placeholder-color: hsv(0, 0, 75%);
+@input-placeholder-color: fade(@white, 45%);
 @input-color: @text-color;
 @input-border-color: @border-color-base;
-@input-bg: #fff;
-@input-number-handler-active-bg: #f4f4f4;
+@input-bg: @component-background;
+@input-number-handler-active-bg: @background-color-light;
 @input-number-handler-hover-bg: @primary-5;
 @input-number-handler-bg: @component-background;
 @input-number-handler-border-color: @border-color-base;
@@ -342,7 +342,7 @@
 // Tooltip max width
 @tooltip-max-width: 250px;
 // Tooltip text color
-@tooltip-color: #fff;
+@tooltip-color: @white;
 // Tooltip background color
 @tooltip-bg: rgba(0, 0, 0, 0.75);
 // Tooltip arrow width
@@ -355,7 +355,7 @@
 // Popover
 // ---
 // Popover body background color
-@popover-bg: #fff;
+@popover-bg: @component-background;
 // Popover text color
 @popover-color: @text-color;
 // Popover maximum width
@@ -376,7 +376,7 @@
 @modal-header-bg: @component-background;
 @modal-footer-bg: transparent;
 @modal-footer-border-color-split: @border-color-split;
-@modal-mask-bg: fade(@black, 45%);
+@modal-mask-bg: fade(@black, 65%);
 
 // Progress
 // --
@@ -411,9 +411,9 @@
 // dark theme
 @menu-dark-color: @text-color-secondary-dark;
 @menu-dark-bg: @layout-header-background;
-@menu-dark-arrow-color: #fff;
+@menu-dark-arrow-color: @white;
 @menu-dark-submenu-bg: #000c17;
-@menu-dark-highlight-color: #fff;
+@menu-dark-highlight-color: @white;
 @menu-dark-item-active-bg: @primary-color;
 @menu-dark-selected-item-icon-color: @white;
 @menu-dark-selected-item-text-color: @white;
@@ -427,17 +427,17 @@
 
 // Table
 // --
-@table-header-bg: @background-color-light;
+@table-header-bg: @background-color-base;
 @table-header-color: @heading-color;
 @table-header-sort-bg: @background-color-base;
-@table-body-sort-bg: rgba(0, 0, 0, 0.01);
+@table-body-sort-bg: rgba(255, 255, 255, 0.01);
 @table-row-hover-bg: @primary-1;
-@table-selected-row-bg: #fafafa;
-@table-expanded-row-bg: #fbfbfb;
+@table-selected-row-bg: @background-color-base;
+@table-expanded-row-bg: @background-color-base;
 @table-padding-vertical: 16px;
 @table-padding-horizontal: 16px;
 @table-border-radius-base: @border-radius-base;
-@table-footer-bg: @background-color-light;
+@table-footer-bg: @background-color-base;
 @table-footer-color: @heading-color;
 
 // Tag
@@ -480,9 +480,9 @@
 @card-inner-head-padding: 12px;
 @card-padding-base: 24px;
 @card-actions-background: @background-color-light;
-@card-skeleton-bg: #cfd8dc;
+@card-skeleton-bg: #434343;
 @card-background: @component-background;
-@card-shadow: 0 2px 8px rgba(0, 0, 0, 0.09);
+@card-shadow: 0 2px 8px rgba(0, 0, 0, 0.25);
 @card-radius: @border-radius-sm;
 
 // Comment
@@ -490,9 +490,9 @@
 @comment-padding-base: 16px 0;
 @comment-nest-indent: 44px;
 @comment-author-name-color: @text-color-secondary;
-@comment-author-time-color: #ccc;
+@comment-author-time-color: @text-color-secondary;
 @comment-action-color: @text-color-secondary;
-@comment-action-hover-color: #595959;
+@comment-action-hover-color: @text-color;
 
 // Tabs
 // ---
@@ -517,7 +517,7 @@
 
 // BackTop
 // ---
-@back-top-color: #fff;
+@back-top-color: @white;
 @back-top-bg: @text-color-secondary;
 @back-top-hover-bg: @text-color;
 
@@ -529,8 +529,8 @@
 @avatar-font-size-base: 18px;
 @avatar-font-size-lg: 24px;
 @avatar-font-size-sm: 14px;
-@avatar-bg: #ccc;
-@avatar-color: #fff;
+@avatar-bg: @text-color-secondary;
+@avatar-color: @white;
 @avatar-border-radius: @border-radius-base;
 
 // Switch
@@ -555,7 +555,7 @@
 @page-header-padding: 24px;
 @page-header-padding-vertical: 16px;   @page-header-padding-vertical: 16px;
 @page-header-padding-breadcrumb: 12px;
-@page-header-back-color: #000;
+@page-header-back-color: @black;
 
 // Breadcrumb
 // ---
@@ -589,9 +589,9 @@
 // ---
 @tree-title-height: 24px;
 @tree-child-padding: 18px;
-@tree-directory-selected-color: #fff;
+@tree-directory-selected-color: @white;
 @tree-directory-selected-bg: @primary-color;
-@tree-node-hover-bg: @item-hover-bg;
+@tree-node-hover-bg: @black;
 @tree-node-selected-bg: @primary-2;
 
 // Collapse
@@ -603,7 +603,7 @@
 
 // Skeleton
 // ---
-@skeleton-color: #f2f2f2;
+@skeleton-color: #434343;
 
 // Transfer
 // ---
diff --git a/zeppelin-web-angular/src/styles/theme/markdown.less 
b/zeppelin-web-angular/src/styles/theme/markdown.less
index d9f26833d6..3ee3319e23 100644
--- a/zeppelin-web-angular/src/styles/theme/markdown.less
+++ b/zeppelin-web-angular/src/styles/theme/markdown.less
@@ -12,15 +12,15 @@
 
 .markdown-body {
   color: @text-color;
-  font-size: 14px;
+  font-size: 14px !important;
   line-height: 1.5;
 
   h1 {
     color: @heading-color;
-    font-weight: 500;
+    font-weight: 500 !important;
     margin: 0.6em 0 0.6em;
-    font-family: Avenir, @font-family;
-    font-size: 30px;
+    font-family: Avenir, @font-family !important;
+    font-size: 30px !important;
     font-variant: tabular-nums;
     line-height: 38px;
   }
@@ -36,7 +36,7 @@
   h5,
   h6 {
     color: @heading-color;
-    font-family: Avenir, @font-family;
+    font-family: Avenir, @font-family !important;
     font-variant: tabular-nums;
     margin: 0.6em 0 0.6em;
     font-weight: 500;
@@ -94,12 +94,10 @@
   }
 
   code {
-    margin: 0 1px;
     background: #f2f4f5;
     padding: .2em .4em;
     border-radius: 3px;
-    font-size: .9em;
-    border: 1px solid #eee;
+    font-size: .9em !important;
   }
 
   pre {
@@ -113,7 +111,7 @@
     background: #f2f4f5;
     margin: 0;
     padding: 0;
-    font-size: @font-size-base - 1px;
+    font-size: @font-size-base - 1px !important;
     color: @text-color;
     overflow: auto;
   }

Reply via email to