Skip to content

Button

Button is a native-first action primitive. It owns truthful element selection, safe button type, disabled semantics, attribute forwarding, a stable data-slot, and conflict-aware class composition. Your application owns loading state, navigation decisions, business language, and the visual recipe.

There are intentionally no variant, size, color, tone, radius, elevated, or loading props.

Button.vue

Installation

The standard Boring Stack path requires no init, klean-ui.json, alias prompt, or generated cn.js. The installer detects the framework and conventional source paths.

Run one command from a Boring Stack application. Klean detects the framework and conventional destination, then adds the framework-native source and its direct dependencies.

Terminal
npx klean-ui add button

  • No initializer or configuration file
  • No framework, alias, or theme questions
  • No Klean runtime dependency

Usage

usage.vue
<script setup>
import Button from '@/components/ui/button/Button.vue'
</script>

<template>
  <Button type="submit">Save changes</Button>
</template>

Visual styling stays in the framework's ordinary class API. If the same product treatment repeats, create an application-owned component such as PrimaryButton.vue using Button as its semantic base.

Pending actions

Button intentionally has no loading prop. The real request state owns disabled and aria-busy, the visible label says what is happening, and Spinner supplies a decorative mark. A product such as Slipway can pass its own animated mascot through Spinner without changing Button's API.

pending-button.vue

API

InputDefaultPurpose
as'button'Render a native button, native a, or framework component such as Inertia Link.
type'button'Native button behavior: button, submit, or reset. Ignored for non-buttons.
disabledfalseNative disabled behavior for buttons and accessible disabled semantics for links.
classThe visual API. Caller Tailwind classes merge last.
default slotLabel, decorative icon, spinner, or other accessible content.

Semantic elements

Appearance does not decide semantics. Use one truthful interactive element:

  • <button> for actions, local state, dialogs, and form submission;
  • <a> for external navigation, downloads, OAuth, or full-page requests;
  • the Boring Stack Link for internal Inertia navigation.

Do not wrap a Button inside an anchor. Render Button as the anchor or Link.

semantic-usage.vue
Pass the Boring Stack Link component for internal navigation.

Product recipes

Klean's neutral default stays motionless and uses tonal feedback. Hagfish deliberately adds an offset-shadow press; Slipway keeps its dense operational controls quiet. Those opinions are explicit Tailwind classes, not Klean variants.

product-buttons.vue
Every class shown here is built into Tailwind or written as an explicit arbitrary value. There are no hidden Klean theme utilities.

Accessibility contract

  • The default is a real <button type="button">.
  • Submit and reset behavior remain native.
  • Keyboard focus is visible without relying on color alone.
  • Icon-only usage needs an accessible name such as aria-label.
  • Disabled links leave the tab order and cannot activate.
  • Processing indicators are decorative when the visible label already describes the state.
  • Base interaction is motionless, and transitions are removed for reduced-motion preferences.
  • Tooltip — adds short supplementary text without changing the button or link semantics.
  • Spinner — a decorative pending mark inside a truthfully labelled busy button.
  • Slide — higher-friction confirmation for consequential actions.
  • Menu — a compact list of actions and destinations.
  • Popover — a non-modal surface invoked by a button.
  • Dialog — a modal task or confirmation invoked by a native command.
  • Input — native form input with caller-owned labels and errors.

All open source projects are released under the MIT License.