-
Notifications
You must be signed in to change notification settings - Fork 48.8k
Description
DevTools defaults a "light" and a "dark" theme in a root-level CSS file:
react/packages/react-devtools-shared/src/devtools/views/root.css
Lines 1 to 188 in 3101872
:root { | |
/** | |
* IMPORTANT: When new theme variables are added below– also add them to SettingsContext updateThemeVariables() | |
*/ | |
/* Light theme */ | |
--light-color-attribute-name: #ef6632; | |
--light-color-attribute-name-not-editable: #23272f; | |
--light-color-attribute-name-inverted: rgba(255, 255, 255, 0.7); | |
--light-color-attribute-value: #1a1aa6; | |
--light-color-attribute-value-inverted: #ffffff; | |
--light-color-attribute-editable-value: #1a1aa6; | |
--light-color-background: #ffffff; | |
--light-color-background-hover: rgba(0, 136, 250, 0.1); | |
--light-color-background-inactive: #e5e5e5; | |
--light-color-background-invalid: #fff0f0; | |
--light-color-background-selected: #0088fa; | |
--light-color-button-background: #ffffff; | |
--light-color-button-background-focus: #ededed; | |
--light-color-button: #5f6673; | |
--light-color-button-disabled: #cfd1d5; | |
--light-color-button-active: #0088fa; | |
--light-color-button-focus: #23272f; | |
--light-color-button-hover: #23272f; | |
--light-color-border: #eeeeee; | |
--light-color-commit-did-not-render-fill: #cfd1d5; | |
--light-color-commit-did-not-render-fill-text: #000000; | |
--light-color-commit-did-not-render-pattern: #cfd1d5; | |
--light-color-commit-did-not-render-pattern-text: #333333; | |
--light-color-commit-gradient-0: #37afa9; | |
--light-color-commit-gradient-1: #63b19e; | |
--light-color-commit-gradient-2: #80b393; | |
--light-color-commit-gradient-3: #97b488; | |
--light-color-commit-gradient-4: #abb67d; | |
--light-color-commit-gradient-5: #beb771; | |
--light-color-commit-gradient-6: #cfb965; | |
--light-color-commit-gradient-7: #dfba57; | |
--light-color-commit-gradient-8: #efbb49; | |
--light-color-commit-gradient-9: #febc38; | |
--light-color-commit-gradient-text: #000000; | |
--light-color-component-name: #6a51b2; | |
--light-color-component-name-inverted: #ffffff; | |
--light-color-component-badge-background: rgba(0, 0, 0, 0.1); | |
--light-color-component-badge-background-inverted: rgba(255, 255, 255, 0.25); | |
--light-color-component-badge-count: #777d88; | |
--light-color-component-badge-count-inverted: rgba(255, 255, 255, 0.7); | |
--light-color-console-error-badge-text: #ffffff; | |
--light-color-console-error-background: #fff0f0; | |
--light-color-console-error-border: #ffd6d6; | |
--light-color-console-error-icon: #eb3941; | |
--light-color-console-error-text: #fe2e31; | |
--light-color-console-warning-badge-text: #000000; | |
--light-color-console-warning-background: #fffbe5; | |
--light-color-console-warning-border: #fff5c1; | |
--light-color-console-warning-icon: #f4bd00; | |
--light-color-console-warning-text: #64460c; | |
--light-color-context-background: rgba(0,0,0,.9); | |
--light-color-context-background-hover: rgba(255, 255, 255, 0.1); | |
--light-color-context-background-selected: #178fb9; | |
--light-color-context-border: #3d424a; | |
--light-color-context-text: #ffffff; | |
--light-color-context-text-selected: #ffffff; | |
--light-color-dim: #777d88; | |
--light-color-dimmer: #cfd1d5; | |
--light-color-dimmest: #eff0f1; | |
--light-color-error-background: hsl(0, 100%, 97%); | |
--light-color-error-border: hsl(0, 100%, 92%); | |
--light-color-error-text: #ff0000; | |
--light-color-expand-collapse-toggle: #777d88; | |
--light-color-link: #0000ff; | |
--light-color-modal-background: rgba(255, 255, 255, 0.75); | |
--light-color-bridge-version-npm-background: #eff0f1; | |
--light-color-bridge-version-npm-text: #000000; | |
--light-color-bridge-version-number: #0088fa; | |
--light-color-primitive-hook-badge-background: #e5e5e5; | |
--light-color-primitive-hook-badge-text: #5f6673; | |
--light-color-record-active: #fc3a4b; | |
--light-color-record-hover: #3578e5; | |
--light-color-record-inactive: #0088fa; | |
--light-color-scroll-thumb: #c2c2c2; | |
--light-color-scroll-track: #fafafa; | |
--light-color-search-match: yellow; | |
--light-color-search-match-current: #f7923b; | |
--light-color-selected-tree-highlight-active: rgba(0, 136, 250, 0.1); | |
--light-color-selected-tree-highlight-inactive: rgba(0, 0, 0, 0.05); | |
--light-color-shadow: rgba(0, 0, 0, 0.25); | |
--light-color-tab-selected-border: #0088fa; | |
--light-color-text: #000000; | |
--light-color-text-invalid: #ff0000; | |
--light-color-text-selected: #ffffff; | |
--light-color-toggle-background-invalid: #fc3a4b; | |
--light-color-toggle-background-on: #0088fa; | |
--light-color-toggle-background-off: #cfd1d5; | |
--light-color-toggle-text: #ffffff; | |
--light-color-tooltip-background: rgba(0, 0, 0, 0.9); | |
--light-color-tooltip-text: #ffffff; | |
/* Dark theme */ | |
--dark-color-attribute-name: #9d87d2; | |
--dark-color-attribute-name-not-editable: #ededed; | |
--dark-color-attribute-name-inverted: #282828; | |
--dark-color-attribute-value: #cedae0; | |
--dark-color-attribute-value-inverted: #ffffff; | |
--dark-color-attribute-editable-value: yellow; | |
--dark-color-background: #282c34; | |
--dark-color-background-hover: rgba(255, 255, 255, 0.1); | |
--dark-color-background-inactive: #3d424a; | |
--dark-color-background-invalid: #5c0000; | |
--dark-color-background-selected: #178fb9; | |
--dark-color-button-background: #282c34; | |
--dark-color-button-background-focus: #3d424a; | |
--dark-color-button: #afb3b9; | |
--dark-color-button-active: #61dafb; | |
--dark-color-button-disabled: #4f5766; | |
--dark-color-button-focus: #a2e9fc; | |
--dark-color-button-hover: #ededed; | |
--dark-color-border: #3d424a; | |
--dark-color-commit-did-not-render-fill: #777d88; | |
--dark-color-commit-did-not-render-fill-text: #000000; | |
--dark-color-commit-did-not-render-pattern: #666c77; | |
--dark-color-commit-did-not-render-pattern-text: #ffffff; | |
--dark-color-commit-gradient-0: #37afa9; | |
--dark-color-commit-gradient-1: #63b19e; | |
--dark-color-commit-gradient-2: #80b393; | |
--dark-color-commit-gradient-3: #97b488; | |
--dark-color-commit-gradient-4: #abb67d; | |
--dark-color-commit-gradient-5: #beb771; | |
--dark-color-commit-gradient-6: #cfb965; | |
--dark-color-commit-gradient-7: #dfba57; | |
--dark-color-commit-gradient-8: #efbb49; | |
--dark-color-commit-gradient-9: #febc38; | |
--dark-color-commit-gradient-text: #000000; | |
--dark-color-component-name: #61dafb; | |
--dark-color-component-name-inverted: #282828; | |
--dark-color-component-badge-background: rgba(255, 255, 255, 0.25); | |
--dark-color-component-badge-background-inverted: rgba(0, 0, 0, 0.25); | |
--dark-color-component-badge-count: #8f949d; | |
--dark-color-component-badge-count-inverted: rgba(255, 255, 255, 0.7); | |
--dark-color-console-error-badge-text: #000000; | |
--dark-color-console-error-background: #290000; | |
--dark-color-console-error-border: #5c0000; | |
--dark-color-console-error-icon: #eb3941; | |
--dark-color-console-error-text: #fc7f7f; | |
--dark-color-console-warning-badge-text: #000000; | |
--dark-color-console-warning-background: #332b00; | |
--dark-color-console-warning-border: #665500; | |
--dark-color-console-warning-icon: #f4bd00; | |
--dark-color-console-warning-text: #f5f2ed; | |
--dark-color-context-background: rgba(255,255,255,.9); | |
--dark-color-context-background-hover: rgba(0, 136, 250, 0.1); | |
--dark-color-context-background-selected: #0088fa; | |
--dark-color-context-border: #eeeeee; | |
--dark-color-context-text: #000000; | |
--dark-color-context-text-selected: #ffffff; | |
--dark-color-dim: #8f949d; | |
--dark-color-dimmer: #777d88; | |
--dark-color-dimmest: #4f5766; | |
--dark-color-error-background: #200; | |
--dark-color-error-border: #900; | |
--dark-color-error-text: #f55; | |
--dark-color-expand-collapse-toggle: #8f949d; | |
--dark-color-link: #61dafb; | |
--dark-color-modal-background: rgba(0, 0, 0, 0.75); | |
--dark-color-bridge-version-npm-background: rgba(0, 0, 0, 0.25); | |
--dark-color-bridge-version-npm-text: #ffffff; | |
--dark-color-bridge-version-number: yellow; | |
--dark-color-primitive-hook-badge-background: rgba(0, 0, 0, 0.25); | |
--dark-color-primitive-hook-badge-text: rgba(255, 255, 255, 0.7); | |
--dark-color-record-active: #fc3a4b; | |
--dark-color-record-hover: #a2e9fc; | |
--dark-color-record-inactive: #61dafb; | |
--dark-color-scroll-thumb: #afb3b9; | |
--dark-color-scroll-track: #313640; | |
--dark-color-search-match: yellow; | |
--dark-color-search-match-current: #f7923b; | |
--dark-color-selected-tree-highlight-active: rgba(23, 143, 185, 0.15); | |
--dark-color-selected-tree-highlight-inactive: rgba(255, 255, 255, 0.05); | |
--dark-color-shadow: rgba(0, 0, 0, 0.5); | |
--dark-color-tab-selected-border: #178fb9; | |
--dark-color-text: #ffffff; | |
--dark-color-text-invalid: #ff8080; | |
--dark-color-text-selected: #ffffff; | |
--dark-color-toggle-background-invalid: #fc3a4b; | |
--dark-color-toggle-background-on: #178fb9; | |
--dark-color-toggle-background-off: #777d88; | |
--dark-color-toggle-text: #ffffff; | |
--dark-color-tooltip-background: rgba(255, 255, 255, 0.9); | |
--dark-color-tooltip-text: #000000; |
The current theme value ("light", "dark", or "auto") is stored in the ThemeContext
and persisted between sessions using the localStorage
API:
react/packages/react-devtools-shared/src/devtools/views/Settings/SettingsContext.js
Lines 86 to 89 in 3101872
const [theme, setTheme] = useLocalStorage<Theme>( | |
'React::DevTools::theme', | |
'auto', | |
); |
When the theme changes, a layout effect updates root-level CSS variables:
react/packages/react-devtools-shared/src/devtools/views/Settings/SettingsContext.js
Lines 149 to 163 in 3101872
useLayoutEffect(() => { | |
switch (theme) { | |
case 'light': | |
updateThemeVariables('light', documentElements); | |
break; | |
case 'dark': | |
updateThemeVariables('dark', documentElements); | |
break; | |
case 'auto': | |
updateThemeVariables(browserTheme, documentElements); | |
break; | |
default: | |
throw Error(`Unsupported theme value "${theme}"`); | |
} | |
}, [browserTheme, theme, documentElements]); |
This has an unfortunate side effect of creating a race condition between any other imperative code (e.g. a Canvas rendering component) that may want to read the current CSS variables. This code cannot run before the code above or it will read previous/stale values.
We should refactor this code to declaratively share theme variables to its subtree, e.g.:
function ThemeProvider({ value, children }) {
// ...
return (
<ThemeContext.Provider value={theme}>
<div
style={{
'--color-attribute-name': '#ef6632',
'--color-attribute-name-not-editable': '#23272f',
// ...
}}
>
{children}
</div>
</ThemeContext.Provider>
);
}
We will also need to update any place that reads these computed styles, e.g.
react/packages/react-devtools-shared/src/devtools/views/Settings/SettingsContext.js
Lines 274 to 281 in 3101872
// Sizes and paddings/margins are all rem-based, | |
// so update the root font-size as well when the display preference changes. | |
const computedStyle = getComputedStyle((document.body: any)); | |
const fontSize = computedStyle.getPropertyValue( | |
`--${displayDensity}-root-font-size`, | |
); | |
const root = ((document.querySelector(':root'): any): HTMLElement); | |
root.style.fontSize = fontSize; |
A similar approach is used for font size ("comfortable" or "compact"). We should do the same refactor for this.