Button ​

A clickable element that runs an action, submits a form or opens a link.

label
variant
theme
size
<Button
  variant="subtle"
  theme="gray"
  size="sm"
  label="Save"
/>

Examples ​

List view controls ​

Filter and view controls above a list. icon-left and icon-right put an icon beside the label, icon alone makes an icon-only button, and the #suffix slot holds a count.

vue
<script setup>
import { Button } from 'frappe-ui'
</script>

<template>
  <div class="flex w-full max-w-[1100px] items-center gap-8">
    <div class="flex flex-1 items-center gap-1.5">
      <Button>Lead owner</Button>
      <Button icon-right="lucide-chevron-down">Status</Button>
      <Button icon-right="lucide-chevron-down">Organisation</Button>
    </div>
    <div class="flex items-center gap-2">
      <Button icon-left="lucide-eye-off">Columns</Button>
      <Button icon-left="lucide-list-filter">
        Filter
        <template #suffix>
          <span
            class="inline-flex h-4.5 items-center justify-center rounded-1 bg-surface-base px-1.5 text-xs text-ink-gray-7 shadow-sm ring-1 ring-black/5"
          >
            1
          </span>
        </template>
      </Button>
      <Button icon-left="lucide-arrow-up-down">Sort</Button>
      <Button icon="lucide-ellipsis" label="More" />
    </div>
  </div>
</template>

Section header ​

A ghost button with the section name, and an add action at the other end of the row.

vue
<script setup>
import { Button } from 'frappe-ui'
</script>

<template>
  <div class="flex w-full max-w-[352px] items-center gap-2 p-3">
    <Button
      variant="ghost"
      icon-left="lucide-chevron-down"
      class="-ml-2 font-medium"
    >
      Contacts
    </Button>
    <div class="flex-1" />
    <Button icon-left="lucide-plus">Add contact</Button>
  </div>
</template>

Selection toolbar ​

A row of ghost buttons that act on the selected items. theme="red" marks the destructive action.

vue
<script setup>
import { Button } from 'frappe-ui'
</script>

<template>
  <div
    class="inline-flex items-center gap-3.5 rounded-6 bg-surface-base p-1.5 shadow-[0px_0px_1.5px_rgba(0,0,0,0.16),0px_2px_5px_rgba(0,0,0,0.14)]"
  >
    <Button variant="ghost" icon-left="lucide-x">2 selected</Button>
    <div class="h-5 border-l border-outline-gray-1" />
    <div class="flex items-center gap-1">
      <Button variant="ghost" icon-left="lucide-download">Download</Button>
      <Button variant="ghost" icon-left="lucide-corner-up-right">Move</Button>
      <Button variant="ghost" icon-left="lucide-share-2">Share</Button>
      <Button variant="ghost" theme="red" icon-left="lucide-trash-2">
        Delete
      </Button>
    </div>
  </div>
</template>

Primary and secondary action ​

A solid button for the main action beside a default subtle one.

vue
<script setup>
import { Button } from 'frappe-ui'
</script>

<template>
  <div class="flex items-center gap-2">
    <Button>Join</Button>
    <Button variant="solid">Reschedule</Button>
  </div>
</template>

Full-width actions ​

Add class="w-full" to stack buttons in a narrow column.

vue
<script setup>
import { Button } from 'frappe-ui'
</script>

<template>
  <div class="flex w-58 flex-col items-stretch gap-2">
    <Button class="w-full">Join</Button>
    <Button variant="solid" class="w-full">Reschedule</Button>
  </div>
</template>

Card actions ​

Two actions that share the width of a card, each with class="flex-1".

Frappe january - 2023

Wed, Dec 27, 2023
03:45 PM - 04:15 PM (30 min)

Digital products are more abstract and complex than any product we’v…

vue
<script setup>
import { Button } from 'frappe-ui'
</script>

<template>
  <div
    class="flex w-96 flex-col gap-6 rounded-6 bg-surface-base p-4 shadow-[0px_0px_0.75px_rgba(0,0,0,0.16),0px_2px_2.5px_rgba(0,0,0,0.14)]"
  >
    <div class="flex flex-col gap-5">
      <div class="flex flex-col gap-3">
        <p class="text-md-semibold text-ink-gray-7">Frappe january - 2023</p>
        <div class="flex flex-col gap-3">
          <div class="flex items-center gap-2 text-base text-ink-gray-7">
            <span class="lucide-calendar size-4" aria-hidden="true" />
            <span>Wed, Dec 27, 2023</span>
          </div>
          <div class="flex items-center gap-2 text-base text-ink-gray-7">
            <span class="lucide-clock size-4" aria-hidden="true" />
            <span>03:45 PM - 04:15 PM (30 min)</span>
          </div>
        </div>
      </div>
      <p class="text-base leading-normal text-ink-gray-7">
        Digital products are more abstract and complex than any product we’v…
      </p>
    </div>
    <div class="flex items-center gap-2">
      <Button class="flex-1">Start</Button>
      <Button variant="solid" class="flex-1" icon-left="lucide-video">
        Join
      </Button>
    </div>
  </div>
