Skip to content

Button

Stable

An element used to initiate actions or trigger events.

vue
<script setup lang="ts">
import { HButton } from '@holistics/design-system'
</script>

<template>
  <HButton type="primary-highlight">
    Button
  </HButton>
</template>

Examples

Types

vue
<script setup lang="ts">
import { HButton, BUTTON_TYPES } from '@holistics/design-system'
</script>

<template>
  <div class="flex flex-wrap items-center gap-x-4 gap-y-2">
    <HButton
      v-for="type in BUTTON_TYPES"
      :key="type"
      :type="type"
    >
      {{ type }}
    </HButton>
  </div>
</template>

Sizes

vue
<script setup lang="ts">
import { HButton, BUTTON_SIZES } from '@holistics/design-system'
</script>

<template>
  <div class="flex flex-wrap items-center gap-x-4 gap-y-2">
    <HButton
      v-for="size in BUTTON_SIZES"
      :key="size"
      type="primary-highlight"
      :size="size"
    >
      {{ size }}
    </HButton>
  </div>
</template>

States

vue
<script setup lang="ts">
import { HButton } from '@holistics/design-system'
</script>

<template>
  <div class="flex flex-wrap items-center gap-x-4 gap-y-2">
    <HButton type="primary-highlight">
      default
    </HButton>

    <HButton
      type="primary-highlight"
      active
    >
      active
    </HButton>

    <HButton
      type="primary-highlight"
      disabled
    >
      disabled
    </HButton>
  </div>
</template>

Pseudo-disabled state

If you want to have default disabled styles and behaviors (e.g. dimmed background, not clickable) while still want to capture pointer event for some use-cases (e.g. showing tooltip on hover), then you can add disabled class to the <HButton> component:

vue
<script setup lang="ts">
import { HButton } from '@holistics/design-system'
</script>

<template>
  <HButton
    type="primary-highlight"
    class="disabled"
  >
    pseudo-disabled
  </HButton>
</template>

Block

Buttons are inline by default, which means you can put it side-by-side with regular text and the button will expand to fit its content. In some cases, it's desirable to make a button always expand to its parent width:

vue
<script setup lang="ts">
import { ref } from 'vue'
import { HButton } from '@holistics/design-system'

const blockWidth = ref(256)
</script>

<template>
  <div class="space-y-4">
    <div>
      <div class="flex items-center">
        <input
          v-model="blockWidth"
          type="range"
          min="256"
          max="512"
        >
        <span class="ml-1">{{ blockWidth }}px</span>
      </div>

      <div
        class="mt-1"
        :style="{ width: blockWidth + 'px' }"
      >
        <HButton
          type="primary-highlight"
          block
        >
          Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
        </HButton>
      </div>
    </div>
  </div>
</template>
vue
<script setup lang="ts">
import { HButton } from '@holistics/design-system'
</script>

<template>
  <div class="flex flex-wrap items-center gap-x-4 gap-y-2">
    <HButton
      type="primary-highlight"
      href="https://holistics.io"
      icon-right="external-link"
    >
      Holistics.io
    </HButton>

    <HButton
      type="primary-highlight"
      to="/"
    >
      Router
    </HButton>
  </div>
</template>

WARNING

You must install vue-router and register its plugin first to be able to use Button as <RouterLink>

Icons

vue
<script setup lang="ts">
import { HButton } from '@holistics/design-system'
</script>

<template>
  <div class="flex flex-wrap items-center gap-x-4 gap-y-2">
    <HButton
      type="primary-highlight"
      icon="home"
    >
      Home
    </HButton>

    <HButton
      type="secondary-default"
      icon-right="home"
    >
      Home
    </HButton>

    <HButton
      type="tertiary-default"
      icon="home"
      icon-right="home"
    >
      Home
    </HButton>

    <HButton
      type="clear-default"
      icon="cancel"
      title="Close!"
    />

    <HButton
      type="primary-highlight"
      icon="loading"
      icon-spin
    >
      Loading
    </HButton>
  </div>
</template>

Unified Paddings

When the button contains only an icon, it's best to make all paddings unified to the same size!

