JSFiddle - React, Tailwind, and code Playground

HTML

<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.0.0-beta.4/vue.min.js"></script>
<div id="app">
  
  <h4>{{ search }}</h4>
  
  <textfield v-model="search" placeholder="Search"></textfield>
  <textfield v-model="search" placeholder="Search" lazy></textfield>
  
</div>

CSS

body {
  padding: 15px;
}

.textfield {
  border-bottom: 1px solid #c1c3d0;
  margin-bottom: 15px;
}

.textfield hr {
  position: relative;
  top: 1px;
  margin: 0;
  border: 1px solid #17e5ff;
  transition: transform .3s cubic-bezier(0.23, 1, 0.32, 1);
  transform: scaleX(0);
}

.textfield.is-floating hr {
  transform: scaleX(1);
}

.textfield-control {
  width: 100%;
  padding: 15px;
  border: 0;
  font-size: 18px;
  outline: none;
}

JavaScript

// npm i debounce

function debounce(func, wait, immediate){
  var timeout, args, context, timestamp, result;
  if (null == wait) wait = 100;

  function later() {
    var last = Date.now() - timestamp;

    if (last < wait && last > 0) {
      timeout = setTimeout(later, wait - last);
    } else {
      timeout = null;
      if (!immediate) {
        result = func.apply(context, args);
        if (!timeout) context = args = null;
      }
    }
  };

  var debounced = function(){
    context = this;
    args = arguments;
    timestamp = Date.now();
    var callNow = immediate && !timeout;
    if (!timeout) timeout = setTimeout(later, wait);
    if (callNow) {
      result = func.apply(context, args);
      context = args = null;
    }

    return result;
  };

  debounced.clear = function() {
    if (timeout) {
      clearTimeout(timeout);
      timeout = null;
    }
  };

  return debounced;
}

Vue.component('textfield', {
  props: {
    value: String,
    placeholder: String,
    inputClass: String,
    debounce: {
      type: [String, Number],
      default: 0,
    },
    lazy: {
      type: Boolean,
      default: false,
    },
    textarea: {
      type: Boolean,
      default: false,
    },
  },
  computed: {
    isDirty() {
      return this.value !== '';
    },
  },
  data() {
    return {
      isFocused: false,
    };
  },
  created() {
    const debounceTime = parseInt(this.debounce, 10) || 0;

    if (debounceTime > 0) {
      this.debouncedEmit = debounce(this.debouncedEmit, debounceTime);
    }
  },
  methods: {
    setFocused(e) {
      this.isFocused = e.type === 'focus';
    },
    onInput(e) {
      const value = e.target.value;

      if (value) {
        this.debouncedEmit(value);
      } else {
        this.$emit('input', value);
      }
    },
    debouncedEmit(value) {
      this.$emit('input', value);
    },
  },
  render(createElement) {
    const inputEvent = this.lazy === true ? 'change' : 'input';
    const isTextarea = this.textarea...