<CheckboxRoot>
<CheckboxLabel>Accept terms and conditions</CheckboxLabel>
<CheckboxControl />
</CheckboxRoot>
npx @left4code/cosmic-ui-cli@latest add checkbox
This writes the component and everything it imports, then installs the packages it needs. Run npx @left4code/cosmic-ui-cli@latest initfirst if you haven't already (it detects Vue automatically).
Install the following dependencies:
This component is built directly on the Zag.js state machine (not a wrapper). No Portal or presence composable needed, the check mark just toggles opacity, no exit animation. The hidden native input (needed for keyboard, focus and form submission) renders automatically insideCheckboxRoot. Vue SFCs can't export multiple components from one file, so Checkbox is a folder instead of a single file — copy all of it into your project.
import type { InjectionKey, ComputedRef } from "vue";
import type * as checkbox from "@zag-js/checkbox";
type CheckboxApi = ComputedRef<ReturnType<typeof checkbox.connect>>;
const CheckboxApiKey: InjectionKey<CheckboxApi> = Symbol("checkbox-api");
export { CheckboxApiKey };
export type { CheckboxApi };
<script setup lang="ts">
import { computed, provide, useAttrs, useId } from "vue";
import { useMachine, normalizeProps } from "@zag-js/vue";
import * as checkbox from "@zag-js/checkbox";
import { twMerge } from "tailwind-merge";
import { CheckboxApiKey } from "./context";
defineOptions({ inheritAttrs: false });
const rawAttrs = useAttrs() as Partial<checkbox.Props> & { class?: string };
const id = useId();
const machineProps = computed(() => {
const { class: _class, ...rest } = rawAttrs;
return { id, ...rest };
});
const service = useMachine(checkbox.machine, machineProps);
const api = computed(() => checkbox.connect(service, normalizeProps));
provide(CheckboxApiKey, api);
</script>
<template>
<label
v-bind="api.getRootProps()"
:class="twMerge(['flex gap-3.5 items-center cursor-pointer', rawAttrs.class])"
>
<slot />
<input v-bind="api.getHiddenInputProps()" />
</label>
</template>
<script setup lang="ts">
import { inject } from "vue";
import { twMerge } from "tailwind-merge";
import { CheckboxApiKey } from "./context";
const { class: className } = defineProps<{ class?: string }>();
const injectedApi = inject(CheckboxApiKey);
if (!injectedApi) throw new Error("Checkbox parts must be used within <CheckboxRoot>");
const api = injectedApi;
</script>
<template>
<span v-bind="api.getLabelProps()" :class="twMerge(['order-2', className])">
<slot />
</span>
</template>
<script setup lang="ts">
import { inject } from "vue";
import { twMerge } from "tailwind-merge";
import Frame, { parsePaths } from "@/components/ui/frame.vue";
import { Check } from "@lucide/vue";
import { CheckboxApiKey } from "./context";
const { class: className } = defineProps<{ class?: string }>();
const injectedApi = inject(CheckboxApiKey);
if (!injectedApi) throw new Error("Checkbox parts must be used within <CheckboxRoot>");
const api = injectedApi;
const framePaths = parsePaths(
'[{"show":true,"style":{"strokeWidth":"1","stroke":"var(--color-frame-1-stroke)","fill":"var(--color-frame-1-fill)"},"path":[["M","50% - 28.125%","0"],["L","50% + 28.125%","0"],["L","100% + 0","50% - 28.125%"],["L","100% + 0","50% + 28.125%"],["L","50% + 28.125%","100% - 0"],["L","50% - 28.125%","100% + 0"],["L","0","50% + 28.125%"],["L","0","50% - 28.125%"],["L","50% - 28.125%","0"]]}]'
);
</script>
<template>
<div
v-bind="api.getControlProps()"
:class="
twMerge([
'group relative size-5 flex items-center justify-center data-[state=checked]:drop-shadow-[0_0px_20px_var(--color-primary)]',
'[--color-frame-1-stroke:var(--color-primary)]/80',
'[--color-frame-1-fill:var(--color-primary)]/10',
className,
])
"
>
<Frame :paths="framePaths" />
<Check
class="group-data-[state=checked]:opacity-100 opacity-0 size-6 -mt-1 -mr-2 stroke-(--color-primary)/80 drop-shadow-[0_0px_2px_var(--color-primary)] transition-all duration-100"
/>
</div>
</template>
export { default as CheckboxRoot } from "./CheckboxRoot.vue";
export { default as CheckboxLabel } from "./CheckboxLabel.vue";
export { default as CheckboxControl } from "./CheckboxControl.vue";
Update the import paths to match your project setup.
import { CheckboxRoot, CheckboxLabel, CheckboxControl } from "@/components/ui/checkbox";
<CheckboxRoot>
<CheckboxLabel>Accept terms and conditions</CheckboxLabel>
<CheckboxControl />
</CheckboxRoot>