Skip to content

Styling

Styles are plain CSS in a css template.

import { define, html, css } from '@alacris/core';
define('x-card', {
styles: css`
:host { display: block; border: 1px solid #ddd; border-radius: 8px }
h3 { margin: 0 }
`,
setup: () => html`<h3><slot name="title"></slot></h3><slot></slot>`,
});

css interns by text: identical CSS always returns the same constructed stylesheet. Two components sharing a reset share the parse, and adopting a sheet into a shadow root is a pointer copy, not a parse.

const reset = css`*, ::before, ::after { box-sizing: border-box }`;
define('x-a', { styles: [reset, css`:host { display: block }`], setup });
define('x-b', { styles: [reset, css`:host { display: flex }`], setup });
// `reset` is parsed once, and both components adopt the same object

A thousand elements of the same component cost one parse and a thousand pointer copies.

Interpolating one sheet into another inlines its text, so stylesheets compose without anything being parsed twice:

const tokens = css`:host { --gap: .75rem }`;
const base = css`${tokens} :host { display: grid; gap: var(--gap) }`;

styles accepts a sheet, a raw CSS string, a CSSStyleSheet, or an array of any of those, applied in order:

styles: [reset, tokens, css`:host { color: red }`]

Dynamic values belong in custom properties

Section titled “Dynamic values belong in custom properties”

Do not rebuild a stylesheet to change a colour. Bind a custom property instead — one property write, no CSS re-parse, and the browser does the rest:

html`<div style=${() => ({ '--bar-fill': pct() + '%', opacity: fade() })}>`
.bar { width: var(--bar-fill) }

style accepts an object with custom properties included, and clears any key you stop passing. class accepts objects and arrays:

html`<button class=${() => ({ btn: true, 'btn--on': active() })}>`

A sheet can be rewritten in place. Every element that adopted it updates on a single write — no re-adoption, no new rules, no re-render:

const skin = css`:host { --tone: #111 }`;
skin.replace(':host { --tone: #eee }'); // every instance, immediately

That is the cheapest possible theme switch.

Style the element itself:

:host { display: block }
:host([disabled]) { opacity: .5 }
:host(.compact) { padding: .25rem }

:host only matches from inside the component. Note that a bare tag selector in the page beats it, which is usually what you want — see theming.

Content passed in from the page stays in the light DOM. Reach it with ::slotted(), which matches only top-level slotted elements:

::slotted(p) { margin: 0 }
::slotted(.tag) { font-size: .85rem }

With shadow: false there is nothing to encapsulate — your page’s CSS applies normally, and styles are added to the containing document once rather than per element.