Appearance
@vibe-labs/design-vue-toggles
Vue 3 toggle switch component for the Vibe Design System.
Installation
ts
import { VibeToggle } from "@vibe-labs/design-vue-toggles";Requires the CSS layer from @vibe-labs/design-components-toggles.
Component
VibeToggle
Toggle switch with label support, native form submission, and proper role="switch" ARIA semantics.
Usage
vue
<!-- Basic -->
<VibeToggle v-model="enabled" label="Enable notifications" />
<!-- Without label -->
<VibeToggle v-model="darkMode" aria-label="Dark mode" />
<!-- Label on the left -->
<VibeToggle v-model="active" label="Active" label-position="left" />
<!-- Sizes -->
<VibeToggle v-model="val" size="sm" label="Small" />
<VibeToggle v-model="val" size="md" label="Medium" />
<VibeToggle v-model="val" size="lg" label="Large" />
<!-- Disabled -->
<VibeToggle v-model="locked" label="Locked setting" disabled />
<!-- In a form (native submission) -->
<form @submit="onSubmit">
<VibeToggle v-model="agreed" name="terms" value="accepted" label="I agree to the terms" required />
</form>
<!-- External label -->
<label id="my-label">WiFi</label>
<VibeToggle v-model="wifi" aria-labelledby="my-label" />
<!-- With description -->
<VibeToggle v-model="sync" label="Auto-sync" aria-describedby="sync-desc" />
<p id="sync-desc">Automatically sync changes every 5 minutes.</p>Props
| Prop | Type | Default | Description |
|---|---|---|---|
modelValue | boolean | false | Checked state (v-model) |
size | ToggleSize | "md" | sm · md · lg |
disabled | boolean | false | Disabled state |
label | string | — | Label text beside the toggle |
labelPosition | "left" | "right" | "right" | Label placement |
name | string | — | Form field name |
value | string | "on" | Submitted value when checked |
required | boolean | false | Required field |
id | string | auto | Input element ID |
ariaLabel | string | — | Accessible label (when no visible label) |
ariaLabelledby | string | — | External label element ID |
ariaDescribedby | string | — | Describing element ID |
Events
| Event | Payload | Description |
|---|---|---|
update:modelValue | boolean | Toggle state changed |
change | boolean | Toggle state changed |
Accessibility
Uses role="switch" with aria-checked — the correct ARIA pattern for toggle switches (not just a styled checkbox). The hidden <input type="checkbox"> provides native form semantics and keyboard activation (Space to toggle).
Dependencies
| Package | Purpose |
|---|---|
@vibe-labs/design-components-toggles | CSS tokens + generated styles |
Build
bash
npm run buildBuilt with Vite + vite-plugin-dts. Outputs ES module with TypeScript declarations.
Usage Guide
Setup
ts
import { VibeToggle } from "@vibe-labs/design-vue-toggles";
import "@vibe-labs/design-components-toggles";VibeToggle — Practical Examples
Notification Preferences Form
vue
<script setup lang="ts">
import { reactive } from "vue";
import { VibeToggle } from "@vibe-labs/design-vue-toggles";
const prefs = reactive({
emailNotifications: true,
pushNotifications: false,
weeklyDigest: true,
marketingEmails: false,
});
async function savePreferences() {
await fetch("/api/preferences", {
method: "PUT",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(prefs),
});
}
</script>
<template>
<form @submit.prevent="savePreferences">
<fieldset>
<legend>Notification Preferences</legend>
<VibeToggle
v-model="prefs.emailNotifications"
label="Email notifications"
size="md"
/>
<VibeToggle
v-model="prefs.pushNotifications"
label="Push notifications"
/>
<VibeToggle
v-model="prefs.weeklyDigest"
label="Weekly digest"
/>
<VibeToggle
v-model="prefs.marketingEmails"
label="Marketing emails"
/>
</fieldset>
<button type="submit">Save</button>
</form>
</template>Dark Mode Toggle
vue
<script setup lang="ts">
import { ref, watch } from "vue";
import { VibeToggle } from "@vibe-labs/design-vue-toggles";
const isDark = ref(document.documentElement.classList.contains("dark"));
watch(isDark, (dark) => {
document.documentElement.classList.toggle("dark", dark);
localStorage.setItem("theme", dark ? "dark" : "light");
});
</script>
<template>
<VibeToggle
v-model="isDark"
aria-label="Dark mode"
size="md"
/>
</template>Toggle with External Description
vue
<script setup lang="ts">
import { ref } from "vue";
import { VibeToggle } from "@vibe-labs/design-vue-toggles";
const autoSync = ref(false);
</script>
<template>
<div class="setting-row">
<div class="setting-row__labels">
<label id="sync-label" class="setting-row__title">Auto-sync</label>
<p id="sync-desc" class="setting-row__description">
Automatically sync changes to the server every 5 minutes.
</p>
</div>
<VibeToggle
v-model="autoSync"
aria-labelledby="sync-label"
aria-describedby="sync-desc"
label-position="left"
/>
</div>
</template>Common Patterns
Disabled When Dependent Setting is Off
vue
<script setup lang="ts">
import { ref } from "vue";
import { VibeToggle } from "@vibe-labs/design-vue-toggles";
const notificationsEnabled = ref(false);
const soundEnabled = ref(false);
</script>
<template>
<div>
<VibeToggle v-model="notificationsEnabled" label="Enable notifications" />
<VibeToggle
v-model="soundEnabled"
label="Notification sounds"
:disabled="!notificationsEnabled"
/>
</div>
</template>Loading State During Async Toggle
vue
<script setup lang="ts">
import { ref } from "vue";
import { VibeToggle } from "@vibe-labs/design-vue-toggles";
const enabled = ref(false);
const saving = ref(false);
async function handleChange(value: boolean) {
saving.value = true;
await fetch("/api/feature-flag", {
method: "PATCH",
body: JSON.stringify({ enabled: value }),
});
enabled.value = value;
saving.value = false;
}
</script>
<template>
<VibeToggle
:model-value="enabled"
label="Feature flag"
:disabled="saving"
@change="handleChange"
/>
</template>Form Submission with Native Checkbox Value
vue
<template>
<form method="POST" action="/api/settings">
<VibeToggle
:model-value="terms"
name="terms"
value="accepted"
label="I agree to the terms of service"
required
@change="(v) => terms = v"
/>
<button type="submit">Continue</button>
</form>
</template>