vue
<script setup lang="ts">
import { BUTTON_SIZES, HButton } from '@holistics/design-system'
</script>

<template>
  <div class="flex flex-wrap items-center gap-x-4 gap-y-2">
    <HButton
      v-for="size in BUTTON_SIZES"
      :key="size"
      type="primary-highlight"
      icon="data-model"
      :size="size"
      unified
    />
  </div>
</template>

Accessibility

Icon-only buttons

Icon-only buttons can present accessibility challenges because they lack visible text to explain their purpose. Without an accessible name, screen readers cannot identify what these buttons do, and users may not understand unfamiliar icons.

To ensure accessibility, HButton determines its accessible name from these sources, in order of priority:

  1. label prop – Explicitly sets the accessible name.
  2. Visible text content – Text provided within the button slot.
  3. ARIA attributesaria-label, aria-labelledby, or aria-describedby.
  4. HTooltip content – When the button contains only an icon, the tooltip content can serve as the accessible name.

ESLint Integration

Our @holistics/require-button-tooltip ESLint rule automatically detects icon-only buttons without accessible names and flags them as errors. Running pnpm lint --fix will wrap problematic buttons in tooltip placeholders for you to complete.

✅ Correct Usage Examples

html
<!-- Good: Using aria-label for screen readers when the icon is universally recognised -->
<HButton
  icon="close"
  aria-label="Close dialog"
/>

<!-- Good: Using aria-labelledby to reference another element -->
<HButton
  icon="edit"
  aria-labelledby="edit-label"
/>
<span id="edit-label">Edit Profile</span>

<!-- Good: Using aria-describedby for additional context -->
<HButton
  icon="delete"
  aria-label="Delete item"
  aria-describedby="delete-warning"
/>
<div id="delete-warning">This action cannot be undone</div>

<!-- Good: Icon button wrapped with tooltip -->
<HTooltip content="Save your changes">
  <HButton icon="save" />
</HTooltip>

❌ What NOT to do

html
<!-- Bad: Icon-only button without any accessibility support -->
<HButton icon="save" />

API

Pass-through

  • If to is specified: <RouterLink>
  • Else, if href is specified: <a>
  • Otherwise, <button>

What does this mean?

All props, events, and attrs that are not specified in the tables below will be passed to the element/component described above.

Props

NameTypeDescription
type *
"primary-highlight" | "secondary-default" | "tertiary-highlight" | "tertiary-default" | "primary-danger" | "tertiary-danger" | "primary-warning" | "primary-success" | "outline-danger" | ... 5 more ... | "clear-danger"
nativeType 
"reset" | "submit" | "button"
= BUTTON_NATIVE_TYPES[1]
size 
"sm" | "md" | "lg"
= BUTTON_SIZES[1]
theme 
"light" | "dark"
= "light"
active 
boolean

Control the active state of the button programatically.

block 
boolean

Whether the Button should display as a block.

unified 
boolean

Button will use the same padding value for block and inline direction. Must enable this prop when the button is icon only.

disabled 
boolean
href 
string

The href value if the button is used as a link.

to 
string

A string representing a path or route. Where to navigate when the button is clicked.

replace 
boolean

Specifies whether or not the history should be replaced. Usually use with to.

target 
HTMLAttributeTarget

The target attribute specifies where to open the linked document. Usually use with href.

label 
string
icon 
"function" | "cancel" | "copy" | "cut" | "error" | "pause" | "play" | "add-block" | "add-circle" | "add-filter" | "add-tag" | "add-user" | "add" | "address-card" | "adhoc-query" | ... 466 more ...
iconSpin 
boolean
iconRight 
"function" | "cancel" | "copy" | "cut" | "error" | "pause" | "play" | "add-block" | "add-circle" | "add-filter" | "add-tag" | "add-user" | "add" | "address-card" | "adhoc-query" | ... 466 more ...
iconRightSpin 
boolean
iconSize 
"sm" | "md" | "lg" | "xl" | "xs" | "2x" | "3x" | "4x" | "onboarding" | 100

The size of both left and right icons.

shortcut 
string

Events

NameParametersDescription
@click
[event: MouseEvent]

Slots

NameScopedDescription
#default
any