How to override scoped styles in Vue components? - css

Let's save I have a .Vue component that I grab off of Github somewhere. Let's call it CompB and we'll add a single CSS rule-set there for a blue header:
CompB.Vue (a dependency I don't own, perhaps pulled from Github)
<template>
...
</template>
<script>
...
</script>
<style lang="scss" scoped>
.compb-header {
color: blue;
}
</style>
I plan to nest CompB into my own project's CompA. Now what are my options for overriding the scoped rule?

After playing around a bit, I think I've got a good approach.
Global overrides
For situations where I want to override the rules in all cases for CompB I can add a rule-set with more specificity like this: #app .compb-header { .. }. Since we always only have one root (#app) it's safe to use an ID in the rule-set. This easily overrides the scoped rules in CompB.
Parent overrides
For situations where I want to override both the global and scoped rules. Thankfully VueJS allows adding both a scoped and unscoped style block into a .Vue file. So I can add an even more specific CSS rule-set. Also notice I can pass a class into CompB's props since Vue provides built-in class and style props for all components:
// in CompA (the parent)
<template>
...
<!-- Vue provides built-in class and style props for all comps -->
<compb class="compb"></compb>
</template>
<script>
...
</script>
<style lang="scss">
#app .compb .compb-header {
color: red;
}
</style>
<style lang="scss" scoped>
...
</style>
(note: You can get more specific and make the class you pass into CompB have a more unique name such as .compa-compb or some hash suffix so there is no potential for collision with other components that use CompB and a class .compb. But I think that might be overkill for most applications.)
And of course CompB may provide its own additional props for adjusting CSS or passing classes/styles into areas of the component. But this may not always be the case when you use a component you don't own (unless you fork it). Plus, even with this provided, there is always those situations where you are like "I just want to adjust that padding just a bit!". The above two solutions seem to work great for this.

You will need to add more specificity weight to your css. You have to wrap that component in another class so you can override it. Here is the complete code
<template>
<div class="wrapper">
<comp-b>...</comp-b>
</div>
</template>
<script>
import CompB from 'xxx/CompB'
export default {
components: {
CompB
}
}
</script>
<style>
.wrapper .compb-header {
color: red;
}
</style>

Related

Vue conditional style tag in single file component

I have started development on a vue web component library. Members of my team asked for the potential to remove default styles via an HTML attribute on the web component. I know that I could use CSS class bindings on the template elements, however, I was wondering if there is a way to conditionally include the style tag itself so that I would not need to change the class names in order to include the base styles or not.
Example of a component's structure
<template>
<section class="default-class" />
</template>
<script>
export default {
props: {
useDefault: Boolean
}
}
</script>
<style>
// Default styles included here
// Ideally the style tag or it's content could be included based off useDefault prop
</style>
Potential implementation
<web-component use-default="false"></web-component>
As I read your question; you want to keep <style> both affecting Global DOM and shadowDOM
One way is to clone those <style> elements into shadowDOM
But maybe ::parts works better for you; see: https://meowni.ca/posts/part-theme-explainer/
customElements.define("web-component", class extends HTMLElement {
constructor() {
super()
.attachShadow({mode:"open"})
.innerHTML = "<div>Inside Web Component</div>";
}
connectedCallback() {
// get all styles from global DOM and clone them inside the Web Component
let includeStyles = this.getAttribute("clone-styles");
let globalStyles = document.querySelectorAll(`[${includeStyles}]`);
let clonedStyles = [...globalStyles].map(style => style.cloneNode(true));
this.shadowRoot.prepend(...clonedStyles);
}
});
<style mystyles>
div {
background: gold
}
</style>
<style mystyles>
div {
color: blue
}
</style>
<div>I am Global</div>
<web-component clone-styles="mystyles"></web-component>

How to load different .css files on different components of a react application?

