Polymer / old-docs-site

Old Polymer site. Replaced by these repos: polymer-project.org, polymer-library-docs
https://www.polymer-project.org
1.02k stars 2.45k forks source link

Overview of styling possibilities #1821

Open mercmobily opened 7 years ago

mercmobily commented 7 years ago

I kept on getting confused by the styling options in Polymer. Also, I am employing a great web designer with no Polymer experience, and I had a bit of a hard time explaining everything.

So, I wrote this short guide. It sums up nicely the whole "CSS" thing so that anybody can hopefully understand. I feel it's important to have one file that:

It would be awesome if you could:

1) Check that it's all factually correct (I am especially worried about having used the right technical terms throughout) 2) Give me the OK to turn this into a blog post (I will flesh things up) 3) Include it in the official documentation "somehow".

I realise that the documentation covers all of this, but I think the documentation can be made stronger in terms of best practices etc.

I could just publish this in Free Software Magazine, but I think it's important enough to be in the main documentation...

Overview

In Polymer (and web components in general), CSS encapsulation is enforced by the following principle:

Here is a verbose explanation of all this.

Defining global custom properties with :root: my-props.html

Create a file with just the <style> tag (with the the custom-style attribute). Any property or mixin defined within a :root selector will be set in the CSS global scope, and will therefore be available to modules and main documents.

my-props.html

<link rel="import" href="../polymer/polymer.html">
<style is="custom-style">

  // The custom property `--red` is defined in the CSS global scope
  :root {
    --red: {
      color: red;
    };
    --main-background: #FFFFFF;
  }

  // The following selector will have NO effect in shadow DOM of modules
  // even if `my-props.html` is loaded in file containing the module.
  .blue {
    color: blue;
  }

</style>

Include properties in the main document

Here as soon as my-props is loaded, the custom CSS properties are added to the CSS global scope (in this case, the --red mixin and the --main-background property). So, they can be used at will immediately. Also, since there is no shadow dom involved (this is just a plain HTML file), the .blue style will also work.

file.html

<link rel="import" href="bower_components/my-props/my-props.html">

<style is="custom-style">
  .red {
    @apply(--red);
    background-color: var(--main-background);
  }
</style>

<body>
  <p class="red">I am red, and have FFFFFF as background color</p>
  <p class="blue">I am blue</p>
</body>

Import props in an element

Here too, as soon as my-mixinx.html is loaded, the custom CSS properties are available (in this case, --red). However, other selectors (like .blue) will fail as they are not allowed to pierce through the shadow DOM. So, within a module, only CSS custom properties and mixins matching :root can actually be used within that module.

my-element.html

<link rel="import" href="bower_components/my-props/my-props.html">
<link rel="import" href="../polymer/polymer.html">

<dom-module is="my-element">
  <style>
    .red {
      @apply(--red);
      background-color: var(--main-background);
    }
    ...
  </style>

  <p class="red">I am red, and have FFFFFF as background color</p>
  <p class="blue">I am NOT blue</p>

  <script>
    Polymer({
      is: 'my-element'
    });
  </script>

</dom-module>

Defining CSS selectors and use them in files and modules: my-classes.html

If you want to define styles and make them available both within an element or a main HTML document, you will need to encapsulated them and then include them in the element or the main HTML document.

Create a module where the template only contains a <style> -- no actual HTML nor Polymer() call:

my-classes.html

<link rel="import" href="../polymer/polymer.html">
<dom-module id="my-classes">
  <template>
    <style>
      .red {
        color: red;
      }
    </style>
  </template>
</dom-module>

Include classes in the main document

Just importing such a file will not affect CSS styles in the document -- it will just define a template within a <dom-module> element. In order to actually enable those styles, they need to be imported in a <style> tag.

file.html

<head>

  <link rel="import" href="bower_components/my-classes/my-classes.html">

  <style is="custom-style" include="my-classes"></style>
</head>
...
<body>
  <p class="red">This is red</p>
</body>

This will have the effect of actually applying the styles defined in my-classes.html.

TODO: check that the style will strictly only apply to that document, or if standard CSS rules will apply.

