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@clickevent + parent-controlled:loadingstate 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
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
type | 'default' | 'primary' | 'danger' | 'warning' | 'info' | undefined | Semantic 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 | |
disabled | boolean | false | Whether disabled | |
loading | boolean | false | Loading state, auto-disables + shows default spinner | |
autofocus | boolean | false | Pass-through native autofocus | |
icon | Component | undefined | Left-side icon (Vue component); replaced by spinner during loading; mutually exclusive with #icon slot (prop wins) | |
track | ButtonTrackProp | undefined | Built-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
| Name | Args | Description |
|---|---|---|
click | MouseEvent | Standard click event (not fired during disabled/loading). Async scenarios MUST pair with :loading to prevent double-submit (see SB-001) |
Slots
| Name | Description |
|---|---|
default | Button content (rendered alongside spinner during loading) |
icon | Left icon (mutually exclusive with :icon prop; prop wins) |
icon-right | Right icon (rendered during loading too; use v-if="!loading" yourself to hide) |
loading | Loading 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">.@clickis 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/colorand other el-button visual decorators are not provided; use CSS classes (e.g.<SwButton class="text-sm rounded-full">).