Problems with scoped styles and deep styling in vue 3 - vue.js

Im having a lot of problems triying to style a child from parent in vue3.
In this case, i create a generic button with some css properties, and i try to customize this button from other component
Parent
<template>
<OwnButton
class="accept-button"
#ownButtonClicked="emit('accept')"
>
<slot>
ACCEPT
</slot>
</OwnButton>
</template>
<script setup>
import OwnButton from 'path/to/own-button.vue';
const emit = defineEmits(['accept']);
</script>
<style scoped>
.accept-button :deep(.own-button)
{
background-color : #4CAF50 !important;
outline-color : green !important;
}
.accept-button :deep(.own-button:hover)
{
background-color: green !important;
}
</style>
Child
<template>
<button
class="own-button"
type="button"
#click="emit('ownButtonClicked')"
v-on:keyup.enter="emit('ownButtonClicked')"
>
<slot>
</slot>
</button>
</template>
<script setup>
const emit = defineEmits
([
'ownButtonClicked'
]);
</script>
<style scoped>
.own-button
{
background-color : azure;
outline-color : lightblue;
color : black;
margin : 2px;
padding : 5px;
border-radius : 15px;
border : 0;
box-shadow : 0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19);
outline-style : solid;
min-width : 100px;
max-width : 150px;
}
.own-button:hover
{
cursor: pointer;
}
</style>
I tried everything I could think of, including using :deep(button) in parent

This is a design limitation of Vue 3 when dealing with multi-root nodes
I wrote about this in my article Scoped styles and multi-root nodes don't work well together.
Understanding the issue
In Vue 3 we can finally have more than "one root node" components. That is great, but there is a design limitation when doing that. Imagine we have a child component:
<template>
<p class="my-p">First p</p>
<p class="my-p">Second p</p>
</template>
And a parent component:
<template>
<h1>My awesome component</h1>
<MyChildComponent />
</template>
<style scoped>
// There is no way to style the p tags of MyChildComponent
.my-p { color: red; }
:deep(.my-p) { color: red; }
</style>
There is no way from the scoped styling of the multi-root parent component to style the child component's p tags.
So in short, a multi-root component, can't target multi-root child component's styles with scoped styles.
Solutions
👉 💡 The best way to fix that would be to wrap the parent or child component (or both) so we have only one root element.
But if you absolutely need both to have multi-root nodes, you can:
Use a non-scoped style
<style>
.my-p { color: red; }
</style>
Use CSS Modules
<template>
<h1>My awesome component</h1>
<MyChildComponent :class="$style.trick" />
</template>
<style module>
.trick {
color: red;
}
</style>
Since we are specifying a class here, then the multi-root child component has to explicitly specify the attribute fallthrough behavior.
If you want my opinion, unless you absolutely need a multi-root node component, go with a single root node and don't deal with this design limitation at all.

I think it is related to where you apply the main class.
Try to put a wrapper around like that:
<template>
<div class="accept-button">
<OwnButton
#ownButtonClicked="emit('accept')"
>
<slot>
ACCEPT
</slot>
</OwnButton>
</div>
</template>
<script setup>
import OwnButton from './Comp.vue';
const emit = defineEmits(['accept']);
</script>
<style scoped>
.accept-button :deep(.own-button)
{
background-color : yellow !important;
outline-color : green !important;
}
.accept-button :deep(.own-button:hover)
{
background-color: green !important;
}
</style>
Here is the example above.

Related

Vue3 - possible to bind prop to background-image in style?

Say I have the following component:
<template>
<q-layout view="lHh lpR lFf">
<q-page-container>
<div class="image-container">
Image Container
</div>
<slot />
</q-page-container>
</q-layout>
</template>
<script lang='ts' setup>
const props = defineProps({
image: { type: String, required: true, default: '' },
});
// console.log(image);
</script>
<style lang="scss" scoped>
.image-container {
display: none;
#media (min-width: $breakpoint-sm-min) {
background: v-bind("props.image") no-repeat left center fixed;
display: block;
background-size: cover;
height: 100%;
width: auto;
}
}
</style>
I can verify that the prop comes through as a string correctly, and according to the docs I can use v-bind to more easily insert dynamic values to the styles.
The image doesn't show up though, and I see this when I open the inspector:
I've also tried variations of wrapping the v-bind inside of url(), but nothing appears to be working. Is this just not yet possible with Vue3?
Note: I am currently using Vue v3.2.29.
Thanks to #tony19 in the comments, we figured out that I wasn't too far off. Essentially, rather than trying to use url() in the css, I've moved that up to the parent that is passing down the prop.
Parent.vue
<template>
<Child :image="`url(${image})`">
...
</Child>
</template>
Child.vue
<template>
<q-layout view="lHh lpR lFf">
<q-page-container>
<div class="image-container">
Image Container
</div>
<slot />
</q-page-container>
</q-layout>
</template>
<script lang='ts' setup>
const props = defineProps({
image: { type: String, required: true, default: '' },
});
</script>
<style lang="scss" scoped>
.image-container {
display: none;
#media (min-width: $breakpoint-sm-min) {
background: v-bind("props.image") no-repeat left center fixed;
display: block;
background-size: cover;
height: 100%;
width: auto;
}
}
</style>

