Edit in JSFiddle

Vue.component('form-input', {
  props: {
    label: {type: String, required: true},
    id: {type: String, required: true},
    name: {type: String, required: true},
    type: {type: String, default: 'text'},
    placeholder: {type: String, default: null},
    value: {type: String, default: null},
    max: {type: String|Number, default: null},
    min: {type: String|Number, default: null},
    step: {type: String|Number, default: null},
    validationErrMsg: {type: String, default: 'バリデーションエラー'},
    validationExcludePattern: {type: String, default: null},
    validationTemplate: {type: String, default: null},
  },
  data() {
    return {
      vModelValue: null,
      isValidationErr: false,
      localValidationExcludePattern: null,
      localValidationErrMsg: null,
    };
  },
  created() {
    switch (this.validationTemplate) {
      case 'mail':
        this.localValidationExcludePattern = /[^\w"!#$%&'*+\-/=?^`{|}~. ()<>[\]:;@,]/g;
        this.localValidationErrMsg = `半角英数字と次の記号のみが入力できます。"!#$%&'*+-/=?^\`{|}~. ()<>[]:;@,`;
        break;
      case 'tel':
        this.localValidationExcludePattern = /[^\-\d]/g;
        this.localValidationErrMsg = `半角数字と-のみが入力できます。`;
        break;
      default:
        this.localValidationExcludePattern = this.validationExcludePattern;
        this.localValidationErrMsg = this.validationErrMsg;
    }
  },
  methods: {
    /**
     * バリデーションチェック。エラーが起きるとエラーメッセージを出して自動訂正
     */
    validation() {
      if (this.localValidationExcludePattern == null || this.vModelValue == null) {
        return;
      }
      if (this.vModelValue.length > this.vModelValue.replace(this.localValidationExcludePattern, '').length) {
        this.vModelValue = this.vModelValue.replace(this.localValidationExcludePattern, '');
        this.isValidationErr = true;
      } else {
        this.isValidationErr = false;
      }
    },
  },
  template: '#form-input-template',
});


const vm = new Vue({
  el: '#app',
});
body {
  background: #20262E;
  padding: 20px;
  font-family: Helvetica;
}

#app {
  background: #fff;
  border-radius: 4px;
  padding: 20px;
  transition: all 0.2s;
}

.input-color {
  height: 2.5em;
}
<div id="app">
  <form-input
    label="ユーザ名"
    id="edit_username"
    name="username"
    placeholder="例:浜松 太郎"></form-input>
  <form-input
    label="メールアドレス"
    id="edit_mail"
    name="mail"
    type="mail"
    validation-template="mail"></form-input>
  <form-input
    label="番号"
    id="edit_number"
    name="number"
    type="number"
    value="0.53"
    max="1"
    min="0"
    step="0.01"></form-input>
  <form-input
    label="色"
    id="edit_color"
    name="color"
    type="color"
    value="#F000FF"
    ></form-input>
</div>

<template id="form-input-template">
  <div class="row">
    <div class="col-md-12">
      <div class="form-group row">
        <label
          :for="id"
          class="col-md-3 col-form-label text-md-right">
          {{ label }}
        </label>
        <div class="col-md-9">
          <input
            :id="id"
            :class="type === 'color' ? 'input-color' : null"
            :name="name"
            :type="type"
            :placeholder="placeholder"
            :value="value"
            :max="max"
            :min="min"
            :step="step"
            v-model="vModelValue"
            class="form-control"
            @input="validation()">
          <div
            v-show="isValidationErr"
            class="text-black-50">{{ localValidationErrMsg }}</div>
        </div>
      </div>
    </div>
  </div>
</template>