Import classes in an element

You can import my-classes.html, and then include it in the element's style tag, in order to have the extra classes available within the element's shadow DOM:

my-element.html

<link rel="import" href="bower_components/my-classes/my-classes.html">
<link rel="import" href="../polymer/polymer.html">

<dom-module is="my-element">
  <style include="my-classes">  
    ...
  </style>

  <p class="red">This is red</p>

  <script>
    Polymer({
      is: 'my-element'
    });
  </script>

</dom-module>
robdodson commented 7 years ago

Deleted my prior comments because I didn't realize your original intent was to create a theme file.

OK let's back way up.

First, a custom property != a mixin. Custom properties let you set 1 css property. A mixin lets you set a whole bunch of css properties. Custom properties are natively support by all browsers except for IE/Edge. Mixins are not natively supported by any browser.

Here are the correct patterns for defining and using custom properties and mixins in Custom Elements:

Defining a custom property:

html {
  --background-color: red;
}

Using a custom property in an element:

:host {
  background: var(--background-color, blue); /* blue is the fallback value */
}

Defining a mixin:

html {
  --background-mixin: {
    background-image: url('...');
    background-color: pink;
    background-position: 0% 0%;
  }
}

Using a mixin in an element:

:host {
  background-image: url('...');
  background-color: green;
  background-position: 50% 50%;  /* The above 3 properties are fallback values */
  @apply(--background-mixin);
}

The way your element exposes styling hooks is exactly as I have outlined above. For example, you could say that the above custom property element exposes a custom property of --background-color. The consumer of your custom element can choose to set this value to theme your element.

If you're building a custom element it probably should not define a value for a custom property/mixin, unless you are only using that property internally. Instead it should only consume properties/mixins. This is because you don't want the act of including a custom element to suddenly change the theme of your entire site. That would violate the idea of css scoping/encapsulation.

/* Inside a Custom Element's shadow root... */
:root {
  --background-color: red;  /* probably bad */
}
:host {
  background: var(--background-color, red); /* Good. We only consume properties */
}

Also I would suggest avoiding the use of :root and instead use html when you are trying to define document level custom properties. I know the docs say to use :root and technically I think that's fine to do at the document level, but Polymer also used to allow you to use :root inside of the Shadow DOM and that was actually a bug. Avoiding :root all together might just be a good habit to get into. cc @arthurevans to correct me if I'm totes wrong about stuff

mercmobily commented 7 years ago

@robdodson wait a sec, my man intent wasn't about creating a theme, as much as explaining how styling works in general. Then, this info is usable to create themes, consume them, etc.

I know mixins and properties are different. The confusion in the document came from the fact that I see mixins as properties are strictly inter-related -- they are just used differently. I mean, writing:

:root {
  --my-property: red;
}

Or:

:root {
   --my-mixin: {
     color: red;
     background-color: blue;
     ...
   }
}

In my little head is still "assigning" something to the "variables" --my-property and --my-mixin. Sure, one is a property and the other one is a mixin. But... I updated my post, adding both a mixin and a property. This actually makes it more complete.

Thank you!

mercmobily commented 7 years ago

(Ah, let me know if I should change :root with HTML too -- I did't address that in your response, sorry!)

arthurevans commented 7 years ago

Hey @mercmobily. Sorry for the late response. Let me go though your first section, with advance apologies for being nitpicky.

In Polymer (and web components in general), CSS encapsulation is enforced by the following principle:

Just to clarify, we're talking about shadow DOM here, not anything specific to Polymer. (Except for style modules, at the end, and some details about Polyfills.) I note this because in the past we've had confusion about what is custom elements vs. what is shadow DOM vs. what is Polymer.

Global styles won't apply to tags in the shadow DOM of a component

This depends on what you mean by "apply". Really, when we're talking about shadow DOM style encapsulation, what we're talking about is selector matching. A selector in the main document can't match an element inside a shadow tree. A selector inside a shadow tree can only match:

If you're using shady DOM, note that CSS styles in the main document can leak into shadow trees. The fix for this is to define document-level styles inside a custom-style element.

The only things that can "leak" from a global