Nuxt: css of error.vue appears in page rendering

I am new to Nuxt.
I have a page
pages/page1.vue
<template>
<h1 class="title">Page</h1>
</template>
<style>
.title {
font-size: 16px;
}
</style>
Then, under layouts I have an error.vue
layouts/error.vue
<template>
<h1 class="title">Error Page</h1>
</template>
<style>
.title {
color: red;
font-size: 18px;
}
</style>
What I found is that when page1 is rendered, the title appears in Red. I checked the inspect elements and found that the CSS of error as well as of page 1 is applied.
I do not have a default.vue in the layouts directory.
As mentioned this is my first project in Nuxt (or vue) and want to understand how to ensure that CSS of a page are applied on that page only. This is in development mode (npm run dev). Thanks
The issue is CSS without scoped attribute renders at application level. You should use scoped attribute in styles tag as
layouts/error.vue
<template>
<h1 class="title">Error Page</h1>
</template>
<style scoped>
.title {
color: red;
font-size: 18px;
}
</style>
**When a <style> tag has the scoped attribute, its CSS will apply to elements of the current component only otherwise consider global style.

Vuetify: changing min-height of v-application--wrap

I am using a vuetify component as a widget on a page along with another content after the widget. However, I have too much free space between the widget and the rest of the page. The vuetify app takes too much height and I can't figure out how to remove it.
Here's how it look in the browser.
I have tried to override the css of App.vue in the following way but it doesn't work. Any suggestions?
<style scoped>
[data-vuetify] {
overflow-y: hidden;
}
[data-vuetify] .v-application--wrap {
min-height: 0vh !important;
}
</style>
Here's how App.vue looks like:
<template>
<div data-vuetify>
<v-app id="app">
<router-view></router-view>
</v-app>
</div>
</template>
<style scoped>
section {
margin: 10px 0;
}
[data-vuetify] {
overflow-y: hidden;
}
[data-vuetify] .v-application--wrap {
min-height: 0vh !important;
}
</style>
Here is the solution that worked for me.
<style scoped lang="scss">
::v-deep .v-application--wrap {
min-height: fit-content;
}
</style>
I have had the same problem and I have solved it as follows:
<style>
.v-application--wrap {
min-height: 0vh !important;
}
</style>
In App.vue
Also, I have built it as a library using vue-cli
I'm new to Vue and Vuetify.
Try unsetting it via CSS in your App.vue file:
<style>
.v-application--wrap {
min-height: unset;
}
</style>
It should unset the default rule "min-height: 100vh;".
For me, I removed <v-app> from the component and added it to the enclosing page (I'm using Nuxt) which removed the extra space from the component.

Bootstrap-vue Tab style

I'm having trouble overriding bootstrap styles in my single file component when using bootstrap-vue
My file looks like this:
<template>
<b-tabs pills vertical>
<b-tab title="This title" title-item-class="mytab" acitve>
Some tab
</b-tab>
<b-tab title="This title 2" title-item-class="mytab">
Some other tab
</b-tab>
</b-tabs>
</template>
<script>
export default {}
</script>
<style lang="less" scoped>
.nav-pills .mytab .nav-link:not(.active) {
background-color: red !important;
}
.nav-pills .mytab .nav-link {
background-color: blue !important;
}
.tab-content > .tab-pane {
border: 1px solid;
border-left: 0px none;
}
</style>
I can inspect my component and I can see that the "mytab" class is being added to the li parent divs with nav-item classname but the css isn't showing up.
It works here: https://jsfiddle.net/3xwmm1qt/43/ but I'm pretty sure it's because the css is being loaded after the page renders. I'm not 100% about that.
Updated I also tried removing the 'scoped' attribute from the style tag and the css still would work. It still doesn't even show up when I inspect the div. I can still see the classname, but in the Rules tab (using FF) there's no styling for my custom classname.
Yeah the difference between the jsfiddle and your code is that you've written scoped CSS (less):
Here's how you fix it, Remove the scoped from style:
<style lang="less">
.nav-pills .mytab .nav-link:not(.active) {
background-color: red !important;
}
.nav-pills .mytab .nav-link {
background-color: blue !important;
}
.tab-content > .tab-pane {
border: 1px solid;
border-left: 0px none;
}
</style>
Scoped means that the css code will only work in this element and vue will try to do it only for the classes attached to the elements which have the class you tried to override while not being scoped means that it'll do it for the entire document hence will override bootstrap's css.

Use Vue variable in style section of a component

Is it possible to use a variable with the style tag of a component? Basically I have imported a mixin to my style tag that accepts 2 colors to create a gradient within a class. It works great but I want this dynamic so I can set it via a database. I understand I can bind a style via inline but a gradient for a div is rather long and works way better as a mixin.
component:
<template>
<section class="section" v-bind:class=" { 'color-section' : content.gradient_color_one }">
<div class="container">
<div class="columns">
<div class="column is-half">
<h2 class="is-size-4" v-html="content.title"></h2>
<div class="section-content" v-html="content.description"></div>
<a class="button" :href=" '/'+ content.button_link">{{ content.button_text }}</a>
</div>
<div class="column">
<img :src="content.image" :alt="content.title" />
</div>
</div>
</div>
</section>
</template>
<script>
export default {
props:[
'content'
],
}
</script>
<style lang="scss" scoped>
#import "../../sass/helpers/mixins";
.color-section{
color:red;
#include diagonalGradient( content.gradient_color_one , content.gradient_color_two);
}
</style>
mixins
#mixin diagonalGradient($top, $bottom){
background: $top;
background: -moz-linear-gradient(-45deg, $top 0%, $bottom 100%);
background: -webkit-gradient(left top, right bottom, color-stop(0%, $top), color-stop(100%, $bottom));
background: -webkit-linear-gradient(-45deg, $top 0%, $bottom 100%);
background: -o-linear-gradient(-45deg, $top 0%, $bottom 100%);
background: -ms-linear-gradient(-45deg, $top 0%, $bottom 100%);
background: linear-gradient(135deg, $top 0%, $bottom 100%);
filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#92fe9d', endColorstr='#00c8ff', GradientType=1 );
}
In Vue 3 you can now use v-bind in single file component <style> sections like this:
<style scoped>
.color-section {
color: v-bind('sectionColor');
}
</style>
You should use computed properties, as they probably are the best and cleanest way to achieve what you're trying to do.
They also have a whole site about it on the Vue Docs:
https://v2.vuejs.org/v2/guide/class-and-style.html
Basically, you COULD do something like this:
computed: {
style () {
return 'background: ' + this.top + ';' + '...'
}
}
Instead of passing the mixin, you could then pass the top and bottom variables. This is quite handy because in your computed style () function you have the freedom to do any javascript related stuff you want, so you can have conditionals, expressions and whatnot. Gives you powerful control over your style ;)
as we can see from #Maccesch's answer you can now use v-bind() in <style></style> section of vue sfc (.vue file) as of vue3, I found his answer very useful so I tought it's worth mentioning some notes:
as vue3 docs states :
The syntax works with <script setup>, and supports JavaScript expressions (must be wrapped in quotes):
so we only need to use quotes when we are going to use a js expression in our style block.
another thing to note, I didn't see anything mentioned about using const color3 = ref('green') in vue docs and when I tried it in a vue playground I found out it will get unwrapped automatically and we don't need to use v-bind('color3.value') and we can use it like v-bind(color3).
take a look at examples:
<template>
<h1>Header number one</h1>
<h2>Header number two</h2>
<h3>Header number three</h3>
</template>
<script setup>
const theme = {color:'red'}
const color2 ='blue'
const color3 = ref('green')
</script>
<style scoped>
/* we don't need to use quotes here */
h1 {
color: v-bind(color2);
}
/* we are using quotes here (' ') because theme.color is a javaScript expression */
h2 {
color: v-bind('theme.color');
}
h3 {
color: v-bind(color3);
}
</style>