Custom checkbox design in Vue

Share on Facebook
Share on Twitter
Browsers' default checkbox inputs are ok for basic prototyping. However, sooner rather than later developers aim at redesigning the initial appearance to achieve
  • unique design,
  • unified appearance across browsers,
  • additional visual effects.
Share on Facebook
Share on Twitter

There is a tutorial on w3.schools and several other sites how to achieve this through pure css. It is a fine approach by any means. There's a catch though - it's just outdated. Nowadays, in the age of amazing javascript frameworks and new powerful css features, we can achieve this considerably easier.

In this short post, I'll focus on Vue. However, the idea is easily reproducible in React, Angular and other frameworks.

Checkbox in Vue

It makes a lot of sense to turn this concept into a pure component, meaning it will have no state (data) on it's own. It's generally a good practice to components that are to be reused across the app on multiple occasions as concise and independent as possible. This will ensure that you just need to import the component, pass it the required props and it will keep the appearance unified everywhere.

Let's begin by creating a short single file component, let's call it Checkbox.vue for instance.

1 Custom and reactive

Props

It's going to accept two props.


props: {
  /**
    * Unique checkbox identifier
  */
  id: {
    type: String,
    default: '',
  },
  /**
    * Checked/Unchecked. Accessible through v-model
    */
  value: {
    type: Boolean,
    default: false,
  },
}

        

id should be unique across the app (or at least at the component level) and will be used for label tag, which is associated with the checkbox input. value is the checked/unchecked state. Note that it is named value, because this will allow to use it with v-model in the parent component. Checkout the vue documentation to read more about that.

Methods

We only need one method to make this work.


methods: {
  onInput(event: any) {
    /**
    * v-model implementation
    * @type {boolean}
    */
      this.$emit('input', event.target.checked);
  },
}

        

Anytime the checkbox is clicked, it will emit the current state as input event. This is the name required for the custom v-model implementation.

Template

Let's move to the template part.


<template>
    <div class="checkbox">
      <div
        class="checkbox-custom-input-wrapper"
        :class="{'checkbox-custom-input-wrapper--checked': value}"
      >
        <input
          :id="id"
          class="checkbox-hidden-input"
          type="checkbox"
          :checked="value"
          @input="onInput"
        >
        <span
          v-if="value"
          class="checkbox-custom-input"
          :class="{'checkbox-custom-input--checked': value}"
        >
          ✓ <!-- tickmark -->
        </span>
      </div>
    <label
      class="label"
      :for="id"
    >
      <slot />
    </label>
  </div>
</template>

        

Hopefully the class names are self-explanatory for the structure. Note that the the appearance is dynamically changed based on the value prop.

CSS

Below is the implementation in pure css for easier understanding. However, I'd strongly suggest that you use SASS, LESS or better still css modules.


// Use flex to align content of the component
.checkbox {
  display: flex;
  whitespace: no-wrap;
}

.checkbox-custom-input-wrapper {
  position: relative;
  border: 2px solid blue;
  height: 16px;
  width: 16px;
}

.checkbox-custom-input-wrapper--checked {
  border-color: purple;
}

// Modify the color and background as you wish
.checkbox-custom-input {
  width: 100%;
  height: 100%;
  display: flex;
  align-items: center;
  justify-content: center;

  background-color: blue;
  color: white;
  font-weight: 400;
}

// Also modify the checked state
.checkbox-custom-input--checked {
  background-color: purple;
}

// Hide default checkbox, but preserve it
// This way it is still clickable and covers the same area as the custom checkbox
.checkbox-hidden-input {
  position: absolute;
  opacity: 0;
  cursor: pointer;
  width: 100%;
  height: 100%;
  margin: 0;
}

// To not overlap the checkbox, shift label to the right
.label {
  cursor: inherit;
  margin-left: 8px;
}

        

Usage

And that's it. You can now import it and use it anywhere you like.


<checkbox v-model="checked" id="unique">
     Label
</checkbox>

        

It maintains the unified appearance across the application and browsers alike. Cool eh? You can preview the implementation in the codepen below. Happy coding!

< back