From 4eaa9314edb103b4729ccba22fc741fae735f1bc Mon Sep 17 00:00:00 2001
From: Dan Piazza <220388267+DanPiazza-Netwrix@users.noreply.github.com>
Date: Wed, 18 Mar 2026 13:31:38 -0400
Subject: [PATCH 1/6] Fix mobile layout for Algolia search modal
---
src/css/custom.css | 53 ++++++++++++++++++++++++++++++++++++
src/theme/SearchBar/index.js | 13 +++------
2 files changed, 57 insertions(+), 9 deletions(-)
diff --git a/src/css/custom.css b/src/css/custom.css
index 83d471e0b6..d283bd8f23 100644
--- a/src/css/custom.css
+++ b/src/css/custom.css
@@ -382,6 +382,59 @@ html {
position: relative;
}
+/* Transfer the search-area visual chrome (background, bottom border, top radius)
+ from .DocSearch-Form to .DocSearch-SearchBar now that our custom controls are
+ siblings of the form rather than children of it. */
+.DocSearch-SearchBar {
+ background: var(--docsearch-searchbox-focus-background);
+ border-bottom: 1px solid var(--docsearch-subtle-color);
+ border-radius: 4px 4px 0 0;
+ align-items: center;
+}
+
+/* Hide the "close the query" text label — keep only the × icon.
+ !important is needed because DocSearch styles are dynamically imported
+ after our CSS, so they would otherwise win the cascade. */
+.DocSearch-Clear {
+ font-size: 0 !important;
+}
+.DocSearch-Clear span {
+ display: none;
+}
+
+/* Strip the duplicate chrome from the form itself. */
+.DocSearch-Form {
+ flex: 1 1 auto;
+ width: auto;
+ background: transparent;
+ border-block-end: none !important;
+ border-radius: 0;
+}
+
+/* On mobile, wrap the custom controls onto their own row below the search input. */
+@media (max-width: 768px) {
+ .DocSearch-SearchBar {
+ flex-wrap: wrap;
+ }
+
+ .search-custom-controls {
+ flex: 0 0 100%;
+ margin-right: 0 !important;
+ padding: 6px 16px 8px;
+ border-bottom: 1px solid var(--docsearch-subtle-color);
+ }
+
+ /* Fix .DocSearch-Title — the library sets line-height: 0.5em which causes
+ wrapped lines to overlap, and there is no word-break so long query strings
+ overflow the viewport horizontally. */
+ .DocSearch-Title {
+ line-height: 1.4 !important;
+ white-space: normal !important;
+ word-break: break-word;
+ overflow-wrap: break-word;
+ }
+}
+
/* Responsive improvements */
@media (max-width: 768px) {
.DocSearch-Button-Keys {
diff --git a/src/theme/SearchBar/index.js b/src/theme/SearchBar/index.js
index 2ebdd418d0..29b89f36ba 100644
--- a/src/theme/SearchBar/index.js
+++ b/src/theme/SearchBar/index.js
@@ -686,24 +686,18 @@ export default function SearchBar() {
const [modalHeaderEl, setModalHeaderEl] = useState(null);
const onModalOpen = useCallback(() => {
- // Try to locate the header/searchbar area in the DocSearch modal.
- // DocSearch v3 uses these classnames.
+ // Target .DocSearch-SearchBar so our controls are a sibling of .DocSearch-Form.
+ // This lets us move them below the search row on mobile via flex-wrap.
const el =
document.querySelector('.DocSearch-SearchBar') ||
- document.querySelector('.DocSearch-Form') ||
document.querySelector('.DocSearch-Modal');
setModalHeaderEl(el || null);
}, []);
- // When modalHeaderEl exists, insert filters BEFORE the form input if possible.
- // We target .DocSearch-Form when present, otherwise the container itself.
const portalTarget = useMemo(() => {
if (!modalHeaderEl) return null;
- return (
- modalHeaderEl.querySelector('.DocSearch-Form') ||
- modalHeaderEl
- );
+ return modalHeaderEl;
}, [modalHeaderEl]);
// Keep contextualSearch stable to prevent query reset
@@ -724,6 +718,7 @@ export default function SearchBar() {
createPortal(
// Wrapper to align with DocSearch input
Date: Wed, 18 Mar 2026 13:45:23 -0400
Subject: [PATCH 2/6] Fix text wrap in search modal at certain screen widths
---
src/css/custom.css | 19 ++++++++++---------
1 file changed, 10 insertions(+), 9 deletions(-)
diff --git a/src/css/custom.css b/src/css/custom.css
index d283bd8f23..dec597b75e 100644
--- a/src/css/custom.css
+++ b/src/css/custom.css
@@ -392,6 +392,15 @@ html {
align-items: center;
}
+/* Fix .DocSearch-Title — the library sets line-height: 0.5em which causes
+ wrapped lines to overlap, and no word-break so long queries overflow horizontally. */
+.DocSearch-Title {
+ line-height: 1.4 !important;
+ white-space: normal !important;
+ word-break: break-word;
+ overflow-wrap: break-word;
+}
+
/* Hide the "close the query" text label — keep only the × icon.
!important is needed because DocSearch styles are dynamically imported
after our CSS, so they would otherwise win the cascade. */
@@ -424,15 +433,7 @@ html {
border-bottom: 1px solid var(--docsearch-subtle-color);
}
- /* Fix .DocSearch-Title — the library sets line-height: 0.5em which causes
- wrapped lines to overlap, and there is no word-break so long query strings
- overflow the viewport horizontally. */
- .DocSearch-Title {
- line-height: 1.4 !important;
- white-space: normal !important;
- word-break: break-word;
- overflow-wrap: break-word;
- }
+
}
/* Responsive improvements */
From db09e199cda0e703aedbde76de77e75b1c960182 Mon Sep 17 00:00:00 2001
From: Dan Piazza <220388267+DanPiazza-Netwrix@users.noreply.github.com>
Date: Wed, 18 Mar 2026 14:46:01 -0400
Subject: [PATCH 3/6] Dark Mode: Fix parts of the background on search results
page
---
src/theme/SearchPage/index.js | 6 ++++--
1 file changed, 4 insertions(+), 2 deletions(-)
diff --git a/src/theme/SearchPage/index.js b/src/theme/SearchPage/index.js
index cba39c632f..e76e08dc89 100644
--- a/src/theme/SearchPage/index.js
+++ b/src/theme/SearchPage/index.js
@@ -683,7 +683,7 @@ function SearchPageContent() {
position: 'sticky',
top: 'var(--ifm-navbar-height)',
zIndex: 10,
- backgroundColor: 'var(--ifm-navbar-background-color)',
+ backgroundColor: 'var(--ifm-background-color)',
paddingTop: '16px',
paddingBottom: '12px',
}}>
@@ -708,6 +708,8 @@ function SearchPageContent() {
fontSize: '16px',
borderRadius: '8px',
border: '2px solid var(--ifm-color-emphasis-300)',
+ background: 'var(--ifm-background-color)',
+ color: 'var(--ifm-font-color-base)',
marginBottom: '0',
transition: 'border-color 0.2s',
}}
@@ -852,7 +854,7 @@ function SearchPageContent() {
flexDirection: 'column',
paddingTop: '16px',
paddingBottom: '16px',
- backgroundColor: 'var(--ifm-navbar-background-color)',
+ backgroundColor: 'var(--ifm-background-color)',
}}>
Date: Wed, 18 Mar 2026 16:55:31 -0400
Subject: [PATCH 4/6] Dark Mode: Improve general experience, contrast,
backgrounds, buttons, etc.
---
docusaurus.config.js | 2 +-
.../CommunityHighlights/styles.module.css | 5 ++
src/pages/index.module.css | 4 ++
src/theme/ColorModeToggle/index.js | 60 +++++++++++++++++++
src/theme/ColorModeToggle/styles.module.css | 36 +++++++++++
src/theme/SearchBar/index.js | 2 +-
6 files changed, 107 insertions(+), 2 deletions(-)
create mode 100644 src/theme/ColorModeToggle/index.js
create mode 100644 src/theme/ColorModeToggle/styles.module.css
diff --git a/docusaurus.config.js b/docusaurus.config.js
index 57175b8ae4..6b316bbc0b 100644
--- a/docusaurus.config.js
+++ b/docusaurus.config.js
@@ -218,7 +218,7 @@ const config = {
logo: {
alt: 'Netwrix Logo',
src: 'branding/Netwrix_Logo_Dark.svg',
- srcDark: 'branding/Netwrix_Logo_Light.svg',
+ srcDark: 'branding/logo-light.svg',
href: '/',
},
items: [
diff --git a/src/components/CommunityHighlights/styles.module.css b/src/components/CommunityHighlights/styles.module.css
index fefcb3479b..d0087b6246 100644
--- a/src/components/CommunityHighlights/styles.module.css
+++ b/src/components/CommunityHighlights/styles.module.css
@@ -105,6 +105,11 @@
border-color: var(--ifm-color-emphasis-200);
}
+[data-theme='dark'] .featuredLabel {
+ background: linear-gradient(45deg, #8d1f1f, #835b00);
+ color: #fff;
+}
+
.featuredCard::before {
content: '';
position: absolute;
diff --git a/src/pages/index.module.css b/src/pages/index.module.css
index 181268cf43..171c653051 100644
--- a/src/pages/index.module.css
+++ b/src/pages/index.module.css
@@ -234,6 +234,10 @@
background: var(--ifm-background-color);
}
+[data-theme='dark'] .stats {
+ background: linear-gradient(135deg, #003c6c 0%, #005a9e 100%);
+}
+
/* Responsive */
@media screen and (max-width: 996px) {
.heroBanner {
diff --git a/src/theme/ColorModeToggle/index.js b/src/theme/ColorModeToggle/index.js
new file mode 100644
index 0000000000..280ae8b676
--- /dev/null
+++ b/src/theme/ColorModeToggle/index.js
@@ -0,0 +1,60 @@
+import React from 'react';
+import clsx from 'clsx';
+import useIsBrowser from '@docusaurus/useIsBrowser';
+import {translate} from '@docusaurus/Translate';
+import IconDarkMode from '@theme/Icon/DarkMode';
+import IconLightMode from '@theme/Icon/LightMode';
+import styles from './styles.module.css';
+
+/**
+ * Swizzled ColorModeToggle.
+ *
+ * Docusaurus default shows the current-mode icon (sun in light, moon in dark).
+ * This version shows the target-mode icon instead:
+ * - Light mode → moon ("switch to dark")
+ * - Dark mode → sun ("switch to light")
+ */
+export default function ColorModeToggle({className, buttonClassName, value, onChange}) {
+ const isBrowser = useIsBrowser();
+ const title = translate(
+ {
+ message: 'Switch between dark and light mode (currently {mode})',
+ id: 'theme.colorToggle.ariaLabel',
+ description: 'The ARIA label for the navbar color mode toggle',
+ },
+ {
+ mode:
+ value === 'dark'
+ ? translate({
+ message: 'dark mode',
+ id: 'theme.colorToggle.ariaLabel.mode.dark',
+ description: 'The name for the dark color mode',
+ })
+ : translate({
+ message: 'light mode',
+ id: 'theme.colorToggle.ariaLabel.mode.light',
+ description: 'The name for the light color mode',
+ }),
+ },
+ );
+
+ return (
+
+
+
+ );
+}
diff --git a/src/theme/ColorModeToggle/styles.module.css b/src/theme/ColorModeToggle/styles.module.css
new file mode 100644
index 0000000000..e2178235a8
--- /dev/null
+++ b/src/theme/ColorModeToggle/styles.module.css
@@ -0,0 +1,36 @@
+.toggle {
+ display: flex;
+ align-items: center;
+}
+
+.toggleButton {
+ display: flex;
+ overflow: hidden;
+}
+
+.toggleButtonDisabled {
+ cursor: not-allowed;
+}
+
+.icon {
+ width: 1.4rem;
+ height: 1.4rem;
+}
+
+/* Moon (lightModeIcon) visible in light mode */
+.lightModeIcon {
+ display: block;
+}
+
+/* Sun (darkModeIcon) hidden in light mode */
+.darkModeIcon {
+ display: none;
+}
+
+[data-theme='dark'] .lightModeIcon {
+ display: none;
+}
+
+[data-theme='dark'] .darkModeIcon {
+ display: block;
+}
diff --git a/src/theme/SearchBar/index.js b/src/theme/SearchBar/index.js
index 29b89f36ba..4c912a2cd4 100644
--- a/src/theme/SearchBar/index.js
+++ b/src/theme/SearchBar/index.js
@@ -390,7 +390,7 @@ function MultiSelectDropdown({label, options, selectedValues, onChange, placehol
padding: '8px 12px',
cursor: 'pointer',
fontSize: '14px',
- background: isSelected ? 'var(--docsearch-hit-active-color)' : 'transparent',
+ background: 'transparent',
}}
onMouseEnter={(e) => {
if (!isSelected) {
From abc741b49978f151fdddf3a7cab3d5743e5ebc39 Mon Sep 17 00:00:00 2001
From: Dan Piazza <220388267+DanPiazza-Netwrix@users.noreply.github.com>
Date: Wed, 18 Mar 2026 18:24:13 -0400
Subject: [PATCH 5/6] Change position of search modal "close" icon so it's
always in the upper-right on both mobile and desktop
---
src/css/custom.css | 64 +++++++++++++++++----
src/theme/SearchBar/index.js | 105 ++++++++++++++++++-----------------
2 files changed, 107 insertions(+), 62 deletions(-)
diff --git a/src/css/custom.css b/src/css/custom.css
index dec597b75e..b758549aea 100644
--- a/src/css/custom.css
+++ b/src/css/custom.css
@@ -407,37 +407,77 @@ html {
.DocSearch-Clear {
font-size: 0 !important;
}
-.DocSearch-Clear span {
- display: none;
-}
-/* Strip the duplicate chrome from the form itself. */
+/* Strip the duplicate chrome from the form itself.
+ width: auto !important overrides DocSearch's dynamically-loaded width: 100%,
+ which would otherwise force the form to consume the entire first flex row and
+ push the × button onto its own line on mobile. */
.DocSearch-Form {
flex: 1 1 auto;
- width: auto;
+ width: auto !important;
background: transparent;
border-block-end: none !important;
border-radius: 0;
}
-/* On mobile, wrap the custom controls onto their own row below the search input. */
+/* Hide the DocSearch-owned close button — we render our own in the portal so
+ we never mutate React-owned DOM nodes (which causes reconciliation crashes). */
+.DocSearch-Close {
+ display: none !important;
+}
+
+/* Custom controls (Typo Tolerance toggle + product filter) portaled into the search bar. */
+.search-custom-controls {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ margin-right: 8px;
+}
+
+/* Our replacement close button rendered inside the portal. */
+.search-modal-close {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ flex-shrink: 0;
+ background: none;
+ border: none;
+ cursor: pointer;
+ color: var(--docsearch-muted-color);
+ padding: 0;
+ margin: 0 16px 0 8px;
+}
+.search-modal-close:hover {
+ color: var(--docsearch-text-color);
+}
+[data-theme='dark'] .search-modal-close {
+ color: white;
+}
+
+/* On mobile, wrap the custom controls onto their own row below the search input,
+ but keep the × button on the first row (upper-right corner). */
@media (max-width: 768px) {
.DocSearch-SearchBar {
flex-wrap: wrap;
}
+ .DocSearch-Form {
+ order: 1;
+ }
+
+ .search-modal-close {
+ order: 2;
+ margin: 0 16px 0 8px;
+ }
+
.search-custom-controls {
+ order: 3;
flex: 0 0 100%;
- margin-right: 0 !important;
+ margin-right: 0;
padding: 6px 16px 8px;
border-bottom: 1px solid var(--docsearch-subtle-color);
}
-
-}
-
-/* Responsive improvements */
-@media (max-width: 768px) {
.DocSearch-Button-Keys {
display: none;
}
diff --git a/src/theme/SearchBar/index.js b/src/theme/SearchBar/index.js
index 4c912a2cd4..83cb7e1920 100644
--- a/src/theme/SearchBar/index.js
+++ b/src/theme/SearchBar/index.js
@@ -441,20 +441,20 @@ function DocSearch({externalUrlRegex, onModalOpen, selectedProductsRef, typoTole
}
}, []);
+ const closeModal = useCallback(() => {
+ setIsOpen(false);
+ searchButtonRef.current?.focus();
+ setInitialQuery(undefined);
+ }, []);
+
const openModal = useCallback(() => {
prepareSearchContainer();
importDocSearchModalIfNeeded().then(() => {
setIsOpen(true);
// Let React render the modal, then caller can locate DOM nodes
- setTimeout(() => onModalOpen?.(), 0);
+ setTimeout(() => onModalOpen?.(closeModal), 0);
});
- }, [prepareSearchContainer, onModalOpen]);
-
- const closeModal = useCallback(() => {
- setIsOpen(false);
- searchButtonRef.current?.focus();
- setInitialQuery(undefined);
- }, []);
+ }, [prepareSearchContainer, onModalOpen, closeModal]);
const handleInput = useCallback(
(event) => {
@@ -684,22 +684,20 @@ export default function SearchBar() {
// This is where we will portal the filters into the modal DOM.
const [modalHeaderEl, setModalHeaderEl] = useState(null);
+ // Holds the closeModal function passed up from the inner DocSearch component.
+ const closeModalRef = useRef(null);
- const onModalOpen = useCallback(() => {
+ const onModalOpen = useCallback((closeModal) => {
// Target .DocSearch-SearchBar so our controls are a sibling of .DocSearch-Form.
// This lets us move them below the search row on mobile via flex-wrap.
const el =
document.querySelector('.DocSearch-SearchBar') ||
document.querySelector('.DocSearch-Modal');
+ closeModalRef.current = closeModal ?? null;
setModalHeaderEl(el || null);
}, []);
- const portalTarget = useMemo(() => {
- if (!modalHeaderEl) return null;
- return modalHeaderEl;
- }, [modalHeaderEl]);
-
// Keep contextualSearch stable to prevent query reset
// Product filters are handled via transformSearchClient instead
const contextualSearch = false;
@@ -714,45 +712,52 @@ export default function SearchBar() {
onModalOpen={onModalOpen}
/>
- {portalTarget &&
+ {modalHeaderEl &&
createPortal(
- // Wrapper to align with DocSearch input
-
-
-
-
+ {/* Custom controls: Typo Tolerance + product filter */}
+
+
+
+
+ Typo Tolerance
+
+
+
-
- Typo Tolerance
-
- ,
- portalTarget,
+ {/* Our own close button — replaces the DocSearch-Close button
+ (which is hidden via CSS) so we avoid mutating React-owned
+ DOM nodes, which causes reconciliation errors. */}
+
+ >,
+ modalHeaderEl,
)}
>
);
From a1c6b6ce8863c303576e3e6e425cbb0bca91b760 Mon Sep 17 00:00:00 2001
From: Dan Piazza <220388267+DanPiazza-Netwrix@users.noreply.github.com>
Date: Wed, 18 Mar 2026 18:53:01 -0400
Subject: [PATCH 6/6] Fix how full search results page scrolls for both light
and dark mode
---
src/css/custom.css | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/src/css/custom.css b/src/css/custom.css
index b758549aea..64c9272c6a 100644
--- a/src/css/custom.css
+++ b/src/css/custom.css
@@ -62,6 +62,10 @@
--ifm-color-success: #34C263;
--ifm-color-success-dark: #27914A;
--ifm-button-background-color: #5851DB;
+ /* Infima defaults this to `transparent` (relying on the browser's white
+ canvas). An explicit opaque value is needed so that sticky elements
+ like search-page controls can hide content scrolling behind them. */
+ --ifm-background-color: #fff;
}
.table-of-contents {