Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 12 additions & 3 deletions docusaurus/website/docusaurus.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,22 @@ const PORTAL_URL = `https://${process.env.DEV_PORTAL_HOST}`;
const PORTAL_HOME_HREF = `${PORTAL_URL}/developers`;

// Replace background and color to better match Grafana theme.
const grafanaPrismTheme: PrismTheme = {
const grafanaPrismDarkTheme: PrismTheme = {
...themes.oneDark,
plain: {
color: 'rgb(204, 204, 220)',
backgroundColor: '#181b1f',
},
};

const grafanaPrismLightTheme: PrismTheme = {
...themes.github,
plain: {
color: 'rgb(36, 41, 46)',
backgroundColor: '#ffffff',
},
};

console.log(`Building with environment variables from: ${__dirname}/${envFile}`);
console.log(`process.env.DEV_PORTAL_HOST: ${process.env.DEV_PORTAL_HOST}`);

Expand Down Expand Up @@ -210,7 +218,8 @@ const config: Config = {
copyright: `Copyright © ${new Date().getFullYear()} Grafana Labs. Built with Docusaurus.`,
},
prism: {
theme: grafanaPrismTheme,
theme: grafanaPrismLightTheme,
darkTheme: grafanaPrismDarkTheme,
additionalLanguages: ['bash', 'diff', 'json'],
magicComments: [
{
Expand All @@ -222,7 +231,7 @@ const config: Config = {
},
colorMode: {
defaultMode: 'dark',
disableSwitch: true,
disableSwitch: false,
respectPrefersColorScheme: false,
Comment thread
jackw marked this conversation as resolved.
},
},
Expand Down
52 changes: 48 additions & 4 deletions docusaurus/website/src/css/custom.css
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,32 @@ html[data-theme='dark'] {
--ifm-tabs-color-active-border: var(--warning-main);
}

html[data-theme='light'] {
--grafana-gradient-brand-vertical: var(--gradients-brandVertical);
--ifm-background-color: var(--background-canvas);
--ifm-card-background-color: var(--background-primary);
--ifm-card-border-radius: 0;
--ifm-code-background: var(--background-primary);
--ifm-color-danger: var(--error-main);
--ifm-color-info: var(--info-main);
--ifm-color-primary-dark: var(--warning-shade);
--ifm-color-primary-darker: var(--warning-main);
--ifm-color-primary: var(--text-primary);
--ifm-color-secondary: var(--text-secondary);
--ifm-color-success: var(--success-main);
--ifm-color-warning: var(--warning-main);
--ifm-link-color: var(--text-link);
--ifm-link-focus-color: var(--text-link);
--ifm-link-hover-color: var(--text-link);
--ifm-navbar-background-color: var(--background-primary);
--ifm-pagination-nav-color-hover: var(--text-link);
--ifm-background-surface-color: var(--background-primary);
--ifm-pre-background: var(--background-primary);
--ifm-table-border-color: var(--background-secondary);
--ifm-table-stripe-background: var(--background-secondary);
--ifm-tabs-color-active-border: var(--warning-main);
}

.gap {
gap: var(--ifm-spacing-horizontal);
}
Expand Down Expand Up @@ -147,10 +173,14 @@ html[data-theme='dark'] {

/* footer */

.footer.footer--dark {
html[data-theme='dark'] .footer.footer--dark {
--ifm-footer-background-color: rgba(0, 0, 0, 0.45);
}

html[data-theme='light'] .footer.footer--dark {
--ifm-footer-background-color: #22252b;
}

/* docs footer */
.theme-doc-footer {
border-top: 1px solid var(--ifm-toc-border-color);
Expand All @@ -165,11 +195,16 @@ html[data-theme='dark'] {
opacity: 1;
}

.button--outline {
html[data-theme='dark'] .button--outline {
--ifm-button-color: white;
--ifm-button-border-color: white;
}

html[data-theme='light'] .button--outline {
--ifm-button-color: var(--text-primary);
--ifm-button-border-color: var(--border-strong);
}

.button--primary {
--ifm-button-background-color: var(--ifm-color-secondary);
--ifm-button-border-color: var(--background-secondary);
Expand Down Expand Up @@ -231,9 +266,17 @@ html[data-theme='dark'] {
text-transform: lowercase;
}

.code-block-addition-highlighted-line {
html[data-theme='dark'] .code-block-addition-highlighted-line {
background-color: rgb(56, 64, 51);
box-shadow: inset 3px 0 0 0 rgb(128, 164, 102);
}

html[data-theme='light'] .code-block-addition-highlighted-line {
background-color: rgb(218, 237, 207);
box-shadow: inset 3px 0 0 0 rgb(87, 150, 56);
}

.code-block-addition-highlighted-line {
display: block;
margin: 0 calc(-1 * var(--ifm-pre-padding));
padding: 0 var(--ifm-pre-padding);
Expand Down Expand Up @@ -262,7 +305,8 @@ html[data-theme='dark'] {
}

/* Set background for suggestions */
html[data-theme='dark'] .algolia-autocomplete .ds-with-1.ds-dropdown-menu:before {
html[data-theme='dark'] .algolia-autocomplete .ds-with-1.ds-dropdown-menu:before,
html[data-theme='light'] .algolia-autocomplete .ds-with-1.ds-dropdown-menu:before {
background: var(--ifm-background-color);
}

Expand Down
70 changes: 70 additions & 0 deletions docusaurus/website/src/css/theme.css
Original file line number Diff line number Diff line change
Expand Up @@ -121,3 +121,73 @@
--hoverFactor: 0.03;
--tonalOffset: 0.15;
}

html[data-theme='light'] {
--mode: light;
--whiteBase: 36, 41, 46;
--border-weak: rgba(36, 41, 46, 0.12);
--border-medium: rgba(36, 41, 46, 0.2);
--border-strong: rgba(36, 41, 46, 0.3);
--text-primary: rgb(36, 41, 46);
--text-secondary: rgba(36, 41, 46, 0.65);
--text-disabled: rgba(36, 41, 46, 0.6);
--text-link: #1f62e0;
--text-maxContrast: #000000;
--primary-main: #1f62e0;
--primary-text: #1f62e0;
--primary-border: #1f62e0;
--primary-name: primary;
--primary-shade: rgb(56, 113, 220);
--primary-transparent: #1f62e026;
--primary-contrastText: #ffffff;
--secondary-main: rgba(36, 41, 46, 0.1);
--secondary-shade: rgba(36, 41, 46, 0.14);
--secondary-transparent: rgba(36, 41, 46, 0.08);
--secondary-text: rgb(36, 41, 46);
--secondary-contrastText: rgb(36, 41, 46);
--secondary-border: rgba(36, 41, 46, 0.08);
--secondary-name: secondary;
--info-main: #1f62e0;
--info-text: #1f62e0;
--info-border: #1f62e0;
--info-name: info;
--info-shade: rgb(56, 113, 220);
--info-transparent: #1f62e026;
--info-contrastText: #ffffff;
--error-main: #d10e5c;
--error-text: #c41253;
--error-name: error;
--error-border: #d10e5c;
--error-shade: rgb(186, 37, 100);
--error-transparent: #d10e5c26;
--error-contrastText: #ffffff;
--success-main: #1a7f4b;
--success-text: #137547;
--success-name: success;
--success-border: #1a7f4b;
--success-shade: rgb(47, 140, 94);
--success-transparent: #1a7f4b26;
--success-contrastText: #ffffff;
--warning-main: #d48b00;
--warning-text: #c48100;
--warning-name: warning;
--warning-border: #d48b00;
--warning-shade: rgb(199, 141, 19);
--warning-transparent: #d48b0026;
--warning-contrastText: #000000;
--background-canvas: #f4f5f5;
--background-primary: #ffffff;
--background-secondary: #f0f1f1;
--action-hover: rgba(36, 41, 46, 0.12);
--action-selected: rgba(36, 41, 46, 0.08);
--action-focus: rgba(36, 41, 46, 0.12);
--action-hoverOpacity: 0.08;
--action-disabledText: rgba(36, 41, 46, 0.6);
--action-disabledBackground: rgba(36, 41, 46, 0.04);
--action-disabledOpacity: 0.38;
--gradients-brandHorizontal: linear-gradient(270deg, #f55f3e 0%, #ff8833 100%);
--gradients-brandVertical: linear-gradient(0.01deg, #f55f3e 0.01%, #ff8833 99.99%);
--contrastThreshold: 3;
--hoverFactor: 0.03;
--tonalOffset: 0.15;
}
41 changes: 41 additions & 0 deletions docusaurus/website/src/theme/ColorModeToggle/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import React, { useRef, useEffect } from 'react';
import { useColorMode } from '@docusaurus/theme-common';
import ColorModeToggle from '@theme-original/ColorModeToggle';
import type { Props } from '@theme/ColorModeToggle';

function getTitle(colorMode: string): string {
if (colorMode === 'dark') {
return 'Let there be light (careful, bugs may become visible)';
}
return 'Return to the dark side';
}

export default function ColorModeToggleWrapper(props: Props): React.ReactNode {
const ref = useRef<HTMLDivElement>(null);
const { colorMode } = useColorMode();
const title = getTitle(colorMode);

// Use a MutationObserver to strip the inner button's native title whenever
// React re-applies it, so the browser falls through to our wrapper's title.
useEffect(() => {
const button = ref.current?.querySelector('button');
if (!button) {
return;
}
button.removeAttribute('title');

const observer = new MutationObserver(() => {
if (button.hasAttribute('title')) {
button.removeAttribute('title');
}
});
observer.observe(button, { attributes: true, attributeFilter: ['title'] });
return () => observer.disconnect();
}, [colorMode]);
Comment on lines +18 to +34
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we want this level of customisation I think it would be best to swizzle the ColorModeToggle with eject and customize it rather than rely on useEffect with MutationObserver.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FWIW Claude got very angry when Codex suggested this, I had to separate the two before nuclear war erupted

codex: The MutationObserver for the tooltip is heavy-handed. It works, but it's fragile — if Docusaurus changes the inner DOM structure, it could silently break. A simpler approach might be to fully swizzle (--eject) the component and just change the title prop directly, or use CSS pointer-events: none on the inner button with title on the wrapper. That said, the wrapping approach is safer for upgrades, so this is a reasonable tradeoff.

claude: MutationObserver — agree it's heavy-handed, but the alternatives are worse. pointer-events: none on the button would break the toggle. Ejecting is fragile on upgrades. Current approach is the right tradeoff. No change.

But I'm happy to switch to the eject approach

Copy link
Copy Markdown
Collaborator

@jackw jackw Mar 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't agree with Claude and neither does Docusaurus:

cd docusaurus/website && npm run swizzle @docusaurus/theme-classic -- --list

shows us it's safe to both wrap or eject:

image


return (
<div ref={ref} title={title}>
<ColorModeToggle {...props} />
</div>
);
}
Loading