Remaining character count in Vue.js

Often you'll want to limit the number of characters a user can put into an input field such as a textarea. When you do this it's a good idea to show the number of characters the user has left available in real time to avoid frustration. This is a common practice and something that's really easy to achieve in Vue.js.

Skip to the demo →

Vue component scaffolding

Let's start by scoping out a simple vue component. All that's happening here is we're rendering a HTML textarea where the content is bound to the text data property and where the id, name and label are populated by props. There's a placeholder for the characters remaining message underneath too. We also need a data property to set the max number of characters a user can enter.

<!-- characters-remaining.vue -->
<template>
  <div>
    <label :for="id">Comment</label>
    <textarea v-model="text" :id="id" :name="id"></textarea>
    <p>You have xxx characters remaining.</p>
  </div>
</template>

<script>
  module.exports = {
    props: {
      id: {
        type: String,
        required: true
      },
      label: {
        type: String,
        required: true
      }
    },
    data: function() {
      return {
        text: '',
        maxCharacters: 100,
      }
    },
  }
</script>

<!-- Displaying our component -->
<characters-remaining id="comment" label="Comment"></characters-remaining>

Dynamically showing the number of characters remaining

We can work out the number of characters remaining by subtracting the length of the text property (which is bound to the textarea) from the max characters.

This is a good use case for a computed property.

<!-- characters-remaining.vue -->
<template>
  <div>
    <label :for="id">Comment</label>
    <textarea v-model="text" :id="id" :name="id"></textarea>
    <p>You have {{charactersRemaining}} characters remaining.</p>
  </div>
</template>

<script>
  module.exports = {
    props: {
      id: {
        type: String,
        required: true
      },
      label: {
        type: String,
        required: true
      }
    },
    data: function() {
      return {
        text: '',
        maxCharacters: 100,
      }
    },
    computed: {
      charactersRemaining: function () {
        return this.maxCharacters - this.text.length;
      }
    }
  }
</script>

<!-- Displaying our component -->
<characters-remaining id="comment" label="Comment"></characters-remaining>

Notice the computed property for charactersRemaining which returns the result of the calculation. The property gets reevaluated every time either the max character count or, more likely, the text in the textarea changes. We can display the result by referencing it like a normal data property in the template.

Telling the user when they've gone over

We now have a working countdown of the number of characters remaining as the user types, but what happens when the user goes over? We see a minus number. This is a bit rubbish, so let's add another message which tells them when they've gone over, and by how much.

<!-- characters-remaining.vue -->
<template>
  <div>
    <label :for="id">Comment</label>
    <textarea v-model="text" :id="id" :name="id"></textarea>
    <p v-if="! isOver()">You have {{charactersRemaining}} characters remaining.</p>
    <p v-else class="over">You are {{ charactersOver }} characters over the limit.</p>
  </div>
</template>

<script>
  module.exports = {
    props: {
      id: {
        type: String,
        required: true
      },
      label: {
        type: String,
        required: true
      }
    },
    data: function() {
      return {
        text: '',
        maxCharacters: 100,
      }
    },
    computed: {
      charactersRemaining: function () {
        return this.maxCharacters - this.text.length;
      },
      charactersOver: function () {
        return this.isOver() ? this.text.length - this.maxCharacters : 0;
      }
    },
    methods: {
      isOver: function () {
       return this.charactersRemaining < 0; 
      }
    }
  }
</script>

<!-- Displaying our component -->
<characters-remaining id="comment" label="Comment"></characters-remaining>

Here we've added a new computed property which calculates how many characters over the limit the text is. There's also a isOver method which returns a boolean depending on if the user has gone over or not. This is used to toggle the display of the two messages.

Make it configurable

We won't always want to have the same limit for all of our textareas, and we want our component to be reusable, so let's make it configurable.

<!-- characters-remaining.vue -->
<template>
  <div>
    <label :for="id">Comment</label>
    <textarea v-model="text" :id="id" :name="id"></textarea>
    <p v-if="! isOver()">You have {{charactersRemaining}} characters remaining.</p>
    <p v-else class="over">You are {{ charactersOver }} characters over the limit.</p>
  </div>
</template>

<script>
  module.exports = {
    props: {
      id: {
        type: String,
        required: true
      },
      label: {
        type: String,
        required: true
      },
      limit: {
        type: Number,
        required: true
      },
    },
    data: function() {
      return {
        text: '',
        maxCharacters: 100,
      }
    },
    computed: {
      charactersRemaining: function () {
        return this.maxCharacters - this.text.length;
      },
      charactersOver: function () {
        return this.isOver() ? this.text.length - this.maxCharacters : 0;
      }
    },
    methods: {
      isOver: function () {
       return this.charactersRemaining < 0; 
      }
    },
    mounted: function () {
      this.maxCharacters = this.limit;
    }
  }
</script>

<!-- Displaying our component -->
<characters-remaining id="comment" label="Comment" :limit="150"></characters-remaining>

We've added a new prop for limit and a hook for when the component is mounted. We need to set the maxCharacter data property to the value of the limit prop when the component is initialised. The mounted hook is fired when the component is inserted into the DOM, which is just what we need. It's worth becoming familiar with the Vue instance lifecycle.

Demo time

Well that's it. There's a whole bunch of stuff we could add to make this even better, but that's our basic character counter.

Check out the demo →