SwButton

SwButton is the framework's base button component. On top of native <button> it adds loading state management (with default spinner), disabled state, and icon rendering logic. All HTML attributes (class, style, aria-*) pass through to the native <button> via inheritAttrs, and the API is fully vue-idiomatic: standard @click event + :loading / :disabled props.

Async MUST pair with :loading: SwButton has no built-in throttling (aligned with element-plus / antd / mui / reka-ui industry norms); use @click event + parent-controlled :loading state to prevent double-submit (see standards/usage-review-rules.md Rule SB-001).

Basic usage

Bind a standard @click event listener.

Basic usage

加载中...

Async operations (must use )

During an async handler, the parent component MUST manage the :loading state; otherwise the user can click again during await → double-submit.

Async + :loading state

加载中...

Disabled state

:disabled="true" disables the button. The disabled attribute is also reflected on the native <button>, so CSS :disabled pseudo-class works.

Disabled state

加载中...

Semantic type (data-type, self-styled)

加载中...

Form submit (native-type=submit + Enter)

加载中...

Semantic type

The type prop is rendered as a data-type HTML attribute, with no built-in colors. Project defines visual styling via CSS attribute selectors:

<SwButton type="primary" @click="submit">Submit</SwButton>
<SwButton type="danger" @click="remove">Delete</SwButton>
.sw-button[data-type="primary"] { background: var(--primary); }
.sw-button[data-type="danger"]  { background: var(--destructive); }

Form submit button

Use native-type="submit" to make the button trigger form submission (native HTML5 validation, Enter-key submit, etc.).

<form @submit.prevent="onLogin">
  <input v-model="username" required>
  <input v-model="password" type="password" required>
  <SwButton native-type="submit" :loading="submitting" type="primary">
    Log in
  </SwButton>
</form>

Icon button

Pass a Vue component via :icon prop as the left-side icon. During loading it's automatically replaced by the spinner.

<script setup>
import { PhFloppyDisk, PhTrash } from '@phosphor-icons/vue'
</script>

<template>
  <SwButton :icon="PhFloppyDisk" @click="save" :loading="saving">Save</SwButton>
  <SwButton :icon="PhTrash" type="danger" @click="remove">Delete</SwButton>
</template>

You can also use the #icon slot (mutually exclusive with :icon prop; prop takes precedence):

<SwButton @click="next">
  Next
  <template #icon-right>→</template>
</SwButton>

Loading spinner

When :loading="true", the default spinner is shown (~10 lines of CSS, the framework's minimal necessary visual). The spinner uses 1em size + currentColor color, automatically following the button's font-size and color — no extra adaptation needed.

Don't like the default? Two override paths:

1. CSS override (color / size / animation):

.sw-button-spinner {
  border-color: #fff #fff transparent;
  width: 1.2em;
  animation-duration: 0.5s;
}

2. #loading slot full replacement:

<SwButton @click="upload" :loading="uploading">
  <template #loading>
    <MyCustomSpinner />
  </template>
  Upload
</SwButton>

Click tracking (:track)

:track is opt-in built-in click tracking (zero behavior when omitted): { click: { event, payload } } reports via useTracker on click, and never fires while disabled / loading; event is constrained to the WebSdkEvent union (typos are caught by TS). If you have your own tracking SDK, just use @click (the escape hatch). Field conventions match SwLink's :track (exposure / click payloads of the same element must agree — see standards SL-003).

<SwButton
  :track="{ click: { event: 'appPageitemClick', payload: {
    item_id: '123', item_type: 'video', item_name: 'Title',
    parent_item_id: '', position: 1, page_key: 'home', page_name: 'Home',
  } } }"
  @click="play"
>
  Play
</SwButton>

Props

NameTypeRequiredDefaultDescription
type'default' | 'primary' | 'danger' | 'warning' | 'info'undefinedSemantic type, output as data-type, no built-in styling (applier CSS hooks in)
nativeType'button' | 'submit' | 'reset''button'Native <button type>; use 'submit' for form submit buttons
disabledbooleanfalseWhether disabled
loadingbooleanfalseLoading state, auto-disables + shows default spinner
autofocusbooleanfalsePass-through native autofocus
iconComponentundefinedLeft-side icon (Vue component); replaced by spinner during loading; mutually exclusive with #icon slot (prop wins)
trackButtonTrackPropundefinedBuilt-in click tracking (opt-in): { click: { event, payload } } reports via useTracker on click; never fires when disabled/loading; zero behavior when omitted. event is constrained to the WebSdkEvent union. Escape hatch: use @click with your own tracking SDK. Example: :track="{ click: { event: 'appPageitemClick', payload: { item_id: '123', /* … */ } } }"

Events

NameArgsDescription
clickMouseEventStandard click event (not fired during disabled/loading). Async scenarios MUST pair with :loading to prevent double-submit (see SB-001)

Slots

NameDescription
defaultButton content (rendered alongside spinner during loading)
iconLeft icon (mutually exclusive with :icon prop; prop wins)
icon-rightRight icon (rendered during loading too; use v-if="!loading" yourself to hide)
loadingLoading custom content; defaults to .sw-button-spinner if not provided

Notes

  • API is standard vue event: <SwButton @click="fn"> works exactly like native <button @click="fn">. @click is no longer swallowed.
  • Async must use :loading: framework no longer does built-in throttling (industry mainstream doesn't either). Preventing double-submit is the parent's responsibility via :loading. Enforced by standards/usage-review-rules.md Rule SB-001.
  • Form submit needs native-type="submit": default 'button' does NOT trigger form submit. See Rule SB-002.
  • Pure styling props NOT built-in: size / plain / round / circle / color and other el-button visual decorators are not provided; use CSS classes (e.g. <SwButton class="text-sm rounded-full">).