I have two .jsx files that represent their respective components.
The first one is, Blog.jsx
import React from "react";
import '../assets/css/blog.css';
export const Blog = () => (
<div>
<h1>Hello Blog</h1>
</div>
)
With its respective CSS file, blog.css
div { background: #ff481f; }
and the second one is, Landing.jsx
import React from "react";
import '../assets/css/landing.css'
export const Landing = () => (
<div>
<h1>Hello Landing</h1>
</div>
)
With its respective CSS file, landing.css
div { background: #86ff2b; }
I have added routing and called instances of those two components on the App.js file which is the default file of a React application. After running the application, when navigated to components, I faced a problem that the background color is the same for both of the components (It loads landing.css only and never changes).
How can I fix that problem that each component only uses its respective .css file and loads it?
By default webpack and other build tools will compile all CSS files into one, even if css was imported in separate JSX files. So you can't use different CSS files and expect you don't affect on another part of page.
You have some options:
Use BEM for naming class names.
Use cssModules. In this case elements will have their own css class name and styles will not affect any other elements except if you use :global selector.
css-module
Using html tags as css selectors is a bad practice (because there is the behaviour you describe).
You should use only css classes or inline styles (using id's is another bad practise, id's have high priority).
div {
width: 20px;
height: 20px;
}
#id {
background: red;
}
.class {
background: green;
}
<div id="id" class="class"></div>
In case using css classes there is the same problem (when you have two similar classes). And this case is decided using css modules (set of rules of building) or you can use css-in-js (external library) which has its pros and cons. Also you can use BEM (methodology) and if your application is not big you can avoid this trouble.
css modules will add to your classes random hash and instead .my-awesome-class-name you will get .my-awesome-class-name-dew3sadf32s.
So, if you have two classes .class in the first file and .class in the second file in the end you will get two classes .class-dew23s2 and .class-dg22sf and you styles will resolve as expected.
css-in-js is a similar way except you should write your styles using javascript with some profits like including only those styles that are needed at the moment (what you are looking for right now) and several others.
You can code using pure css / scss / postcss / etc but many companies already choose between css modules and css-in-js.
BEM is just naming conventions for your class names.
And lastly - if you use inline-styles using react you should remember:
{} is constructor of object and {} returns new object on every call, it's mean if you write something like:
class MyAwesomeComponent extends Component {
render() {
return <div style={{color: "red"}}></div>;
}
}
or
class MyAwesomeComponent extends Component {
render() {
const divStyles = {color: "red"};
return <div style={divStyles}></div>;
}
}
div will re-render every time your render will call because div takes new prop.
Instead, you should define your styles (for example) in outside of your class:
const divStyles = {color: "red"};
class MyAwesomeComponent extends Component {
render() {
return <div style={divStyles}></div>;
}
}
Don't define your css using HTML tags because it will affect your entire application.
use className,id or inline styling.
like- App.css
.myApp{ color: red;}
#myApp2{ color: blue;}
App.js
import './App.css'
<div className="myApp">color changed by className</div>
<div id="myApp2">color changed by id</div>
<div style={{color:'red'}}>color changed by inline object styling</div> // inline styling
This is not the best solution if you are looking forward to improve yours css imports/loads.
However could be the best solution if you dont want to go in deep in css, resolve the problem fast and keep working with HTML tag.
You can add a div for each component, define an Id for the div and wrap the component. Afterwards in the component's css fies you are going to define all the styles starting with the #id so all the css classe or HTML tag will affect just the corresponding component.
//App render in root
ReactDOM.render(
<App />,
document.getElementById('root')
);
//App
function App(props){
return [
<Blog />, <OtherComponent />
]
}
//Blog component
function Blog(props){
return <div id="blog">
<h1>I am Blog</h1>
</div>
}
//Other component
function OtherComponent(props){
return <div id="otherComponent">
<h1>Im am other component</h1>
</div>
}
/* blog.css*/
#blog h1{
color:yellow;
}
/* otherComponent.css*/
#otherComponent h1{
color:green;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="root"></div>

Nesting SCSS Rules in VueJS Modules

How can I handle nested SCSS rules, which expand on a base style, when dealing with the CSS Modules to import styles to the local component?
In my case, I have the following two SCSS files:
icon.scss
.my-icon {
// base styles for an icon
}
button.scss
.my-button {
...
.my-icon {
// special styles when an icon is in a button
}
}
Which I then import into their respective components:
MyIcon.vue
<template><i class="my-icon" /></template>
<style lang="scss" module>
#import '/path/to/icon.scss'
</style>
MyButton.vue
<template><button class="my-button"><slot /></button></template>
<style lang="scss" module>
#import '/path/to/button.scss'
</style>
The trouble is that the nested .my-icon class, in 'button.scss', generates a different hash ('._2XJ5') than the root .my-icon class, in 'icon.scss' (._2UFd). So, when I attempt to embed an icon into a button I get an output that looks like this:
<button class="._3S2w"><i class="._2UFd" /></button>
Which is correct, but the "special styles" for an icon in a button are not applied, because that class was generated as a different hash.
How do I make sure the hash value for '.my-icon' always comes out the same?
Somewhere, hidden deep in the Vue loader, the documentation talks briefly about how you can escape the scoped styling. The solution is to use a /deep/ selector, which is preprocessed by scss to give everything behind it no hash. There is something similar in normal css, but it is somewhat unrelated.
Say we have our my-button component and want to style my-icon classes inside it, we can do the following:
<template>
<div>
<slot></slot>
</div>
</template>
<style lang="scss" scoped>
/deep/ .my-icon {
background: red;
}
</style>
The following should generate something like the following. It is still scoped within your component, but .my-icon does not have to be part of your component. In this case, it could be inside the slot for example.
[data-v-f3f3eg9] .my-icon {
background: red;
}

How use custom-style or shared styles in polymer 3 preview

