Keyboard shortcut: Ctrl + K
Get started

Split button

VSplitButton joins a main action to a menu of related ones. The main action is a VButton; the button beside it opens a VMenu.

Usage

vue

Last action: Nothing yet

<script setup lang="ts">
import { ref } from 'vue'
import { VMenuItem, VMenuSeparator, VSplitButton, VTypography } from 'vectis-ui'

const last = ref('Nothing yet')
</script>

<template>
  <div class="demo">
    <VSplitButton label="Save" @click="last = 'Saved'">
      <VMenuItem label="Save as draft" @select="last = 'Saved as draft'" />
      <VMenuItem label="Save and close" @select="last = 'Saved and closed'" />
      <VMenuSeparator />
      <VMenuItem label="Discard changes" tone="danger" @select="last = 'Changes discarded'" />
    </VSplitButton>
    <VTypography variant="body-sm" tone="muted">Last action: {{ last }}</VTypography>
  </div>
</template>

<style scoped>
.demo {
  display: grid;
  gap: var(--vectis-space-3);
  justify-items: start;
}
</style>

Examples

Variants and tones

variant, tone, size, compact and elevated take the values of VButton and apply to both halves. A line always separates them.

vue
<script setup lang="ts">
import { VMenuItem, VSplitButton } from 'vectis-ui'
</script>

<template>
  <div class="row">
    <VSplitButton label="Publish">
      <VMenuItem label="Schedule" />
    </VSplitButton>
    <VSplitButton label="Publish" variant="soft">
      <VMenuItem label="Schedule" />
    </VSplitButton>
    <VSplitButton label="Export" variant="outline" tone="neutral">
      <VMenuItem label="Export as CSV" />
    </VSplitButton>
    <VSplitButton label="Delete" variant="ghost" tone="danger">
      <VMenuItem label="Delete all versions" tone="danger" />
    </VSplitButton>
  </div>
</template>

<style scoped>
.row {
  display: flex;
  flex-wrap: wrap;
  gap: var(--vectis-space-4);
}
</style>

Sizes

The menu button stays square at every size.

vue
<script setup lang="ts">
import { VMenuItem, VSplitButton } from 'vectis-ui'
</script>

<template>
  <div class="row">
    <VSplitButton
      v-for="size in ['xs', 'sm', 'md', 'lg', 'xl'] as const"
      :key="size"
      :size="size"
      label="Save"
    >
      <VMenuItem label="Save as draft" />
    </VSplitButton>
  </div>
</template>

<style scoped>
.row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--vectis-space-4);
}
</style>

Loading and disabled

loading puts the spinner in the main action and disables the menu button too, since an action is already under way. disabled turns off both halves.

vue
<script setup lang="ts">
import { ref } from 'vue'
import { VMenuItem, VSplitButton } from 'vectis-ui'

const saving = ref(false)

function save() {
  saving.value = true
  setTimeout(() => (saving.value = false), 1500)
}
</script>

<template>
  <div class="row">
    <VSplitButton label="Save" :loading="saving" @click="save">
      <VMenuItem label="Save as draft" />
    </VSplitButton>
    <VSplitButton label="Save" disabled>
      <VMenuItem label="Save as draft" />
    </VSplitButton>
  </div>
</template>

<style scoped>
.row {
  display: flex;
  flex-wrap: wrap;
  gap: var(--vectis-space-4);
}
</style>

href turns the main action into a link. iconStart and iconEnd belong to the main action, and menuIcon replaces the chevron. menuLabel replaces the name screen readers hear for the menu button, "More options" by default.

vue
Open report
<script setup lang="ts">
import { VMenuItem, VSplitButton } from 'vectis-ui'
import { description } from 'vectis-ui/icons'
</script>

<template>
  <VSplitButton
    href="#link-and-icons"
    variant="outline"
    tone="neutral"
    :icon-start="description"
    menu-label="Other formats"
    label="Open report"
  >
    <VMenuItem label="Download as CSV" />
    <VMenuItem label="Download as PDF" />
  </VSplitButton>
</template>

Full width and menu placement

The menu is anchored to the whole control and lines up with its end by default. With fullWidth, the main action takes the room and the menu button stays square; matchWidth keeps the menu at least as wide as the control.

vue
<script setup lang="ts">
import { VMenuItem, VSplitButton } from 'vectis-ui'
</script>

<template>
  <div class="column">
    <VSplitButton full-width match-width placement="bottom-start" label="Export">
      <VMenuItem label="Export as CSV" />
      <VMenuItem label="Export as PDF" />
      <VMenuItem label="Export as JSON" />
    </VSplitButton>
  </div>
</template>

<style scoped>
.column {
  max-inline-size: 22rem;
}
</style>

API

Props

PropTypeDefault
labelstringnone
Text of the main action.
variantButtonVariant'solid' | 'outline' | 'ghost' | 'soft''solid'
Visual weight of both halves.
toneButtonTone'accent' | 'neutral' | 'danger''accent'
Colour of both halves.
sizeButtonSize'xs' | 'sm' | 'md' | 'lg' | 'xl''md'
Height of both halves, from the shared control scale.
compactbooleanfalse
Takes 4px off the height.
elevatedbooleanfalse
Raises the control with a shadow.
fullWidthbooleanfalse
Fills the parent. The main action takes the room; the menu button stays square.
hrefstringnone
Turns the main action into a link, inert while disabled or loading.
typeButtonHTMLAttributes['type']'button'
Native type of the main button.
disabledbooleanfalse
Disables both halves.
loadingbooleanfalse
Shows a spinner in the main action and disables the menu button.
iconStartIconSourcenone
Icon before the label of the main action.
iconEndIconSourcenone
Icon after the label of the main action.
iconFilledbooleanfalse
Renders the icons of the main action filled.
menuLabelstringnone
Accessible name of the menu button. "More options" by default.
menuIconIconSourceexpand_more
Icon of the menu button.
placementMenuPlacement'top' | 'top-start' | 'top-end' | 'bottom' | 'bottom-start' | 'bottom-end''bottom-end'
Menu position relative to the whole control; adjusts when space is short.
menuSizeMenuSize'sm' | 'md' | 'lg''sm'
Row height of the menu.
menuWidthnumber | stringnone
Menu width. Numbers use pixels; strings use CSS lengths or keywords.
matchWidthbooleanfalse
Keeps the menu at least as wide as the whole control.
v-model:openbooleanfalse
Open state of the menu.

Events

EventType
click[event: MouseEvent]
The main action was activated.

Slots

SlotType
default{}
VMenuItem, VMenuGroup and VMenuSeparator children.

Types

Types used in the API tables. Import exported types from vectis-ui.

export interface BuiltinIcon {
  name: string
  paths: readonly [string] | readonly [string, string]
}
export type IconRender =
  | { path: string; viewBox?: string }
  | { component: Component; props?: Record<string, unknown> }
  | { src: string }
  | { text: string; class?: string }
  | { class: string }
export type IconSource = string | BuiltinIcon | IconRender