Checkbox

checkbox.svelte

Checkboxes allow users to select one or more options from a set.

Demo

Usage
001 <script lang="ts">
002 import Checkbox from '$lib/components/checkbox.svelte';
003
004 let agreed = $state(false);
005 let newsletter = $state(true);
006 </script>
007
008 <div class="flex flex-col">
009 <Checkbox bind:checked={agreed}>I agree to the terms and conditions</Checkbox>
010 <Checkbox bind:checked={newsletter}>Subscribe to newsletter</Checkbox>
011 <Checkbox checked={true} disabled>This option is locked (disabled)</Checkbox>
012 <Checkbox>Unchecked by default</Checkbox>
013 </div>
014
checkbox.svelte
001 <script lang="ts" module>
002 import type { HTMLInputAttributes } from 'svelte/elements';
003
004 export type CheckboxProps = Omit<HTMLInputAttributes, 'type'> & {
005 ref?: HTMLInputElement | null;
006 checked?: boolean;
007 };
008 </script>
009
010 <script lang="ts">
011 let {
012 class: className = '',
013 ref = $bindable(null),
014 checked = $bindable(false),
015 name,
016 disabled = false,
017 children,
018 ...restProps
019 }: CheckboxProps = $props();
020
021 let focused = $state(false);
022
023 const fallbackId = `checkbox-${Math.random().toString(36).substring(2, 11)}`;
024 const checkboxId = $derived(restProps.id ?? fallbackId);
025
026 const handleKeyDown = (event: KeyboardEvent) => {
027 if (event.key === 'Enter' || event.key === ' ') {
028 event.preventDefault();
029 if (!disabled) {
030 checked = !checked;
031 }
032 }
033 };
034 </script>
035
036 <div class="checkbox-wrapper {className}" class:checked class:focused class:disabled>
037 <div class="checkbox-control">
038 <input
039 bind:this={ref}
040 type="checkbox"
041 id={checkboxId}
042 {name}
043 bind:checked
044 {disabled}
045 onfocus={() => (focused = true)}
046 onblur={() => (focused = false)}
047 onkeydown={handleKeyDown}
048 class="checkbox-input"
049 {...restProps}
050 />
051 <label class="checkbox-figure" for={checkboxId}>
052 {checked ? '╳' : '\u00A0'}
053 </label>
054 </div>
055 {#if children}
056 <label class="checkbox-label" for={checkboxId}>
057 {@render children()}
058 </label>
059 {/if}
060 </div>
061
062 <style lang="postcss">
063 .checkbox-wrapper {
064 display: flex;
065 align-items: flex-start;
066 cursor: pointer;
067
068 &.disabled {
069 opacity: 0.5;
070 cursor: not-allowed;
071 }
072 }
073
074 .checkbox-control {
075 position: relative;
076 flex-shrink: 0;
077 display: inline-block;
078 }
079
080 .checkbox-input {
081 position: absolute;
082 opacity: 0;
083 width: 1px;
084 height: 1px;
085 margin: 0;
086 padding: 0;
087 }
088
089 .checkbox-figure {
090 display: inline-block;
091 padding: 0 1ch;
092 cursor: pointer;
093 color: var(--text-primary);
094 background: var(--border-muted);
095 user-select: none;
096 }
097
098 .checkbox-wrapper.focused .checkbox-figure {
099 background: var(--focus-ring);
100 color: var(--surface-base);
101 }
102
103 .checkbox-wrapper.disabled .checkbox-figure {
104 cursor: not-allowed;
105 }
106
107 .checkbox-label {
108 flex: 1;
109 cursor: pointer;
110 user-select: none;
111 }
112
113 .checkbox-wrapper.disabled .checkbox-label {
114 cursor: not-allowed;
115 }
116 </style>
117