Button
Headless Button; covers shadcn/ui Button, Reka UI Primitive, React Aria Button.
| Package | @vizejs/ui/button |
| Maturity | stable |
| Own the source | vize lib pull button |
| Requires | primitive, press |
| Aliases | action-button, button primitive |
| Covers | shadcn/ui Button, Reka UI Primitive, React Aria Button |
Minimal setup
Use a Vue 3.5+ project with Vue SFC compilation configured (see Vite+ integration).
vp install @vizejs/ui
Add the public import below to <script setup lang="ts">. Components provide behavior without styles; the preview uses optional Paper styles.
import { Button } from "@vizejs/ui/button";
Or copy the source into your project with vize lib pull button (see Source Distribution).
Try it
This is the basic example maintained with the component source, using the public package imports. The preview adds the optional Paper styles.
Open the interactive example · Styling these components

Copy the example
<!-- Native buttons showing press handling, a loading state, and a disabled action. -->
<script setup lang="ts">
import { ref } from "vue";
import { Button } from "@vizejs/ui/button";
const saves = ref(0);
</script>
<template>
<div>
<Button type="button" @press="() => (saves += 1)">Save draft</Button>
<Button v-slot="{ loading }" type="button" loading>
{{ loading ? "Uploading…" : "Upload" }}
</Button>
<Button type="button" disabled>Publish</Button>
<output>Saved {{ saves }} times</output>
</div>
</template>
API
Button
Source: src/families/actions/button/button.vue
Props
| Prop | Type | Default | Description |
|---|---|---|---|
as |
PrimitiveAs |
"button" |
Native element, custom element, or component to render. |
native |
boolean |
true when |
Whether the rendered target already implements native button semantics. |
type |
"button" | "reset" | "submit" |
"button" |
Native button submission behavior. |
disabled |
boolean |
false |
Remove the control from activation and sequential keyboard focus. |
loading |
boolean |
false |
Announce in-progress work and prevent repeated activation while preserving focus. |
Events
| Event | Payload | Description |
|---|---|---|
press |
[event: MouseEvent] |
Fired when pointer or keyboard activation reaches the rendered control. Carries the resulting MouseEvent. |
Slots
| Slot | Slot props | Description |
|---|---|---|
default |
{ readonly disabled: boolean; readonly loading: boolean; readonly unavailable: boolean; } |
Renders the button contents with current availability state. |
Exposed
| Member | Type | Description |
|---|---|---|
element |
Readonly<ShallowRef<HTMLElement | null>> |
Template ref to the rendered root element. |
focus |
— |
Behavior
Normative state × input → outcome table for src/families/actions/button/button.vue
(@vizejs/ui/button).
Every row is proven by the named mounted-DOM test in
src/families/actions/button/button.test.ts; a row
without a passing test is a contract violation.
| # | State | Input | Outcome | Proven by |
|---|---|---|---|---|
| B1 | idle, native | render | role=button, accessible name from slot, type="button", data-state="idle" |
renders a native button with an accessible name |
| B2 | idle | Tab | control receives focus | joins the tab order and focuses programmatically |
| B3 | idle | exposed focus() |
control receives focus | joins the tab order and focuses programmatically |
| B4 | idle | pointer click | exactly one press carrying the MouseEvent |
pointer click fires exactly one press |
| B5 | idle, native | Enter | exactly one press (activation on keydown) |
Enter and Space each fire exactly one press on a native button |
| B6 | idle, native | Space | exactly one press (activation on keyup) |
Enter and Space each fire exactly one press on a native button |
| B7 | idle, non-native | Enter | the component itself clicks the element; exactly one press |
Enter and Space activate a non-native button through its own handlers |
| B8 | idle, non-native | Space | keydown canceled (no page scroll); one press on keyup |
Enter and Space activate a non-native button through its own handlers |
| B9 | idle, non-native | Escape | ignored: not canceled, no press |
Enter and Space activate a non-native button through its own handlers |
| B10 | idle | click, Enter, Space | three press emits in dispatch order, each indistinguishably a MouseEvent |
keyboard and pointer presses are indistinguishable MouseEvents in order |
| B11 | disabled, native | render | native disabled attribute, no aria-disabled mirror, data-state="disabled" |
disabled native button removes activation and keeps native semantics |
| B12 | disabled, native | click / Enter / Space | no press |
disabled native button removes activation and keeps native semantics |
| B13 | disabled, native | Tab | skipped by the tab order | disabled native button removes activation and keeps native semantics |
| B14 | disabled, non-native | render | tabindex="-1", aria-disabled="true" |
disabled non-native button leaves the tab order and announces aria-disabled |
| B15 | disabled, non-native | click / Enter / Space / Tab | no press; skipped by the tab order |
disabled non-native button leaves the tab order and announces aria-disabled |
| B16 | loading | render | aria-busy="true", aria-disabled="true", data-state="loading", focusable |
loading button announces busy, stays focusable, and suppresses press |
| B17 | loading | focus() |
focus is accepted and retained | loading button announces busy, stays focusable, and suppresses press |
| B18 | loading | click / Enter / Space | no press |
loading button announces busy, stays focusable, and suppresses press |
| B19 | any | default slot render | slot receives live disabled, loading, unavailable state |
exposes disabled, loading, and unavailable to the default slot |
Keyboard activation timing (Enter on keydown, Space on keyup, Space keydown
canceled) is additionally pinned as pure logic by matches native keyboard activation timing.