Skip to content

Instantly share code, notes, and snippets.

@Jonarod
Created November 23, 2019 18:20
Show Gist options
  • Star 57 You must be signed in to star a gist
  • Fork 11 You must be signed in to fork a gist
  • Save Jonarod/7ff2fe4f81aae39e431aa7a08ce815bc to your computer and use it in GitHub Desktop.
Save Jonarod/7ff2fe4f81aae39e431aa7a08ce815bc to your computer and use it in GitHub Desktop.
Simple custom CheckBox component for Vue.js, compatible with v-model.
/**
* @usage:
*
* <CheckBox label="Foo" value="foo" v-model="MySelectedValues" />
* <CheckBox label="Bar" value="bar" v-model="MySelectedValues" />
* <CheckBox label="Baz" value="baz" v-model="MySelectedValues" />
*
* data(){
* return {
* MySelectedValues: [],
* }
* }
*/
<template>
<label class="wrapper flex items-center">
{{label}}
<input class="checkbox" type="checkbox" :checked="isChecked" :value="value" @change="updateInput"/>
<span class="checkmark"></span>
</label>
</template>
<script>
export default {
model: {
prop: 'modelValue',
event: 'change'
},
props: {
"value": { type: String },
"modelValue": { default: "" },
"label": { type: String, required: true},
"trueValue": { default: true },
"falseValue": { default: false }
},
computed: {
isChecked() {
if (this.modelValue instanceof Array) {
return this.modelValue.includes(this.value)
}
// Note that `true-value` and `false-value` are camelCase in the JS
return this.modelValue === this.trueValue
}
},
methods: {
updateInput(event) {
let isChecked = event.target.checked
if (this.modelValue instanceof Array) {
let newValue = [...this.modelValue]
if (isChecked) {
newValue.push(this.value)
} else {
newValue.splice(newValue.indexOf(this.value), 1)
}
this.$emit('change', newValue)
} else {
this.$emit('change', isChecked ? this.trueValue : this.falseValue)
}
}
}
}
</script>
<style lang="postcss" scoped>
/* Customize the label (the wrapper) */
.wrapper {
display: block;
position: relative;
padding-left: 35px;
margin-bottom: 6px;
cursor: pointer;
font-size: 22px;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
font-size: 16px;
}
/* Hide the browser's default checkbox */
.wrapper input {
position: absolute;
opacity: 0;
cursor: pointer;
height: 0;
width: 0;
}
/* Create a custom checkbox */
.checkmark {
position: absolute;
top: 0;
left: 0;
height: 21px;
width: 21px;
border-radius: 2px;
background-color: #eee;
border: 1px solid #ccc;
}
/* On mouse-over, add a grey background color */
.wrapper:hover input ~ .checkmark {
background-color: #ccc;
}
/* When the checkbox is checked, add a blue background */
.wrapper input:checked ~ .checkmark {
background-color: #1CD4A7;
}
/* Create the checkmark/indicator (hidden when not checked) */
.checkmark:after {
content: "";
position: absolute;
display: none;
}
/* Show the checkmark when checked */
.wrapper input:checked ~ .checkmark:after {
display: block;
}
/* Style the checkmark/indicator */
.wrapper .checkmark:after {
left: 7px;
top: 0px;
width: 7px;
height: 15px;
border: solid white;
border-width: 0 3px 3px 0;
-webkit-transform: rotate(45deg);
-ms-transform: rotate(45deg);
transform: rotate(45deg);
}
</style>
@abdurasul18
Copy link

thank you bro ! I am looking for this :)

@gianpesto
Copy link

thank you for this beautiful example, there's a problem tho:
if you are having a huge ammount of custom checkboxes that all rely on the same v-model, each change would trigger a rerender in all checkboxes.

i ran across this performance-bottleneck on having 200+ checkboxes.

any thoughts or solutions to this are welcome ;)

@HerrBertling
Copy link

@gianpesto I'd rethink the UI you are building if it requires 200+ checkboxes on the same view 😄 🙈

@sstottelaar
Copy link

Lifesaver, thanks for sharing!

@nevadskiy
Copy link

nevadskiy commented Apr 7, 2021

@micaz
Copy link

micaz commented Apr 20, 2021

Thank for Sharing!!

@gianpesto
Copy link

@gianpesto
It should be more performant.
https://jsfiddle.net/ariel0196/euzn79gc/3/

Hej! Nice simpe idea! ilikeilikeilike! 🥰

@pchmn
Copy link

pchmn commented Jun 27, 2021

@mioe
Copy link

mioe commented Nov 22, 2021

@FarshadGhanbari
Copy link

Thank for Sharing ♥

@JacobRex
Copy link

JacobRex commented Feb 2, 2022

Thanks for sharing!

@plweil
Copy link

plweil commented Mar 22, 2022

@gianpesto @nevadskiy Thank you both! I was having a tough time wrapping my head around this. Works with Pinia, too!

@juanaguillon
Copy link

