I'm working on the VueJS 2 project and I'm trying to clean the code but struggle with scoped styling.
Here is my requirements. :)
I have 3 components those are very similar to each others, so I decide to use mixins to merge the code into one file. Each component will use that mixins of both template and vuejs. When I want to customize the conditions of a particular component, I can simply override the code in it and it is working fine in this part.
However, one thing that I want to do more is to move the scoped style to the mixins as well. At the moment, the style is wrapped in <style lang="scss" scoped></style> tag and this style works very well on its component but I have to duplicate the styling codes into all 3 components.
I know I can add these styles to the global css file but I don't want some styles to the global scope, only one these 3 components will apply for these.
Is it any way to add these styles and apply to mixins?
What is the best practice to code this particular case?
Vue makes this easy.
Solution
To use shared styles in a component you can do this.
MyComponent.js
<template>
</template>
<script>
</script>
<style lang="scss" scoped>
#import '#/scss/shared-styles.scss';
#import 'styles.scss'; // this is the regular CSS used just by the component
</style>
Alternative
You can also import the shared CSS files in the component CSS file instead like below.
MyComponent.js
<template>
</template>
<script>
</script>
<style lang="scss" scoped>
#import 'styles.scss';
</style>
styles.scss
#import '#/scss/shared-styles.scss'
// rest of your component CSS
Automatically import global styles
If you want certain styles to be available in ALL your components you can do this.
vue.config.js
module.exports = {
...
css: {
loaderOptions: {
sass: {
prependData: `
#import "#/scss/global.scss";
`
},
},
},
}
I just found out the scoped style also affect on the child components as well.
Therefore, I found the solution, not sure is it the best practice but I feel very nice for it.
Create a WrapperComponent and I put the scoped style here and a small template.
<template>
<div>
<slot></slot>
</div>
</template>
<style lang="scss" scoped>
/* css style that will apply to all children */
</style>
What happen here is that, when we wrap whatever components with this WrapperComponent, the template will pass on the HTML via slot without any modification and style will be able to apply from now on.
In mixins, I import this wrapper and wrap the component template with the WrapperComponent. Here is the example.
import WrapperComponent from './WrapperComponent'
let MyMixins = {
template: `<wrapper-component>
<div>
Whatever HTML code here
</div>
</wrapper-component>`,
components: {
WrapperComponent,
},
};
When we use this mixins or a child component, the style from WrapperComponent will be automatically applied and also can be used with other groups of components those want to use the same parent style.
Perhaps use modules instead of setting up a style section with a scoped attribute.
https://vue-loader.vuejs.org/en/features/css-modules.html
This way your CSS will still be scoped and not part of your global styling.
I just dropped my fadeTransition.css file into my vue app /assets folder, and import like this:
<template>
<transition name="fade">
<div v-if="showContent">test</div>
</transition>
</template>
<script>
import '#/assets/fadeTransition.css';
// component definition
</script>
fadeTransition.css
/* fade menus in, not out */
.fade-enter-active {
transition: opacity .5s;
}
.fade-enter {
opacity: 0;
}
Clean and simple. Should work for scss, too.
Cheers!
Related
I'm relatively new to Angular, and I have a doubt about component stylesheets.
I have an Angular 12 app and created a component named my-component. The template of the component in question is something like this:
my-component.html
<div>
...some html...
<some-other-angular-component></some-other-angular-component>
...some other html...
</div>
some-other-angular-component is another component, either from the app itself or a third party library.
Now, what I want to do in my-component is apply some CSS rules to the contents of some-other-angular-component. I know that the HTML it generates contains classes that I can target, so I tried to add this to my component CSS:
my-component.scss
.some-other-angular-component-inner-class {
background-color: red;
}
However, this doesn't work, it appears that the component's CSS file only applies rules to the HTML defined directly in the component's template, not the HTML generated by sub-components.
Is there a way to make this work? I find myself having to add my CSS to the webapp's main style.scss file, even when I want to apply the rule only to the particular some-other-angular-component instance inside of my-component. It makes styling confusing and needlessly fragmented. Is this intended, or what am I missing?
I think you may want to look into View Encapsulation.
#Component({
selector: 'app-no-encapsulation',
template: `
<h2>None</h2>
<div class="none-message">No encapsulation</div>
`,
styles: ['h2, .none-message { color: red; }'],
encapsulation: ViewEncapsulation.None,
})
export class NoEncapsulationComponent { }
These styles will be added to head and will be applicable to other components as well if style rule matches.
Please note, with this you are only enabling this behaviour for just this component. Chances of overlapping CSS rules is still there but is lot less in comparison to directly putting styles in style.css.
I will also suggest that you add .class or #id attribute in mark up to ensure that your rules don't overlap by default.
For example:
.my-component .rule-one {
}
It will ensure that my rules are only applied are on component that has this class applied on it.
So I'm migrating an app from CRA to NextJS and I have encountered an error for the .module.scss files of some components and pages:
Syntax error: Selector ":global(.label-primary)" is not pure (pure selectors must contain at least one local class or id)
I get this error for all the :global and :local css-module selectors. Based on what I have searched I can fix this issue by wrapping the selector in a class and editing the jsx aswell. but wouldn't that defeat it's purpose?
And how is this working on the CRA version of the app and not on NextJS?
EDIT:
One solution I have for this is moving :global() selectors to the global css files that are imported in _app.js but my question is that is there any way that we can have so these styles would be usable like they are right now ( :global(...) )?
No there isn't any solution as of yet other than overriding the webpack config itself. It was working in CRA because they probably have mode: local, while Next.js has pure.
I haven't tried overriding css-loader webpack config, so I am simply suggesting a workaround. Since, you are using SCSS, you can wrap your pseudo-global [1] styles like this:
.root :global {
.foo {
color: red;
}
}
Now wrap your component/page in a div and set the class as styles.root on that element. Then, on all the child elements you can directly set className="foo".
import styles from "../styles/index.module.scss";
const IndexPage = () => (
<div className={styles.root}>
<div className="foo">This text should be red!</div>
</div>
);
export default IndexPage;
Note that, you need to consider issues regarding specificity after this method, also this doesn't directly work with animations, you need to separate the keyframes and then make them global.
Demo Sandbox
[1]: This method doesn't make the styles truly global as the styles are still scoped. The class foo will work only when some parent has styles.root as class. This is preferrable only if you didn't intend to use your :global(.selector) from other components, and were using them just because you wanted to manipulate the class names using JS without the styles object.
If you want these to be truly global, add styles.root to document.documentElement in an useEffect hook like this:
import { useEffect } from "react";
import styles from "../styles/index.module.scss";
const IndexPage = () => {
useEffect(() => {
document.documentElement.classList.add(styles.root);
return () => {
document.documentElement.classList.remove(styles.root);
};
}, []);
return (
<div className="foo">
This text should be red, even if you put it in another component until the
page is same. If you want it across pages inject it in _app or _document.
</div>
);
};
export default IndexPage;
Demo Sandbox
PS: Injecting class to html in _app or _document is not exactly same as using a global stylesheet, as it may happen that you have multi-page application, then only the CSS of the components on a particular page will be requested because of automatic CSS code-splitting done by Next.js. If that's not the case and all your pages share same CSS, then there is no need to complicate things, just go with the conventional method of importing styles in _app.
I had the same problem, the right writing is
.root:global {
color:red
}
Another approach is to make a container, wrap it, and carry it out like follows:
import style from '../styles/style.module.css'
<div className={styles.container}>
<p>Hello World</p>
</div>
.container p {
font-size: 20px;
}
And yes, you only need to include your tags and conditions in the CSS file if you have a lot of them.
I want to insert into component inline css like
<div id="slide1" :style="item.css" v-html="item.html" />
Where my item.css contains string with many css definitions e.g.
#slide1 {} #slide1 p {} #slide1 h2 {} #media(...) { #slide {} } ....
What is the best approach to do this?
So what you are doing here is not loading styles, you are loading a full css set of rules. The style property of an HTML element will only let you add css properties, not blocks of rules like #slide1 {...}.
If you want to load the rules for a singular component remotely, you will want to do use a scoped style block for your component (assuming you are using single file components), like so:
<template>
...
</template>
<style scoped>
#import url("link-to-your-remote-styles");
</style>
What we're doing above is:
making sure that the styles loaded in this component will not bleed out of its scope by using the scoped attribute of the <style> tag;
loading the styles using the #import rule from CSS
Here's a link to a sandbox where I load tailwind just for the scope of one component.
I am writing a vue app that renders inside a page on a "traditional" server-rendered site, in this container:
<div id="account-summary-container"></div>
Things work great when developing locally. But when run within the context of the website there is alot of style collision, because both my app and the website's styles are global. My app screws up styles on the entire site.
How can I scope all styles in my app to be local to the selector my app is rendered within?
My app uses bootstrap 4 styles which I am loading with css-loader.
I have webpack.config.js entrypoints like this:
entry: {
app: ["./src/scss/styles.scss", "./src/app.js"]
},
and styles.scss looks like:
#import '~bootstrap/scss/bootstrap';
#import '../css/feather.min.css';
#import '../css/icomoon-spinners.css';
#import url('https://fonts.googleapis.com/css?family=Maven+Pro:400,500,700,900');
#import 'helpers/variables';
#import 'helpers/mixins';
#import 'helpers/placeholders';
...
I am thinking css-modules might be the answer, but I can't figure out how to tell css-loader to make ALL styles local to #account-summary-container. I tried this in styles.scss:
:local(#account-summary-container) {
composes: "~bootstrap/scss/bootstrap";
composes: "../css/feather.min.css";
...
}
And it scoffed at my lame attempt with:
Error: composition is only allowed when selector is single :local class name not in ":local(#account-summary-container)", "#account-summary-container" is weird
I'm wondering if I am approaching it totally wrong. I'm hoping to not have to do alot of rewriting of styles.
In order to target only components inside of your Vue instance you have to use scoped styles. It is kind of a pain because you can't scope the css on the "main" App.vue file and let all children inherit it - you have to use scoped styles on each component that need it, which may result in more requests than you'd like..
As an example (I tested this and it should work)..
<template>
<!--
VUE/COMPONENT TEMPLATE
-->
</template>
<script>
export default {
/* ***********************
VUE/COMPONENT CODE
*********************** */
};
</script>
<!--
*********************************************************
***** SCOPE CSS PER COMPONENT ****************************
***** IMPORT VIA 'src="./path/to/cssfile.css"' ***********
**********************************************************
-->
<style scoped src="../css/style.css">
</style>
Found the answer. Pretty simple. Should had a V8 (conk).
.pony {
#import 'sub';
font-size: 100px;
}
I'm trying to use Bootstrap in a Vue component, and I want all CSS to be scoped. I tried something like this:
<style scoped>
#import "~bootstrap/dist/css/bootstrap.css";
#import "~bootstrap-vue/dist/bootstrap-vue.css";
</style>
But it doesn't seem like the css is scoped. Is there any way to do that?
<style scoped src="~bootstrap/dist/css/bootstrap.css"></style>
<style scoped src="~bootstrap-vue/dist/bootstrap-vue.css"></style>
Update: a hack using SCSS
Reason why the first solution won't work:
With scoped, the parent component's styles will not leak into child
components.
If you want a selector in scoped styles to be "deep", i.e. affecting
child components, you can use the >>> combinator
from the Vue doc for scoped CSS
The modal you mentioned is apparently not being controlled by the component where you imported bootstrap. Perhaps it's a child component. Perhaps you're using the jquery version of Bootstrap modal. Either way, the data attributes won't be added to the modal.
In order to solve this, you need Deep Selector. (you may read about it in more detail in https://vue-loader.vuejs.org/en/features/scoped-css.html)
Here's how I would import the entire Bootstrap CSS using SCSS. (I think it's impossible to do this using pure CSS only.)
<template>
<div class="main-wrapper">
/* ... */
</div>
</template>
<style scoped lang="scss">
.main-wrapper /deep/ {
#import "~bootstrap/dist/css/bootstrap.min";
}
</style>
Some pre-processors, such as Sass, may not be able to parse >>>
properly. In those cases you can use the /deep/ combinator instead -
it's an alias for >>> and works exactly the same.
The generated CSS would be similar to
.main-wrapper[data-v-656039f0] .modal {
/* some css... */
}
.. which is what you want.
BUT, I gotta say, importing the entire Bootstrap CSS is a really bad practice. Try to import only and exactly what you are going to use from bootstrap-sass instead.
This solution is hacky. But it's the only way I know that can work for your use case.
I know it's an old question but this solution work for me
<style lang="scss" scoped>
::v-deep {
#import 'bootstrap/scss/bootstrap.scss';
}
</style>
i wanted use vuetify in my app only on a page , and that crashed my css, then I use
<style scoped src="vuetify/dist/vuetify.min.css"></style>
and now all works perfectly .
<template>
<div> ....... </div>
</template>
<style scoped src="vuetify/dist/vuetify.min.css"></style>
<script> ...... </script>
For me this was the solution to get it to work and prevent leaking:
<style lang="less" scoped>
::v-deep {
#import (less) "../node_modules/vuetify/dist/vuetify.min.css";
}
</style>
The casting to less could obviously also be changed to scss.
They changed the ::v-deep selector in Vue 3, the old method still works, but is deprecated (which can lead to a lot of deprecation messages in your build if you're importing css/scss in this way).
So for the record, this is how you would go about it in Vue 3:
<template>
<div class="main-wrapper">
<div class="bootstrap-scope">
/* All children that need Bootstrap including slotted content etc */
</div>
</div>
</template>
<style scoped lang="scss">
.main-wrapper::v-deep(.bootstrap-scope) {
#import "~bootstrap";
}
</style>
You can also drop the div.main-wrapper and select the root div SFC component. Lets say your component name is my-awesome-component the selector would be:
<style scoped lang="scss">
.my-awesome-component::v-deep(.bootstrap-scope) {
#import "~bootstrap";
}
</style>
Or if you prefer not to use a generated class name you can also go for:
<style scoped lang="scss">
div:first-child::v-deep(.bootstrap-scope) {
#import "~bootstrap";
}
</style>
In actual shadowDom you would use the :host selector to select the root of your component which would make this a more concise, but from what I understand (https://github.com/vuejs/vue-loader/issues/1601) the vue-loader team didn't decide yet what to do with this.
The :deep() selector has a small section devoted to it in the official Vue 3 docs: https://v3.vuejs.org/api/sfc-style.html#deep-selectors
There's also a VueJS RFCS on the deep selector which describes in more detail why it keeps on changing: https://github.com/vuejs/rfcs/blob/master/active-rfcs/0023-scoped-styles-changes.md