Skip to content

@carrot/design-vue-buttons

Vue 3 button components for the Carrot Design System. Polymorphic rendering, custom color cascading, and full accessibility.

Installation

ts
import { CarrotButton, CarrotButtonGroup } from "@carrot/design-vue-buttons";

Requires the CSS layer from @carrot/design-components-buttons.

Components

CarrotButton

Polymorphic button with variant styling, loading state, and custom color support.

Usage

vue
<!-- Basic variants -->
<CarrotButton>Primary</CarrotButton>
<CarrotButton variant="secondary">Secondary</CarrotButton>
<CarrotButton variant="ghost">Ghost</CarrotButton>
<CarrotButton variant="danger">Delete</CarrotButton>
<CarrotButton variant="link">Learn more</CarrotButton>

<!-- Sizes -->
<CarrotButton size="sm">Small</CarrotButton>
<CarrotButton size="md">Medium</CarrotButton>
<CarrotButton size="lg">Large</CarrotButton>

<!-- Shapes -->
<CarrotButton shape="circle" icon aria-label="Play">
  <PlayIcon />
</CarrotButton>

<!-- Loading -->
<CarrotButton :loading="isSubmitting">Save</CarrotButton>

<!-- Full width -->
<CarrotButton full>Continue</CarrotButton>

<!-- Icon button (square aspect ratio) -->
<CarrotButton icon aria-label="Settings">
  <SettingsIcon />
</CarrotButton>

<!-- Custom color (cascades through all accent-derived tokens) -->
<CarrotButton color="#e91e63">Pink Button</CarrotButton>

<!-- Render as a link -->
<CarrotButton as="a" href="/dashboard">Go to Dashboard</CarrotButton>

<!-- Render as a router-link -->
<CarrotButton :as="RouterLink" :to="{ name: 'settings' }">Settings</CarrotButton>

<!-- Submit button -->
<CarrotButton type="submit">Submit Form</CarrotButton>

<!-- Disabled -->
<CarrotButton disabled>Can't touch this</CarrotButton>

Props

PropTypeDefaultDescription
variantButtonVariant"primary"primary · secondary · ghost · danger · link
sizeButtonSize"md"sm · md · lg
shapeButtonShape"default"default · circle (full radius + hover/active scale)
loadingbooleanfalseShows spinner, disables interaction
fullbooleanfalseFull width
iconbooleanfalseSquare aspect ratio for icon-only buttons
disabledbooleanfalseDisabled state
colorstringCustom accent color (cascades via --color-accent)
asstring | Component"button"Polymorphic element/component
type"button" | "submit" | "reset""button"Native button type (only for <button>)

Events

EventPayloadDescription
clickMouseEventClick handler (prevented when disabled/loading)

Slots

SlotDescription
defaultButton 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
<CarrotButton shape="circle" icon variant="primary" aria-label="Play">
  <PlayIcon />
</CarrotButton>

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 disabled for <button> elements, aria-disabled for others
  • aria-busy="true" during loading state
  • tabindex="-1" on disabled non-button elements
  • Click prevented when disabled or loading

CarrotButtonGroup

Groups buttons with shared border radius handling and overlap.

Usage

vue
<CarrotButtonGroup label="Actions">
  <CarrotButton variant="secondary">Left</CarrotButton>
  <CarrotButton variant="secondary">Center</CarrotButton>
  <CarrotButton variant="secondary">Right</CarrotButton>
</CarrotButtonGroup>

<!-- As toolbar -->
<CarrotButtonGroup role="toolbar" label="Text formatting">
  <CarrotButton icon><BoldIcon /></CarrotButton>
  <CarrotButton icon><ItalicIcon /></CarrotButton>
  <CarrotButton icon><UnderlineIcon /></CarrotButton>
</CarrotButtonGroup>

Props

PropTypeDefaultDescription
role"group" | "toolbar""group"ARIA role
labelstringAccessible group label

Dependencies

PackagePurpose
@carrot/design-components-buttonsCSS tokens + generated styles

Build

bash
npm run build

Built with Vite + vite-plugin-dts. Outputs ES module with TypeScript declarations.


Usage Guide

Setup

ts
import { CarrotButton, CarrotButtonGroup } from "@carrot/design-vue-buttons";
import "@carrot/design-components-buttons";

CarrotButton

Variants and states

vue
<script setup lang="ts">
import { CarrotButton } from "@carrot/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>
  <CarrotButton @click="save" :loading="saving">Save</CarrotButton>
  <CarrotButton variant="secondary">Cancel</CarrotButton>
  <CarrotButton variant="ghost">Reset</CarrotButton>
  <CarrotButton variant="danger" @click="deleteItem">Delete</CarrotButton>
</template>
vue
<script setup lang="ts">
import { CarrotButton } from "@carrot/design-vue-buttons";
import { RouterLink } from "vue-router";
</script>

<template>
  <!-- Native anchor -->
  <CarrotButton as="a" href="https://docs.example.com" variant="link">
    Read the docs
  </CarrotButton>

  <!-- Vue Router link -->
  <CarrotButton :as="RouterLink" :to="{ name: 'settings' }" variant="secondary">
    Settings
  </CarrotButton>
</template>

Icon button with circle shape

vue
<script setup lang="ts">
import { CarrotButton } from "@carrot/design-vue-buttons";
</script>

<template>
  <!-- Square icon button -->
  <CarrotButton icon variant="ghost" aria-label="Edit">
    <EditIcon />
  </CarrotButton>

  <!-- Circular FAB-style -->
  <CarrotButton shape="circle" icon variant="primary" size="lg" aria-label="Play">
    <PlayIcon />
  </CarrotButton>
</template>

CarrotButtonGroup

Toolbar

vue
<script setup lang="ts">
import { CarrotButton, CarrotButtonGroup } from "@carrot/design-vue-buttons";
import { ref } from "vue";

const bold = ref(false);
const italic = ref(false);
</script>

<template>
  <CarrotButtonGroup role="toolbar" label="Text formatting">
    <CarrotButton icon :variant="bold ? 'primary' : 'ghost'" @click="bold = !bold" aria-label="Bold">
      <BoldIcon />
    </CarrotButton>
    <CarrotButton icon :variant="italic ? 'primary' : 'ghost'" @click="italic = !italic" aria-label="Italic">
      <ItalicIcon />
    </CarrotButton>
  </CarrotButtonGroup>
</template>

Common Patterns

Submit form button

vue
<script setup lang="ts">
import { CarrotButton } from "@carrot/design-vue-buttons";

defineProps<{ submitting: boolean; valid: boolean }>();
</script>

<template>
  <CarrotButton type="submit" full :loading="submitting" :disabled="!valid">
    Create Account
  </CarrotButton>
</template>

Custom brand color

vue
<template>
  <!-- The color cascades to all variant tokens on this button -->
  <CarrotButton color="#e91e63">Subscribe</CarrotButton>
</template>

Carrot