I need to use common styles for app, but <custom-style> doesn't work. Attribute include in <style></style> doesn't work also. How do it?
EDIT: Alternative approach, use template literals.
I realized that there is, what I feel is a much better and more
natural alternative now that template literals is being used.
In shared-styles.js:
import { html } from #polymer/polymer/polymer-element.js;
export const sharedStyles = html`
<style>
... various styles here ...
</style>
`;
Now in the custom element file you want to use it ( my-element.js ):
import { PolymerElement, html } from #polymer/polymer/polymer-element.js;
import { sharedStyles } from './shared-styles.js';
class MyElement extends PolymerElement {
... element code ...
get template() {
return html`
${sharedStyles}
<style>
... your element custom styles here ...
</style>
`;
}
... rest of your element code ...
}
For me this makes far more sense because we are using native javascript and
avoid any extra polyfills that may be required by the include=""
functionality.
I also think this is a bit more future proof, since the include="" syntax
isn't really in the spec and I don't expect it will be? (Can anyone correct me
if I'm wrong on that?)
END (Alternative approach)
OLD Answer below
This has now been documented in the upgrade guide when 3.0 went live.
See the first section under "Less common upgrade tasks" on this page:
https://www.polymer-project.org/3.0/docs/upgrade
Also, remember that you would need to import custom-style into the js module before it will work.
Should be located here: polymer/lib/elements/custom-style.js
Had the same Problem, solved it by doing it manually:
const documentContainer = document.createElement('div');
documentContainer.setAttribute('style', 'display: none;');
documentContainer.innerHTML = `
<dom-module id="shared-styles">
<template>
<style>...</style>
</template>
</dom-module>`;
document.head.appendChild(documentContainer);
Then you can use it by simply importing and including.
import './shared-styles.js';
...
static get template() {
return `<style include="shared-styles"></style>...`;
}
I got this solution from the polymer-modulizer, but i hope there will be a better way in the future.
If we follow what polymer team has done already, then you need myapp-styles.js as below
import {html} from '#polymer/polymer/lib/utils/html-tag.js';
/*
custom classes for my application
*/
const template = html`
<dom-module id="myapp-styles">
<template>
<style>
.bold{
font-weight: bold;
}
</style>
</template>
</dom-module>
`;
template.setAttribute('style', 'display: none;');
document.head.appendChild(template.content);
Then in the component page you need to import 'myapp-styles.js'; and simply include it along with custom-style tag
<style is="custom-style" include="myapp-styles iron-flex iron-flex-alignment"></style>

Dynamically inject shared styles in polymer element (polymer 1.2.3)

I do have several nested polymer elements created by myself. Currently with using polymers shared styles I'm able to inject custom styling into other elements. Unfortunately this approach is restricted to static use. So at implementation time I do need to know which Element should use which shared style by import the shared style module with <link rel="import" href="my-shared-style.html"> and <style include="my-shared-style"></style>.
But in my use case I do need to inject shared styles into polymer elements at runtime. Is there any possibility to achieve this?
UPDATE
I tried following approach inspired by Günters answer below:
Polymer({
is : 'html-grid-row',
/**
* Inject style into element
* #param {string} style
*/
injectStyle : function(style) {
var customStyle = document.createElement('style', 'custom-style');
customStyle.textContent = style;
Polymer.dom(this.root).appendChild(customStyle);
}
/**
* Additional Elements JS functionality is put here...
*
*/
)}
When I now try to dynamically add style by calling injectStyle('div {font-weight: bold;}') on elements instance at runtime the style module is injected into the element but is not displayed because polymer seems to edit custom-styles text content like the following:
<style is="custom-style" class="style-scope html-grid-row">
div:not([style-scope]):not(.style-scope) {font-weight: bold;}
</style>
Is there a way to prevent polymer from adding the :not([style-scope]):not(.style-scope) prefix to style rules?
UPDATE 2:
Referencing a global shared style using include='my-shared-style' does have the same effect.
Include this shared style which is statically imported globally using html import:
<dom-module id="my-shared-style">
<template>
<style>
div {
font-weight: bold;
}
</style>
</template>
</dom-module>
After dynamically import and referencing shared-style Polymer includes following:
<style is="custom-style" include="my-shared-style" class="style-scope
html-grid-row">
div:not([style-scope]):not(.style-scope) {
font-weight: bold;
}
</style>
SOLUTION
Finally I used a workaround for dynamically inject styling into Polymer elements at runtime by extending the <style> HTML Element with document.register('scoped-style', {extends : 'style', prototype : ...}). The injectStyle(style) method (see above) now creates a <style is="scoped-style"> element directly as child of the Elements root node. Indeed it's inspired by https://github.com/thomaspark/scoper. This works so far for me.
I used something like this successfully to inject styles dynamically
var myDomModule = document.createElement('style', 'custom-style');
myDomModule.setAttribute('include', 'mySharedStyleModuleName');
Polymer.dom(sliderElem.root).appendChild(myDomModule);
The style-module 'mySharedStyleModuleName' needs to be imported somewhere (like index.html).
See also https://github.com/Polymer/polymer/issues/2681 about issues I run into with this approach and https://stackoverflow.com/a/34650194/217408 for more details

Resources