Uncheck radio button

Here goes a simple one without using v-model and just only takes one variable for storing the current selected value.

var app = new Vue({
  el: '#app',
  data: {
    list: ['one', 'two', 'three'],
    selected: 'two'
  },
  methods: {
    uncheck: function (val) {
      if (val === this.selected) {
        this.selected = false
      } else {
        this.selected = val
      }
    }
  }
})
<script src="//cdnjs.cloudflare.com/ajax/libs/vue/2.4.2/vue.min.js"></script>
<div id="app">
  <div v-for="(val, key) in list">
    <input type="radio" :checked="selected===val" :id="val" @click="uncheck(val)" :key="'input' + key">
    <label :for="val" :key="'label' + key">{{ val }}</label>
  </div>
</div>

The difficulty is that v-model is changing the value of selected before you can examine it to see what its previous value is. You need another variable.

The idea is: when you click an item, it checks whether it matches previouslySelected (rather than selected). If it matches, unselect. Then set previouslySelected to the value of selected.

The click handler should be on the input, not on the label; it is the function of the label to forward clicks to the input, anyway.

var app = new Vue({
  el: '#app',
  data: {
    list: ['one', 'two', 'three'],
    selected: 'two',
    previouslySelected: 'two'
  },
  methods: {
    uncheck: function(val) {
      if (val === this.previouslySelected) {
        this.selected = false;
      }
      this.previouslySelected = this.selected;
    },
    uncheckAll: function() {
      this.selected = false;
    }
  }
})
<script src="//cdnjs.cloudflare.com/ajax/libs/vue/2.4.2/vue.min.js"></script>
<div id="app">
  <div v-for="(val, key) in list">
    <input type="radio" name="radio" :value="val" v-model="selected" :id="val" @click="uncheck(val)">
    <label :for="val">{{ val }}</label>
  </div>
  <button @click="uncheckAll">Uncheck all</button>
</div>
Note: the below method does not work with Vue 2.5 or later because clicking an already-selected radio does not cause a set to happen. (thanks to Vlad T. in the comments)

A more in-the-data way of approaching it would be to have a computed based on your selected, and have its setter do the check for re-selecting. No click handler. Everything is handled in the normal course of setting the value.

var app = new Vue({
  el: '#app',
  data: {
    list: ['one', 'two', 'three'],
    d_selected: 'two'
  },
  computed: {
    selected: {
      get() {
        return this.d_selected;
      },
      set(v) {
        if (v === this.d_selected) {
          this.d_selected = false;
        } else {
          this.d_selected = v;
        }
      }
    }
  },
  methods: {
    uncheckAll: function() {
      this.d_selected = false;
    }
  }
})
<script src="//cdnjs.cloudflare.com/ajax/libs/vue/2.4.2/vue.min.js"></script>
<div id="app">
  <div v-for="(val, key) in list">
    <input type="radio" name="radio" :value="val" v-model="selected" :id="val">
    <label :for="val">{{ val }}</label>
  </div>
  <button @click="uncheckAll">Uncheck all</button>
</div>

You can use the prevent modifier to prevent the default behavior of clicking the label:

<label :for="val" @click.prevent="uncheck( val )">{{ val }}</label>

Here's a working Codepen.