@dzpower
Copy link

dzpower commented May 19, 2022

+tnx

@AlejandroMut
Copy link

A vue 3 example based on @nevadskiy answer: https://codesandbox.io/s/interesting-paper-yeydb?file=/src/components/Checkbox.vue

@juanaguillon great solution! However, I'm getting the following warning from typescript on the v-model attribute:

Type 'string | number | boolean | (string | number)[] | undefined' is not assignable to type 'any[] | Set<any> | Booleanish | undefined'.
Type 'string' is not assignable to type 'any[] | Set<any> | Booleanish | undefined'.

Any ideas?

@flora-le
Copy link

flora-le commented Feb 9, 2023

Thanks for sharing this. It's working but since I'm using Vue 3 with Composition API, we need to use the event "update:modelValue" when using emit(), otherwise the array (if it's checkboxes group) won't work.
The event change doesn't work on its own, but if I remove it and only keeps "update:modelValue", it's fine!

@kebalicious
Copy link

Thanks for sharing this. It's working but since I'm using Vue 3 with Composition API, we need to use the event "update:modelValue" when using emit(), otherwise the array (if it's checkboxes group) won't work. The event change doesn't work on its own, but if I remove it and only keeps "update:modelValue", it's fine!

Can you share the working one? Thank you in advance! :)

@flora-le
Copy link

flora-le commented Feb 13, 2023

Thanks for sharing this. It's working but since I'm using Vue 3 with Composition API, we need to use the event "update:modelValue" when using emit(), otherwise the array (if it's checkboxes group) won't work. The event change doesn't work on its own, but if I remove it and only keeps "update:modelValue", it's fine!

Can you share the working one? Thank you in advance! :)

Replied with the wrong account lol (work account) so here the same answer you probably received by mail but with my personal account

Template

<template>
    <label :class="['checkbox']" :for="id">
        <input type="checkbox" :id="id" tabindex="0" role="checkbox" @change="updateInput" :value="value" :checked="isChecked" @on-change="emit('onChange')">
        {{label}}
    </label>
</template>

script setup

import { defineProps, defineEmits, computed } from 'vue'

const props = defineProps({
    id: {
        type: String,
        default: ""
    },
    label: {
        type: String,
        default: "",
        required: true
    },
    value: {
        type: [String, Object],
        default: "",
    },
    checked: {
        type: [Boolean, Array],
        default: false,
    },
    disabled: {
        type: Boolean,
        default: false,
    },
    modelValue: { default: "" },
    trueValue: { default: true },
    falseValue: { default: false }
})

const emit = defineEmits(['onChange', 'update:modelValue']);

const isChecked = computed(() => {
    if (props.modelValue instanceof Array) {
        // console.log('props.modelValue', props.modelValue)
        return props.modelValue.includes(props.value)
    }
    // Note that `true-value` and `false-value` are camelCase in the JS
    return props.modelValue === props.trueValue
})

const updateInput = (e) => {
    // emit("onChange", e.target.value);
    // emit('update:modelValue', props.modelValue)
    let isChecked = event.target.checked
    console.log('isChecked', isChecked, props.modelValue instanceof Array)
    if (props.modelValue instanceof Array) {
        let newValue = [...props.modelValue]
        // console.log('newValue', newValue)
        if (isChecked) {
            newValue.push(props.value)

            // console.log('checked!', newValue)
        } else {
            newValue.splice(newValue.indexOf(props.value), 1)
        }
        // emit('change', newValue)
        emit('update:modelValue', newValue)
    } else {
        // emit('change', isChecked ? props.trueValue : props.falseValue)
        emit('update:modelValue', isChecked ? props.trueValue : props.falseValue)
    }

};

Usage
<mycheckbox :label="name" :id="id" :value="myValue" v-model="arrayOrObjectOrStringWhatever" @on-change="doSth"></mycheckbox>

@plweil
Copy link

plweil commented Feb 13, 2023

Why put aria-checked on the label element here? First, it's not supported on label; second, it's useless on <input type="checkbox">. The native checked attribute takes precedence.

 <label :class="['checkbox']" :for="id" :aria-labelledby="`label-${props.id}`" :aria-checked="props.checked">

@flora-le
Copy link

Why put aria-checked on the label element here? First, it's not supported on label; second, it's useless on <input type="checkbox">. The native checked attribute takes precedence.

 <label :class="['checkbox']" :for="id" :aria-labelledby="`label-${props.id}`" :aria-checked="props.checked">

Oh thanks for noticing it. I think it was "garbage" of another tutorial I tried to follow and I mixed it with the content here. Imma remove it thanks

@kebalicious
Copy link

Thanks for sharing @flora-le :)

@flora-le
Copy link

You're welcome @miqbalhakim ! Nice day

@plweil
Copy link

plweil commented Feb 13, 2023

Thanks @flora-le. I was going to add that the same goes for putting aria-labelledby inside a label element, but it looks as though that's been removed too.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment