#22076·vuetify

[Feature Request] Add `VSplitButton` Component

Author: avinash-skylarkdronesCreated Sep 17, 2025Updated Sep 11, 2026
LabelsT: featureC: New Component

Problem to solve

Description

Currently, Vuetify does not provide a built-in SplitButton component. A SplitButton combines:

  • A primary action button (executes the most common/primary action).
  • A secondary dropdown toggle (reveals additional related actions).

This pattern is widely used in enterprise apps, admin panels, and productivity tools to reduce button clutter while still offering quick access to multiple related commands.


Proposed API / Usage Example

xml
<v-split-btn
  label="Save"
  :items="[
    { label: 'Save As Draft', value: 'draft' },
    { label: 'Save & Publish', value: 'publish' }
  ]"
  color="primary"
  variant="flat"
  size="large"
  @click="onSave"
  @click:item="onSplitAction"
/>
  • Props:

    • label → primary action text
    • items → array of dropdown items (or allow slot for full flexibility)
    • color, variant, size → inherited from v-btn for consistency
  • Events:

    • primary-click → when the main button is clicked
    • item-click → when a dropdown item is selected

Why this belongs in Vuetify

  • Material Design Adjacent: Many modern design systems (Material Design, Fluent UI, Bootstrap, Ant Design) include split buttons because they solve a common UX problem: offering a default action with secondary options nearby.
  • Developer Experience: Today, developers must manually compose v-btn + v-menu + v-list. This is repetitive boilerplate and inconsistent across projects.
  • Consistency: A canonical Vuetify implementation would ensure accessibility (keyboard navigation, focus trapping, aria roles), consistent sizing/alignment, and theme support.

Alternatives Today

  • Manually compose a v-btn and a v-menu with v-list (works, but verbose and error-prone).
  • Use v-btn-toggle (not semantically the same, doesn’t support a primary action + dropdown).

References


Additional Context

This component would be especially valuable in enterprise dashboards, admin panels, and data-heavy applications, where contextual actions need to be accessible but not overwhelming.

Proposed solution

xml
<template>
  <div class="d-inline-flex">
    <!-- Primary Action Button -->
    <v-btn
      :color="props.color"
      :variant="props.variant"
      :size="props.size"
      @click="emit('click')"
    >
      <slot>{{ props.label }}</slot>
    </v-btn>

    <!-- Dropdown Menu -->
    <v-menu>
      <template #activator="{ props: menuProps }">
        <v-btn
          v-bind="menuProps"
          :color="props.color"
          :variant="props.variant"
          :size="props.size"
          icon
        >
          <!-- Or let this icon be a prop/slot -->
          <v-icon>mdi-chevron-down</v-icon>
        </v-btn>
      </template>

      <v-list>
        <v-list-item
          v-for="(item, index) in props.items"
          :key="index"
          @click="emit('click:item', item)"
        >
          <v-list-item-title>{{ item.label }}</v-list-item-title>
        </v-list-item>
      </v-list>
    </v-menu>
  </div>
</template>

<script setup lang="ts">
// Or use a similar existing type
type SplitButtonItem = {
  label: string;
  value?: string | number | boolean | object;
};

// Or use a similar existing type
type SplitButtonVariant =
  | "text"
  | "flat"
  | "outlined"
  | "tonal"
  | "elevated"
  | "plain";

// Or use a similar existing type
type SplitButtonSize =
  | "x-small"
  | "small"
  | "default"
  | "large"
  | "x-large";

// Or use a similar existing type
type SplitButtonProps = {
  label?: string;
  items?: SplitButtonItem[];
  color?: string;
  variant?: SplitButtonVariant;
  size?: SplitButtonSize;
};

// Or use a similar existing type
type SplitButtonEmits = {
  (e: "click"): void;
  (e: "click:item", item: SplitButtonItem): void;
};

const props = withDefaults(defineProps<SplitButtonProps>(), {
  label: "Action",
  items: () => [],
  color: "primary",
  variant: "flat",
  size: "default",
});

const emit = defineEmits<SplitButtonEmits>();
</script>