Components are phenomenally useful, but come with some styling challenges in real world builds.
Specifically-
- Shared library components that you want to use in a site, but which need to be styled differently.
- Individual component instances, like a CTA button, that need to be styled differently.
- Marketer-facing components, which require some ad-hoc styling control from build mode.
Approaches 1
Here I'll talk about 3 different ways you can expose varying degrees of styling control via component properties.
- Style String
Property - Class Property
- Variable Override Property
https://www.loom.com/share/17fad8f37b154eb0a66cb52260150d18
Approach Comparison
Before I dive into implementation, familiarize yourself with the pros and cons of each.
| Style String Property | Class Property | Variable Override Property | |
| Design-time | YES | YES | No, code-preview mode or published site only. |
| Styling can be applied to | Custom elements only, and their descendants via inheritance. | Custom elements only, and their descendants via inheritance. | Any element type |
| Control over what can be styled by marketers | None, they have full style string access | Some, they can only use classes you've defined. However those classes must be documented and | |
| Documentation needed | Probably, unless the marketers already know CSS style string syntax well enough | Needed, or they have no idea what classes to use. | Generally not needed, except for color syntax, measurement syntax etc. |
Style String Property
Allows you to expose full style string control to various parts of your component.
Class Property
Allows you to expose the ability to apply classes to various parts of your component. The user needs documentation somewhere as to what classes are possible.
Notes
If you use this approach make sure to check out Timothy Ricks' Lumos Extension, which can be used to improve the property UX with a class picker.
Variable Override Property
Here, component properties are bound to specially-formatted custom attributes
Code;
This code handles the transformation of your special
(() => {
const COMPONENT_ATTR = "component";
const PREFIX = "__";
console.log("[component-vars] script loaded");
function applyVarsFromAttributes(el) {
const attrs = Array.from(el.attributes);
let found = false;
for (const attr of attrs) {
const name = attr.name;
if (!name.startsWith(PREFIX)) continue;
found = true;
const cssVarName = "--" + name.slice(PREFIX.length);
const value = attr.value;
console.log(
"[component-vars] applying",
cssVarName,
"=",
value,
"to",
el
);
el.style.setProperty(cssVarName, value);
}
if (found) {
console.log("[component-vars] element updated:", el);
}
}
const roots = document.querySelectorAll(`[${CSS.escape(COMPONENT_ATTR)}]`);
console.log("[component-vars] components found:", roots.length);
for (const root of roots) {
console.log("[component-vars] processing component root:", root);
const all = [root, ...root.querySelectorAll("*")];
for (const el of all) {
applyVarsFromAttributes(el);
}
}
console.log("[component-vars] done");
})();