Subscribe on changes!

select v-model does not set option selected attribute in rendered HTML using SSR

avatar
Jan 28th 2022

Version

3.2.29

Reproduction link

sfc.vuejs.org/

Steps to reproduce

Create a template with <select v-model="..."> and some <option :value="..."> elements. And then look at the rendered HTML. There will never be a selected attribute on the option elements.

When loading the SSR rendered page with disabled JavaScript it will always select the first option, because no option in the HTML is actually selected.

What is expected?

Similar behavior to <input v-model="...">. For input elements this will insert a value="..." attribute. Though <select v-model="..."> should insert a selected attribute for the selected option element. (Or elements when using <select multiple>).

What is actually happening?

No selected attribute is added to the SSR rendered HTML.


A workaround is to set :selected manually:

<select v-model="selected">
  <option disabled value="">Please select one</option>
  <option value="A" :selected="selected === 'A'">A</option>
  <option value="B" :selected="selected === 'B'">B</option>
  <option value="C" :selected="selected === 'C'">C</option>
</select>
avatar
Feb 2nd 2022

This is a bit tricky as the current implementation of v-model on select elements does set the selected attribute on mounted, and do so on purpose, because it has to wait for the options to be rendered to then pick the right one to set the attribute on.

So in that sense, this works as intended and it's not really a bug.

Though I see the challenge when JS is disabled on the client. So far I don't have a bright idea how this could be improved, besides the workaround already documented.

avatar
Feb 2nd 2022

@LinusBorg Thanks for the explanation. This sounded to me like <input type="radio"> should have the same issue. But it works perfectly well for radio buttons and disabled JavaScript. Or is this something different?

<script setup lang="ts">
const options = ['a', 'b', 'c'];
const value = 'b';
</script>

<template>
    <input type="radio" v-for="option in options" :value="option" :key="option" v-model="value" />
</template>

This will correctly set the checked attribute to the input with value="b" with disabled JavaScript

avatar
Feb 2nd 2022

Radio buttons are different, as Vue can evaluate each one on their own during render, and thus can set the checked attribute before mounting the element.

The select element on the other hand needs to compare its v-model value to the options children that are created independently from it.

Not saying this is not solvable with another approach, just describing the current one and why it works the way it does.

avatar
Mar 2nd 2023

This also causes select inputs to show empty or the first option selected after the initial browser's render. Which is not good.