JSFiddle - React, Tailwind, and code Playground

HTML

<script src="//cdnjs.cloudflare.com/ajax/libs/vue/2.6.10/vue.min.js"></script>

<div id="app">
  <h2>Todo:</h2>
  <ol class="tasks">
    <task-element
      v-for="(n, i) in todos"
      :key="n.id"
      :task="n"
      @update="Object.assign(n, $event)"
      @delete="todos.splice(i, 1)"
    ></task-element>
  </ol>
  <h3>Add new task:</h3>
  <input placeholder="New Todo" v-model="newTodo" @keypress.enter="addTodo">
  <button type="button" @click="addTodo">Add task</button>
</div>

CSS

body {
  background: #20262E;
  padding: 20px;
  font-family: monospace;
}

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

li {
  margin: 8px 0;
  width: 250px;
  border-bottom: 1px solid #ccc;
  margin-bottom: 20px;
}

label.islong span::before {
  content: "⌛";
}

div.loaded ol.tasks li span {
  opacity: 1;
}

h2 {
  font-weight: bold;
  margin-bottom: 15px;
}

.created {
  color: #999;
}

JavaScript

/*
const callbacks = new Set;
const intervalId = setInterval(() => {
  const now = Date.now();
  callbacks.forEach(n => n(now));
}, 1000);
*/
const timer = {
  timeoutId: null,
  timeoutCallback() {
    const now = Date.now();
    timer.callbacks.forEach(n => n(now));
    timer.timeoutId = setTimeout(timer.timeoutCallback, 1000);
  },
  callbacks: new Set,
  add(callback) {
    this.callbacks.add(callback);
    if (!this.timeoutId) {
      this.timeoutCallback();
    }
  },
  delete(callback) {
    this.callbacks.delete(callback);
    if (!this.callbacks.size) {
      clearTimeout(this.timeoutId);
      this.timeoutId = null;
    }
  },
};

Vue.component('task-element', {
  props: [ 'task' ],
  data: () => ({
    created: null,
  }),
  template : `
<li>
  <label :class="{ islong: task.text.length >= 15 }">
    <input type="checkbox" v-model="done">
    <span>{{ task.text }}</span>
  </label>
  <button v-if="task.done" @click="$emit('delete')">delete</button>
  <div v-if="created !== null" class="created">
    created {{ created }} seconds ago
  </div>
</li>`,
  computed: {
    done: {
      get() {
        return this.task.done;
      },
      set(done) {
        this.$emit('update', { done });
      },
    },
  },
  created() {
    const { createdAt } = this.task;
    if (createdAt) {
      const updateCreated = now => this.created = (now - createdAt) / 1e3 | 0;
      /*callbacks*/timer.add(updateCreated);
      this.$on('hook:beforeDestroy', () => /*callbacks*/timer.delete(updateCreated));
    }
  },
});

new Vue({
  el: '#app',
  data: () => ({
    newTodo: '',
    todos: [
      { id: 1, text: 'Task 1', done: false },
      { id: 2, text: 'Task 2', done: false, createdAt: Date.now() - 15000 },
      { id: 3, text: 'Task 3', done: true },
    ],
  }),
  methods: {
    addTodo() {
      const text = this.newTodo;
      if (text.length) {
        this.todos.push({
          id: -~Math.max(0, ...this.todos.map(n => n.id)),
          text,
          done: false,
 ...