dna-poster

DNA

Progressive Web Components.

Styles

DNA can render plain <style> tags in a template, but what about style encapsulation?

Since components can be extended and stylesheets inherited, we need to be able to scope CSS rules based on the final component definition. As well as for slotted templates, DNA can replicate the ShadowDOM specifications for styles in order to encapsulate styles which affects only nodes in the local tree.

Scoped styles

Every <style> tag rendered to a component is scoped.

import { Component, customElements, html, property } from '@chialab/dna';

class Card extends Component {
    static get observedAttributes() {
        return ['title'];
    }

    @property() title = '';

    render() {
        return html`
            &lt;style>
                h1 { color: cadetblue; }
            &lt;/style>
            &lt;h1>${this.title}&lt;/h1>
        `;
    }
}

customElements.define('x-card', Card, { extends: 'div' });

The previous component will br rendered as follow:

&lt;h1>Unaffected heading&lt;/h1>
&lt;div is="x-card" title="Alan Turing">
    &lt;style>
        [is='x-card'] h1 { color: cadetblue; }
    &lt;/style>
    &lt;h1>Alan Turing&lt;/h1>
&lt;div>

In the resulting HTML of the example above, the first H1 color will not be affected by the scoped style declaration.

How does it work?

DNA uses the component definition name to prefix all CSS rules with an unique selector for the element. For example, the rule

h1 { color: cadetblue; }

is transformed into

[is="x-card"] h1 { color: cadetblue; }
The :host pesudo selector

If you need to style the component root itself, you can use the :host selector like in Shadow DOM contexts:

:host {
    color: #333;
    font-family: sans-serif;
}

You can also define styles for component states with :host(*selector*) rule:

:host(:hover) {
    background-color: #e5e5e5;
}

:host(:hover) span {
    color: red;
}

Other styling techniques

Every component node has the is attribute populated with the defined name of the component class. You can use an attribute selector to scope your CSS, for example using the SASS nesting:

/* CUSTOM ELEMENT */
x-card {
    h1 {
        color: cadetblue;
    }
}

/* BUILT IN ELEMENT */
[is='x-card'] {
    h1 {
        color: cadetblue;
    }
}
Plain CSS

Some bundlers import CSS files in JavaScript and auto-append <link> tags. In this case, you have to manually set the scope for your rules:

/* CUSTOM ELEMENT */
x-card .title {
    color: cadetblue;
}

/* BUILT IN ELEMENT */
[is='x-card'] .title {
    color: cadetblue;
}
&lt;link href="x-card.css" rel="stylesheet" />
CSS Modules

If you are using a transpiler, you can also use CSS Modules, which imports CSS classes as JavaScript references in order to use them in a template:

.title {
    color: cadetblue;
}
import { Component, customElements, html, property } from '@chialab/dna';
import { title } from './x-card.css';

class Card extends Component {
    static get observedAttributes() {
        return ['title'];
    }

    @property() title = '';

    render() {
        return html`&lt;h1 class="${title}">&lt;/h1>`;
    }
}

customElements.define('x-card', Card, { extends: 'div' });