The situation is when using css variables with font-family, the defined default font-family is not the fallback font-family.
We have a css variable defined for --myFontFamily.
When the font-family defined by the css variable --myFontFamily is not available on the site, the user-agent font-family is utilized.
The expectation is that our default font-family will be the fallback when the css variable font-family is not available. For clarity --myFontFamily is defined. The value is a custom font that does not exist on the site.
I have reviewed webstyle guide
I reviewed css vars
we utilize the shadowDOM for our content so we utilize the sudo class for :host {} to ensure proper style cascading for elements inside the shadowDOM.
css variable will be injected into the head of the html within a style tag
elements that do not define font-family will inherit the font-family defined within :host {}
<style type="text/css" class="custom-site-style" id="custom-site-styles">
:root {
--myFontFamily: CustomFontButNotOnTheSitePage;
}
</style>
<div>
<!-- there would be a shadow-root here -->
<style>
:host {
font-family: Arial, Helvetica;
}
.my-content {
font-family: var(--myFontFamily);
}
</style>
<div> Font Family is Arial as expected </div>
<div class="my-content"> User Agent font-family is used. Expectation is for font family to default to Arial because the css variable is defined but the font does not exist on the site page. We have a defined font-family that is inherited by other elements.
</div>
</div>
I have experimented and found that this will work and fallback to the default font-family as expected. However this doesn't explain why the user-agent is the fallback in the above example.
<div>
<style>
:host {
--defaultFontFamily: Arial, Helvetica;
font-family: Arial, Helvetica;
}
.my-content {
font-family: var(--myFontFamily), var(--defaultFontFamily);
}
</style>
As requested. A true working example.
<html>
<head>
<style>
body {
font-family: 'Times New Roman';
}
</style>
<style type="text/css" class="custom-site-style" id="custom-site-styles">
:root {
--myFontFamily: CustomFontButNotOnTheSitePage;
}
</style>
</head>
<body>
<div class="inject-here">
</div>
</body>
<script type="text/javascript">
(function () {// mimics proprietary production code
const content = document.querySelector('.inject-here');
if (!content.shadowRoot) {
const shadow = content.attachShadow({ mode: 'open' });
// demonstrates the issue observed
// when the custom variable has defined a font-family that does not exist on the page
// the default font-family will cascade from the page / user agent
// rather than from the defined styles in :host {}
const hostStyles = `
:host {
font-family: Arial, Helvetica;
/*demonstrates the :host is correct*/
color: blue;
font-weight: 500;
}
.my-content {
font-family: var(--myFontFamily);
color: black; /*demonstrates cascade works*/
}`;
// // working example of explicit default font-family will be used as the fallback
// // uncomment this to see it work properly
// const hostStyles = `
// :host {
// --myDefaultFontFamily: Arial, Helvetica;
// font-family: var(--myDefaultFontFamily);
// /*demonstrates the :host is correct*/
// color: blue;
// font-weight: 500;
// }
// .my-content {
// font-family: var(--myFontFamily), var(--myDefaultFontFamily);
// color: black; /*demonstrates cascade works*/
// }`;
shadow.innerHTML = `<style>
${hostStyles}
</style>
<div> Content font-family will be Arial or Helvetica</div>
<div class="my-content">User Agent font-family will be used here. Expected that the :host font-family would be used</div>`;
}
}());
</script>
</html>
The CSS vars are irrelevant to the problem.
Replacing the var with the actual value gives the following CSS which is simpler to reason with:
:host {
font-family: Arial, Helvetica;
}
.my-content {
font-family: DefinedButNotOnTheSitePage;
}
font-family: DefinedButNotOnTheSitePage;
is a syntactically valid CSS declaration, so the browser will apply it to .my-content
, ‘replacing’ the existing font-family inherited to the element just like any other CSS property.
Only if it was an invalid declaration would the browser ignore it and have Arial, Helvetica
inherited.
Since a font called DefinedButNotOnTheSitePage
cannot be found, and the CSS declaration has no fallback fonts specified, the user agent's defined fallback font is used.
For example,
:host {
color: red;
}
.my-content {
color: yellow;
}
does not make .my-content
orange. CSS declarations are not combined together; .my-content
will be yellow.
However,
:host {
color: red;
}
.my-content {
color: abcdefghi;
}
.my-content
will be red because color: abcdefghi;
is invalid: there is no color abcdefghi
at the time of reading the CSS.
For color
, the spec says what values are possible as named colors:
https://developer.mozilla.org/en-US/docs/Web/CSS/named-color
If the color value is a name but is not in this list, the CSS declaration is immediately invalid and ignored.
However font-family
does not have a fixed list of possible values in spec.
font-family = [ <family-name> | <generic-family> ]#
https://developer.mozilla.org/en-US/docs/Web/CSS/font-family
Therefore at the point of reading the CSS any string that could be a family-name is allowed.
It is only later when looking up the font given that it cannot be found. That doesn't make the CSS declaration invalid and ignored — it's ‘too late’ for that.