'Vuetify v-select component width changing

My v-select components should have a fixed width (60px), they fit in a table cell, and I want to prevent them from changing the width after value selected.

They change the width and drop-down arrow moves to the right after selection, so if there a way to decrease the size of an icon or its padding/margin it might be helpful.

Don't really know how to get props of this arrow and how this calls.

Here is the reproducible

https://codesandbox.io/s/competent-dew-eixq2?file=/src/components/Playground.vue

EDIT: Add snippet

<!DOCTYPE html>
<html>

<head>
  <link href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900" rel="stylesheet">
  <link href="https://cdn.jsdelivr.net/npm/@mdi/[email protected]/css/materialdesignicons.min.css" rel="stylesheet">
  <link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/vuetify.min.css" rel="stylesheet">
  <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no, minimal-ui">
  <style>
    .select {
      max-width: 60px;
      max-height: 60px;
      font-size: 11px;
    }
    
    .col {
      max-width: 60px;
      max-height: 60px;
    }
  </style>
</head>

<body>
  <div id="app">
    <v-app>
      <v-row>
        <div class="col" v-for="col in cols" :key="col">
          <v-select class="select" :items="variants" item-value="name" item-text="name" label="" dense outlined hide-details single-line v-model="selected">
          </v-select>
        </div>
      </v-row>
    </v-app>
  </div>

  <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/vue.js"></script>
  <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/vuetify.js"></script>
  <script>
    new Vue({
      el: '#app',
      vuetify: new Vuetify(),
      data: {
        selected: "",
        cols: [1, 2, 3, 4, 5],
        variants: [{
            id: 0,
            name: ""
          },
          {
            id: 1,
            name: "1:0"
          },
          {
            id: 2,
            name: "0:1"
          },
          {
            id: 3,
            name: "1:0 B"
          },
          {
            id: 4,
            name: "0:1 B"
          },
          {
            id: 6,
            name: "1:0 R"
          },
          {
            id: 7,
            name: "0:1 R"
          },
          {
            id: 8,
            name: "1:0 F"
          },
          {
            id: 9,
            name: "0:1 F"
          },
        ],
      },
    })
  </script>
</body>

</html>


Solution 1:[1]

The basic problem is that v-select has some styling (specifically padding and margin) that does not work very well at small widths.

This is the innerHTML of the rendered v-select, with the styles that need reducing

<div class="select...">                                      
  <div class="v-input__control">
    <div role="button" class="v-input__slot">                <!-- padding-right: 12px; -->
      <div class="v-select__slot">
        <div class="v-select__selections">                               
          <div class="v-select__selection--comma">           <!-- margin-right: 4px; -->
            1:0 B
          </div>
        </div>
        <div class="v-input__append-inner">                  <!-- padding-left: 4px; -->
          <div class="v-input__icon v-input__icon--append">  <!-- width: 24px; min-width: 24px;-->
            <i aria-hidden="true" class="v-icon..."></i>                 
          </div>
        </div>
      </div>
    </div>
  </div>
</div>

Aside from those innerHTML changes, you want a fixed width of 60px per column so change

<style>
  .select {
    max-width: 60px;
    ...
  }

to

<style>
  .select {
    width: 60px;
    ...
  }

Adjusting Vuetify inner styles

Looking at the Vuetify issues around styling, there's suggestions of using un-scoped style blocks, or deep-scoped style blocks, but neither worked for me. Vuetify have said they are working on a revamp of the way styles are applied to overcome the issues.

Fortunately Vue itself has tools to do it in javascript.

These are the key steps

  • add a reference to the v-select so that it's accessible in javascript
  • add a change handler to the v-select so that elements can be re-styled when the user selects something
  • define the style adjustments in an object so you can easily tweak them
  • add a method to apply the styles
  • call the method in mounted() and the v-select @change handler
  • use Vue.nextTick() to allow Vuetify to style first, then our custom styles are applied

Here's the adjusted code snippet. I put in some severely minimal padding and margins, and maximized the space for the selected value (to avoid text wrapping). You may want to play with the styles as I'm not sure I understood all of the requirements.

<!DOCTYPE html>
<html>

