This is an RFC on a proposal to introduce silent classes to CSS. This concept borrows heavily from the abandoned @apply
specification while hopefully learning from the places that particular API failed.
Styles cannot be reused across instances of DocumentOrShadowRoot
. While the shadow DOM’s encapsulation is incredibly powerful and enables developers to have full control over portions of the DOM, there are often rules that design system authors might want to reuse across contexts.
While some recent proposals will allow for style sharing across shadow roots (like constructible stylesheets and CSS module scripts), often times the target node type is different (between something like a div
and
:host`).
Re-introduce @apply
to the language, but instead of accepting custom property-style syntax --silent-class
, introduce a new type of rule prefixed tentatively with some other sigil, tentatively %
, but other options are available. The %
prefix is preferred here due to its relationship with Sass’ silent classes.
- Sheet A*
:root {
--primary: #f6f6f6;
--secondary: #141414;
}
%card {
background: var(--primary);
border-radius: 4px;
box-shadow: 0 0 0 4px var(--secondary);
color: var(--secondary);
}
.card {
@apply %card;
&.card--inverted {
--primary: #141414;
--secondary: #f6f6f6;
}
}
.container {
@apply %card;
}
Should be equivalent to
:root {
--primary: #f6f6f6;
--secondary: #141414;
}
.card {
background: var(--primary);
border-radius: 4px;
box-shadow: 0 0 0 4px var(--secondary);
color: var(--secondary);
&.card--inverted {
--primary: #141414;
--secondary: #f6f6f6;
}
}
.container {
background: var(--primary);
border-radius: 4px;
box-shadow: 0 0 0 4px var(--secondary);
color: var(--secondary);
}
The real power of this proposal comes in reusable stylesheets where a single sheet can be added to multiple contexts:
const silentClasses = new CSSStyleSheet();
silentClasses.replace(`%card {
background: var(--primary);
border-radius: 4px;
box-shadow: 0 0 0 4px var(--secondary);
color: var(--secondary);
}`);
document.adoptedStyleSheets = [ silentClasses ];
/** later */
const customElSheet = new CSSStyleSheet();
customElSheet.replace(`:host { @apply %card; }`);
class CustomCard extends HTMLElement {
constructor() {
super();
const root = this.attachShadow({ mode: 'open' });
root.adoptedStyleSheets = [silentClasses, customElSheet];
root.append(document.createElement('slot'));
}
}
customElements.define('custom-card', CustomCard);
This would allow the styles within %card
to be applied to the shadow host regardless of what DocumentOrShadowRoot
instance the element gets instantiated.
This technique could potentially be used in conjunction with Justin Fagnani's Reference selectors concept, such that given Sheet A above, the silent class could be exported as a rule:
import { card } from 'sheetA.css';
const div = document.createElement('div');
div.cssReferences.apply(card);
One possible alternative to this is using ::part
, but currently that selector doesn’t affect light DOM content.