Edit in JSFiddle

function threeStateByClick(cb) {
    var val = true;
    if (cb.readonly) { // 本来はcb.indeterminate(値が保持されないのでreadonlyを代用)
        cb.readonly = cb.indeterminate = false;
        val = cb.checked = false;
    }
    else if (!cb.checked) {
        cb.readonly = cb.indeterminate = true;
        val = null;
    }
    return val;
}
function threeStateByVal(cb, val) {
    if (val === null) {
        cb.readonly = cb.indeterminate = true;
        cb.checked = false;
    }
    else {
        cb.readonly = cb.indeterminate = false;
        cb.checked = val;
    }
}

Vue.directive('three-state', {
    //twoWay: true,
    bind: function(el, binding, vnode) {
        this.handler = function() {
            var val = threeStateByClick(el);
            Vue.set(vnode.context, binding.expression, val);
        }.bind(this);

        el.addEventListener('click', this.handler);
    },
    unbind: function(el) {
        el.removeEventListener('click', this.handler);
    },
    update: function(el, binding) {
        threeStateByVal(el, binding.value);
    },
});

var vm = new Vue({
    el: "#sample",
    data: {
        cbVal: false,
    },
    computed: {
        cbVal_toString: {
            get: function() {
                return this.cbVal + "";
            },
            set: function(val) {
                this.cbVal = eval(val);
            },
        },
    },
});
<div id="sample">
<label>
    <input type="checkbox" v-three-state="cbVal">
    3-state checkbox
</label>
<br/>
<select size="1" v-model="cbVal_toString">
    <option value="true">true</option>
    <option value="false">false</option>
    <option value="null">null</option>
</select>
</div>
input[type=checkbox]{
    -webkit-transform: scale(2);
    transform: scale(2);
}