cssvue.jsvuetify.jsv-select

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/font@4.x/css/materialdesignicons.min.css" rel="stylesheet">
  <link href="https://cdn.jsdelivr.net/npm/vuetify@2.0.1/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/vue@2.6.10/dist/vue.js"></script>
  <script src="https://cdn.jsdelivr.net/npm/vuetify@2.0.1/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

  • 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

    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/font@4.x/css/materialdesignicons.min.css" rel="stylesheet">
      <link href="https://cdn.jsdelivr.net/npm/vuetify@2.0.1/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/vue@2.6.10/dist/vue.js"></script>
      <script src="https://cdn.jsdelivr.net/npm/vuetify@2.0.1/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.