<head>
  <link href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900" rel="stylesheet">
  <link href="https://cdn.jsdelivr.net/npm/@mdi/[email protected]/css/materialdesignicons.min.css" rel="stylesheet">
  <link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/vuetify.min.css" rel="stylesheet">
  <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no, minimal-ui">
  <style>
    .select {
      width: 60px;
      max-height: 60px;
      font-size: 11px;
    }
    
    .col {
      max-width: 60px;
      max-height: 60px;
    }
  </style>
</head>

<body>
  <div id="app">
    <v-app>
      <v-row>
        <div class="col" v-for="col in cols" :key="col">
          <v-select ref="select" @change="applyCustomStyles"
 class="select" :items="variants" item-value="name" item-text="name" label="" dense outlined hide-details single-line v-model="selected">
          </v-select>
        </div>
      </v-row>
    </v-app>
  </div>

  <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/vue.js"></script>
  <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/vuetify.js"></script>
  <script>
    const customStyles = {
      ".v-input__slot": {
        padding: "0 0 0 4px",
      },
      ".v-select__selections": {
        width: "27px",
      },
      ".v-select__selection--comma": {
        margin: "7px 0 7px 0",
      },
      ".v-input__append-inner": {
        "padding-left": "0",
      },
      ".v-input__icon": {
        width: "14px",
        "min-width": "14px",
      },
    };

    new Vue({
      el: '#app',
      vuetify: new Vuetify(),
      mounted() {
        this.applyCustomStyles();
      },
      methods: {
        applyCustomStyles() {
          Vue.nextTick(() => {
            this.$refs.select.forEach((vSelect) => {
              Object.entries(customStyles).forEach(([selector, styles]) => {
                Object.entries(styles).forEach(([style, value]) => {
                  vSelect.$el.querySelector(selector).style[style] = value;
                });
              });
            });
          });
        },
      },
      data: {
        selected: "",
        cols: [1, 2, 3, 4, 5],
        variants: [{
            id: 0,
            name: ""
          },
          {
            id: 1,
            name: "1:0"
          },
          {
            id: 2,
            name: "0:1"
          },
          {
            id: 3,
            name: "1:0 B"
          },
          {
            id: 4,
            name: "0:1 B"
          },
          {
            id: 6,
            name: "1:0 R"
          },
          {
            id: 7,
            name: "0:1 R"
          },
          {
            id: 8,
            name: "1:0 F"
          },
          {
            id: 9,
            name: "0:1 F"
          },
        ],
      },
    })
  </script>
</body>

</html>

Why not a <style> block?

There are suggestions of using an un-scoped style block to override the Vuetify styles.

For example,

<style>
  .select .v-input__slot {
   padding-right: 4px
  }
  ...
</style>

The problem is the Vuetify styles are getting applied after those declared on the component.

enter image description here

You can do it by applying greater specificity than Vuetify uses, e.g.

<style>
  .select.v-text-field.v-text-field--enclosed:not(.v-text-field--rounded)>.v-input__control>.v-input__slot {
  padding-right: 4px
}
</style>

gives you this

enter image description here

so you can hunt out all the existing places where a change is necessary and copy the Vuetify selector.

The problems might occur when a new version of Vuetify is used, or the shape of the component is changed. To me, the javascript solution looks more manageable.

Solution 2:[2]

1. If you don't care about the appended icon, you can remove it with the use of append-icon prop (pass empty value):

<v-select
    class="select"
    ...
    append-icon=""
></v-select>

2. You can override the slot for the icon with your own content:

<v-select
    class="select"
    ...
>
   <template #append>
      <div class="my-custom-icon">...</div>
   </template>
</v-select>

Then add style to your my-custom-icon class to make it appear in one place:

.my-custom-icon {
    position: absolute;
    left: ...;
    right: ...;
}

3. Make use of overflow: hidden property:

<style> // don't add "scoped" attribute, otherwise the style won't be applied
.select .v-input__control {
  overflow: hidden;
}
</style>

Solution 3:[3]

Hello I have facing same problem but as I am using custom made select from vuetify v-select i can not overwrite CSS because it will change drop-down in whole project and this will be the case for all devloper working on projects

So simple solution will be make container div and give it min-width max width and width as you see fit it will solve your size change issue with table cell

It worked for me as i set my drop-down min width to max-width of container so it will be same same width

Ex

<div :class='container'>
  <v-select />  
</div>

<style>
  . container {
      max-width: 9rem // in my case it's the min width for my default selected option
   }
</style>
 

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2 Cheetha
Solution 3 Jemish Kakadiya