Button
An element used to initiate actions or trigger events.
<script setup lang="ts">
import { HButton } from '@holistics/design-system'
</script>
<template>
<HButton type="primary-highlight">
Button
</HButton>
</template>Examples
Types
<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
<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
<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:
<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:
<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>Link
<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
<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!
<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:
labelprop – Explicitly sets the accessible name.- Visible text content – Text provided within the button slot.
- ARIA attributes –
aria-label,aria-labelledby, oraria-describedby. HTooltipcontent – 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
<!-- 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
<!-- Bad: Icon-only button without any accessibility support -->
<HButton icon="save" />API
Pass-through
- If
tois specified:<RouterLink> - Else, if
hrefis 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
| Name | Type | Description |
|---|---|---|
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 |
target | HTMLAttributeTarget | The target attribute specifies where to open the linked document. Usually use with |
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
| Name | Parameters | Description |
|---|---|---|
@click | [event: MouseEvent] |
Slots
| Name | Scoped | Description |
|---|---|---|
#default | any |