Appearance
@vibe-labs/design-vue-buttons
Vue 3 button components for the Vibe Design System. Polymorphic rendering, custom color cascading, and full accessibility.
Installation
ts
import { VibeButton, VibeButtonGroup } from "@vibe-labs/design-vue-buttons";Requires the CSS layer from @vibe-labs/design-components-buttons.
Components
VibeButton
Polymorphic button with variant styling, loading state, and custom color support.
Usage
vue
<!-- Basic variants -->
<VibeButton>Primary</VibeButton>
<VibeButton variant="secondary">Secondary</VibeButton>
<VibeButton variant="ghost">Ghost</VibeButton>
<VibeButton variant="danger">Delete</VibeButton>
<VibeButton variant="link">Learn more</VibeButton>
<!-- Sizes -->
<VibeButton size="sm">Small</VibeButton>
<VibeButton size="md">Medium</VibeButton>
<VibeButton size="lg">Large</VibeButton>
<!-- Shapes -->
<VibeButton shape="circle" icon aria-label="Play">
<PlayIcon />
</VibeButton>
<!-- Loading -->
<VibeButton :loading="isSubmitting">Save</VibeButton>
<!-- Full width -->
<VibeButton full>Continue</VibeButton>
<!-- Icon button (square aspect ratio) -->
<VibeButton icon aria-label="Settings">
<SettingsIcon />
</VibeButton>
<!-- Custom color (cascades through all accent-derived tokens) -->
<VibeButton color="#e91e63">Pink Button</VibeButton>
<!-- Render as a link -->
<VibeButton as="a" href="/dashboard">Go to Dashboard</VibeButton>
<!-- Render as a router-link -->
<VibeButton :as="RouterLink" :to="{ name: 'settings' }">Settings</VibeButton>
<!-- Submit button -->
<VibeButton type="submit">Submit Form</VibeButton>
<!-- Disabled -->
<VibeButton disabled>Can't touch this</VibeButton>Props
| Prop | Type | Default | Description |
|---|---|---|---|
variant | ButtonVariant | "primary" | primary · secondary · ghost · danger · link |
size | ButtonSize | "md" | sm · md · lg |
shape | ButtonShape | "default" | default · circle (full radius + hover/active scale) |
loading | boolean | false | Shows spinner, disables interaction |
full | boolean | false | Full width |
icon | boolean | false | Square aspect ratio for icon-only buttons |
disabled | boolean | false | Disabled state |
color | string | — | Custom accent color (cascades via --color-accent) |
as | string | Component | "button" | Polymorphic element/component |
type | "button" | "submit" | "reset" | "button" | Native button type (only for <button>) |
Events
| Event | Payload | Description |
|---|---|---|
click | MouseEvent | Click handler (prevented when disabled/loading) |
Slots
| Slot | Description |
|---|---|
default | Button content (text, icons, etc.) |
Shape: Circle
Circle buttons are commonly used for play controls, FABs, and tile overlays. The circle shape applies border-radius: var(--radius-full) with a subtle hover scale(1.1) and active scale(1.05). Combine with icon for a perfect circle:
vue
<VibeButton shape="circle" icon variant="primary" aria-label="Play">
<PlayIcon />
</VibeButton>Custom Color Cascade
The color prop sets --color-accent locally on the button element. Since variant tokens like --btn-primary-bg are defined as var(--color-accent) in the component token layer, they automatically resolve to the custom color — no per-variant overrides needed. Hover and active states are derived using color-mix().
Accessibility
- Native
disabledfor<button>elements,aria-disabledfor others aria-busy="true"during loading statetabindex="-1"on disabled non-button elements- Click prevented when disabled or loading
VibeButtonGroup
Groups buttons with shared border radius handling and overlap.
Usage
vue
<VibeButtonGroup label="Actions">
<VibeButton variant="secondary">Left</VibeButton>
<VibeButton variant="secondary">Center</VibeButton>
<VibeButton variant="secondary">Right</VibeButton>
</VibeButtonGroup>
<!-- As toolbar -->
<VibeButtonGroup role="toolbar" label="Text formatting">
<VibeButton icon><BoldIcon /></VibeButton>
<VibeButton icon><ItalicIcon /></VibeButton>
<VibeButton icon><UnderlineIcon /></VibeButton>
</VibeButtonGroup>Props
| Prop | Type | Default | Description |
|---|---|---|---|
role | "group" | "toolbar" | "group" | ARIA role |
label | string | — | Accessible group label |
Dependencies
| Package | Purpose |
|---|---|
@vibe-labs/design-components-buttons | 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 { VibeButton, VibeButtonGroup } from "@vibe-labs/design-vue-buttons";
import "@vibe-labs/design-components-buttons";VibeButton
Variants and states
vue
<script setup lang="ts">
import { VibeButton } from "@vibe-labs/design-vue-buttons";
import { ref } from "vue";
const saving = ref(false);
async function save() {
saving.value = true;
await api.save();
saving.value = false;
}
</script>
<template>
<VibeButton @click="save" :loading="saving">Save</VibeButton>
<VibeButton variant="secondary">Cancel</VibeButton>
<VibeButton variant="ghost">Reset</VibeButton>
<VibeButton variant="danger" @click="deleteItem">Delete</VibeButton>
</template>Polymorphic — link and router-link
vue
<script setup lang="ts">
import { VibeButton } from "@vibe-labs/design-vue-buttons";
import { RouterLink } from "vue-router";
</script>
<template>
<!-- Native anchor -->
<VibeButton as="a" href="https://docs.example.com" variant="link">
Read the docs
</VibeButton>
<!-- Vue Router link -->
<VibeButton :as="RouterLink" :to="{ name: 'settings' }" variant="secondary">
Settings
</VibeButton>
</template>Icon button with circle shape
vue
<script setup lang="ts">
import { VibeButton } from "@vibe-labs/design-vue-buttons";
</script>
<template>
<!-- Square icon button -->
<VibeButton icon variant="ghost" aria-label="Edit">
<EditIcon />
</VibeButton>
<!-- Circular FAB-style -->
<VibeButton shape="circle" icon variant="primary" size="lg" aria-label="Play">
<PlayIcon />
</VibeButton>
</template>VibeButtonGroup
Toolbar
vue
<script setup lang="ts">
import { VibeButton, VibeButtonGroup } from "@vibe-labs/design-vue-buttons";
import { ref } from "vue";
const bold = ref(false);
const italic = ref(false);
</script>
<template>
<VibeButtonGroup role="toolbar" label="Text formatting">
<VibeButton icon :variant="bold ? 'primary' : 'ghost'" @click="bold = !bold" aria-label="Bold">
<BoldIcon />
</VibeButton>
<VibeButton icon :variant="italic ? 'primary' : 'ghost'" @click="italic = !italic" aria-label="Italic">
<ItalicIcon />
</VibeButton>
</VibeButtonGroup>
</template>Common Patterns
Submit form button
vue
<script setup lang="ts">
import { VibeButton } from "@vibe-labs/design-vue-buttons";
defineProps<{ submitting: boolean; valid: boolean }>();
</script>
<template>
<VibeButton type="submit" full :loading="submitting" :disabled="!valid">
Create Account
</VibeButton>
</template>Custom brand color
vue
<template>
<!-- The color cascades to all variant tokens on this button -->
<VibeButton color="#e91e63">Subscribe</VibeButton>
</template>