Skip to content

Instantly share code, notes, and snippets.

@calebdwilliams
Last active May 29, 2021 16:35
Show Gist options
  • Save calebdwilliams/866ca817989de9b2b3b541e89666a0c2 to your computer and use it in GitHub Desktop.
Save calebdwilliams/866ca817989de9b2b3b541e89666a0c2 to your computer and use it in GitHub Desktop.
Silent classes proposal WIP

Introduce silent classes

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.

The problem

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`).

The idea

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);

Alternative solutions

One possible alternative to this is using ::part, but currently that selector doesn’t affect light DOM content.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment