diff --git a/app/client/app.css b/app/client/app.css
index b707a2583b..8b993f80da 100644
--- a/app/client/app.css
+++ b/app/client/app.css
@@ -1,4 +1,5 @@
/* global variables */
+@layer grist-base, grist-theme, grist-custom;
:root {
--color-logo-row: #F9AE41;
--color-logo-col: #2CB0AF;
diff --git a/app/client/lib/getOrCreateStyleElement.ts b/app/client/lib/getOrCreateStyleElement.ts
new file mode 100644
index 0000000000..f29046726a
--- /dev/null
+++ b/app/client/lib/getOrCreateStyleElement.ts
@@ -0,0 +1,14 @@
+/**
+ * Gets or creates a style element in the head of the document with the given `id`.
+ *
+ * Useful for grouping CSS values such as theme custom properties without needing to
+ * pollute the document with in-line styles.
+ */
+export function getOrCreateStyleElement(id: string) {
+ let style = document.head.querySelector(`#${id}`);
+ if (style) { return style; }
+ style = document.createElement('style');
+ style.setAttribute('id', id);
+ document.head.append(style);
+ return style;
+}
diff --git a/app/client/ui/PagePanels.ts b/app/client/ui/PagePanels.ts
index 6a95877954..ba09819a2d 100644
--- a/app/client/ui/PagePanels.ts
+++ b/app/client/ui/PagePanels.ts
@@ -1,6 +1,3 @@
-/**
- * Note that it assumes the presence of cssVars.cssRootVars on
.
- */
import {makeT} from 'app/client/lib/localization';
import * as commands from 'app/client/components/commands';
import {watchElementForBlur} from 'app/client/lib/FocusLayer';
diff --git a/app/client/ui2018/cssVars.ts b/app/client/ui2018/cssVars.ts
index 88aaa8cc99..fa990f0011 100644
--- a/app/client/ui2018/cssVars.ts
+++ b/app/client/ui2018/cssVars.ts
@@ -8,7 +8,8 @@
*/
import {urlState} from 'app/client/models/gristUrlState';
import {getTheme, ProductFlavor} from 'app/client/ui/CustomThemes';
-import {dom, DomElementMethod, makeTestId, Observable, styled, TestId} from 'grainjs';
+import {getOrCreateStyleElement} from 'app/client/lib/getOrCreateStyleElement';
+import {DomElementMethod, makeTestId, Observable, styled, TestId} from 'grainjs';
import debounce = require('lodash/debounce');
import values = require('lodash/values');
@@ -922,12 +923,6 @@ export const theme = {
const cssColors = values(colors).map(v => v.decl()).join('\n');
const cssVars = values(vars).map(v => v.decl()).join('\n');
-const cssFontParams = `
- font-family: ${vars.fontFamily};
- font-size: ${vars.mediumFontSize};
- -moz-osx-font-smoothing: grayscale;
- -webkit-font-smoothing: antialiased;
-`;
// We set box-sizing globally to match bootstrap's setting of border-box, since we are integrating
// into an app which already has it set, and it's impossible to make things look consistently with
@@ -969,8 +964,8 @@ const cssFontStyles = `
}
`;
-const cssVarsOnly = styled('div', cssColors + cssVars);
-const cssBodyVars = styled('div', cssFontParams + cssColors + cssVars + cssBorderBox + cssInputFonts + cssFontStyles);
+const cssRootVars = cssColors + cssVars;
+const cssReset = cssBorderBox + cssInputFonts + cssFontStyles;
const cssBody = styled('body', `
margin: 0;
@@ -980,10 +975,12 @@ const cssBody = styled('body', `
const cssRoot = styled('html', `
height: 100%;
overflow: hidden;
+ font-family: ${vars.fontFamily};
+ font-size: ${vars.mediumFontSize};
+ -moz-osx-font-smoothing: grayscale;
+ -webkit-font-smoothing: antialiased;
`);
-export const cssRootVars = cssBodyVars.className;
-
// Also make a globally available testId, with a simple "test-" prefix (i.e. in tests, query css
// class ".test-{name}". Ideally, we'd use noTestId() instead in production.
export const testId: TestId = makeTestId('test-');
@@ -1060,7 +1057,15 @@ export function isScreenResizing(): Observable {
* Attaches the global css properties to the document's root to make them available in the page.
*/
export function attachCssRootVars(productFlavor: ProductFlavor, varsOnly: boolean = false) {
- dom.update(document.documentElement, varsOnly ? dom.cls(cssVarsOnly.className) : dom.cls(cssRootVars));
+ /* apply each group of rules and variables in the correct css layer
+ * see app/client/app.css for layers order */
+ getOrCreateStyleElement('grist-root-css').textContent = `
+@layer grist-base {
+ :root {
+ ${cssRootVars}
+ }
+ ${!varsOnly && cssReset}
+}`;
document.documentElement.classList.add(cssRoot.className);
document.body.classList.add(cssBody.className);
const customTheme = getTheme(productFlavor);
diff --git a/app/client/ui2018/theme.ts b/app/client/ui2018/theme.ts
index 642732adae..019a368d29 100644
--- a/app/client/ui2018/theme.ts
+++ b/app/client/ui2018/theme.ts
@@ -1,5 +1,6 @@
import { createPausableObs, PausableObservable } from 'app/client/lib/pausableObs';
import { getStorage } from 'app/client/lib/storage';
+import { getOrCreateStyleElement } from 'app/client/lib/getOrCreateStyleElement';
import { urlState } from 'app/client/models/gristUrlState';
import { Theme, ThemeAppearance, ThemeColors, ThemePrefs } from 'app/common/ThemePrefs';
import { getThemeColors } from 'app/common/Themes';
@@ -130,9 +131,13 @@ function attachCssThemeVars({appearance, colors: themeColors}: Theme) {
properties.push(...getCssThemeBackgroundProperties(appearance));
// Apply the properties to the theme style element.
- getOrCreateStyleElement('grist-theme').textContent = `:root {
+ // The 'grist-theme' layer takes precedence over the 'grist-base' layer where
+ // default CSS variables are defined.
+ getOrCreateStyleElement('grist-theme').textContent = `@layer grist-theme {
+ :root {
${properties.join('\n')}
- }`;
+ }
+}`;
// Make the browser aware of the color scheme.
document.documentElement.style.setProperty(`color-scheme`, appearance);
@@ -174,18 +179,3 @@ function getCssThemeBackgroundProperties(appearance: ThemeAppearance) {
: 'url("img/gplaypattern.png")';
return [`--grist-theme-bg: ${value};`];
}
-
-/**
- * Gets or creates a style element in the head of the document with the given `id`.
- *
- * Useful for grouping CSS values such as theme custom properties without needing to
- * pollute the document with in-line styles.
- */
-function getOrCreateStyleElement(id: string) {
- let style = document.head.querySelector(`#${id}`);
- if (style) { return style; }
- style = document.createElement('style');
- style.setAttribute('id', id);
- document.head.append(style);
- return style;
-}
diff --git a/static/custom.css b/static/custom.css
index fc5f332c48..a06b5b83a9 100644
--- a/static/custom.css
+++ b/static/custom.css
@@ -1,42 +1,44 @@
-:root {
- /* logo */
- --icon-GristLogo: url("ui-icons/Logo/GristLogo.svg") !important;
- --grist-logo-bg: #040404 !important;
- --grist-logo-size: 22px 22px !important;
+@layer grist-custom {
+ :root {
+ /* logo */
+ --icon-GristLogo: url("ui-icons/Logo/GristLogo.svg") !important;
+ --grist-logo-bg: #040404 !important;
+ --grist-logo-size: 22px 22px !important;
- /* colors */
- --grist-color-light-grey: #F7F7F7 !important;
- --grist-color-medium-grey: rgba(217,217,217,0.6) !important;
- --grist-color-medium-grey-opaque: #E8E8E8 !important;
- --grist-color-dark-grey: #D9D9D9 !important;
- --grist-color-light: #FFFFFF !important;
- --grist-color-dark: #262633 !important;
- --grist-color-dark-bg: #262633 !important;
- --grist-color-slate: #929299 !important;
- --grist-color-light-green: #16B378 !important;
- --grist-color-dark-green: #009058 !important;
- --grist-color-darker-green: #007548 !important;
- --grist-color-lighter-green: #b1ffe2 !important;
- --grist-color-lighter-blue: #87b2f9 !important;
- --grist-color-light-blue: #3B82F6 !important;
- --grist-color-cursor: #16B378 !important;
- --grist-color-selection: rgba(22,179,120,0.15) !important;
- --grist-color-selection-opaque: #DCF4EB !important;
- --grist-color-selection-darker-opaque: #d6eee5 !important;
- --grist-color-inactive-cursor: #A2E1C9 !important;
- --grist-color-hover: #bfbfbf !important;
- --grist-color-error: #D0021B !important;
- --grist-color-warning: #F9AE41 !important;
- --grist-color-warning-bg: #dd962c !important;
- --grist-color-backdrop: rgba(38,38,51,0.9) !important;
- --grist-label-text-bg: #FFFFFF !important;
- --grist-label-active-bg: #F0F0F0 !important;
- --grist-primary-fg: #16B378 !important;
- --grist-primary-fg-hover: #009058 !important;
- --grist-primary-bg: #ffffff !important;
- --grist-control-bg: #ffffff !important;
- --grist-control-fg: #16B378 !important;
- --grist-primary-fg-hover: #009058 !important;
- --grist-control-border: 1px solid #11B683 !important;
- --grist-toast-bg: #040404 !important;
+ /* colors */
+ --grist-color-light-grey: #F7F7F7 !important;
+ --grist-color-medium-grey: rgba(217,217,217,0.6) !important;
+ --grist-color-medium-grey-opaque: #E8E8E8 !important;
+ --grist-color-dark-grey: #D9D9D9 !important;
+ --grist-color-light: #FFFFFF !important;
+ --grist-color-dark: #262633 !important;
+ --grist-color-dark-bg: #262633 !important;
+ --grist-color-slate: #929299 !important;
+ --grist-color-light-green: #16B378 !important;
+ --grist-color-dark-green: #009058 !important;
+ --grist-color-darker-green: #007548 !important;
+ --grist-color-lighter-green: #b1ffe2 !important;
+ --grist-color-lighter-blue: #87b2f9 !important;
+ --grist-color-light-blue: #3B82F6 !important;
+ --grist-color-cursor: #16B378 !important;
+ --grist-color-selection: rgba(22,179,120,0.15) !important;
+ --grist-color-selection-opaque: #DCF4EB !important;
+ --grist-color-selection-darker-opaque: #d6eee5 !important;
+ --grist-color-inactive-cursor: #A2E1C9 !important;
+ --grist-color-hover: #bfbfbf !important;
+ --grist-color-error: #D0021B !important;
+ --grist-color-warning: #F9AE41 !important;
+ --grist-color-warning-bg: #dd962c !important;
+ --grist-color-backdrop: rgba(38,38,51,0.9) !important;
+ --grist-label-text-bg: #FFFFFF !important;
+ --grist-label-active-bg: #F0F0F0 !important;
+ --grist-primary-fg: #16B378 !important;
+ --grist-primary-fg-hover: #009058 !important;
+ --grist-primary-bg: #ffffff !important;
+ --grist-control-bg: #ffffff !important;
+ --grist-control-fg: #16B378 !important;
+ --grist-primary-fg-hover: #009058 !important;
+ --grist-control-border: 1px solid #11B683 !important;
+ --grist-toast-bg: #040404 !important;
+ }
}