</template>

Behavior ​

Pressed state ​

Button shows its pressed look while its data-state attribute is open or active. There is no active prop.

As a menu trigger, this needs no wiring. The menu sets data-state="open" on the trigger, so the button stays pressed while the menu is open.

vue
<Dropdown :options="options" :button="{ label: 'Options' }" />

For a standalone toggle, such as a toolbar control or a filter chip, set the attribute yourself. It changes only the look, so pair it with aria-pressed:

vue
<Button
  label="Bold"
  :data-state="isBold ? 'active' : 'inactive'"
  :aria-pressed="isBold"
/>

Loading ​

loading shows a spinner in place of the left icon and makes the button non-interactive, without the dimmed disabled look. loading-text replaces the label while it loads. Only disabled dims the button.

With route, the button renders as a router link. With href, it renders as an <a> that opens the URL in a new tab. A disabled or loading button always renders as a native <button>, so it cannot be followed. type sets the native button type and defaults to button.

Accessibility ​

  • An icon-only button needs a label. The label is not shown. It becomes the button's aria-label.
  • A loading button sets aria-busy="true".

API Reference ​

Show types
typescript
import type { RouteDestination } from '../shared/route'
import { type Component, type ExtractPublicPropTypes, type PropType } from 'vue'

export type Theme = 'gray' | 'blue' | 'green' | 'red'
export type Size = 'xs' | 'sm' | 'md' | 'lg'
export type Variant = 'solid' | 'subtle' | 'outline' | 'ghost'

const iconProp = {
  type: [String, Object, Function] as PropType<string | Component>,
  default: undefined,
}

/**
 * Runtime prop definitions — the single source of truth for the button's props.
 * `Button.vue` spreads these into `defineComponent`, and the public `ButtonProps`
 * type is derived from them, so the runtime and the type can never drift apart.
 */
export const buttonProps = {
  /** Visual color theme of the button */
  theme: { type: String as PropType<Theme>, default: 'gray' },
  /** Controls the button size */
  size: { type: String as PropType<Size>, default: 'sm' },
  /** Visual style of the button */
  variant: { type: String as PropType<Variant>, default: 'subtle' },
  /** Text label displayed inside the button */
  label: { type: String, default: undefined },
  /** Icon shown when no left or right icon is specified */
  icon: iconProp,
  /** Icon shown before the label */
  iconLeft: iconProp,
  /** Icon shown after the label */
  iconRight: iconProp,
  /** Tooltip text shown on hover */
  tooltip: { type: String, default: undefined },
  /** Shows a loading state and disables interaction */
  loading: { type: Boolean, default: false },
  /** Text shown while the button is loading */
  loadingText: { type: String, default: undefined },
  /** Disables the button */
  disabled: { type: Boolean, default: false },
  /** Router destination when used as a link */
  route: {
    type: [String, Object] as PropType<RouteDestination>,
    default: undefined,
  },
  /** External URL, opened in a new tab. */
  href: { type: String, default: undefined },
  /** Native button type */
  type: {
    type: String as PropType<'button' | 'submit' | 'reset'>,
    default: 'button',
  },
}

/** Public prop types for `<Button>`. Derived from {@link buttonProps}. */
export type ButtonProps = ExtractPublicPropTypes<typeof buttonProps>

/** Combined theme and variant key */
export type ThemeVariant = `${Theme}-${Variant}`
theme
= 'gray'
Theme

Visual color theme of the button

size
= 'sm'
Size

Controls the button size

variant
= 'subtle'
Variant

Visual style of the button

label
string

Text label displayed inside the button

icon
string | Component

Icon shown when no left or right icon is specified

iconLeft
string | Component

Icon shown before the label

iconRight
string | Component

Icon shown after the label

tooltip
string

Tooltip text shown on hover

loading
= false
boolean

Shows a loading state and disables interaction

loadingText
string

Text shown while the button is loading

disabled
= false
boolean

Disables the button

route
RouteDestination

Router destination when used as a link

href
string

External URL, opened in a new tab.

type
= 'button'
"button" | "submit" | "reset"

Native button type

prefix
[void]

Content shown before the button label (left icon / custom content)

icon
[void]

Icon-only content for icon buttons

default
[void]

Main button content (overrides `label`)

suffix
[void]

Content shown after the button label (right icon / custom content)