Vue UI Components

April 20, 2026 ยท View on GitHub

@rcaferati/vue-awesome-button provides three related Vue 3 button components:

  • AwesomeButton - animated base button
  • AwesomeButtonProgress - progress flow wrapper on top of AwesomeButton
  • AwesomeButtonSocial - social/share wrapper on top of AwesomeButton

This README is updated for the current 1.0.1 release outputs, including base CSS, bundled theme CSS files, Vue slots, emits, and plugin registration.


Preview

Awesome Button visual preview


Installation

npm install @rcaferati/vue-awesome-button

Quick Start

Import the base stylesheet once, import any theme CSS files you want to use, then render a button with the matching theme prop.

<script setup lang="ts">
import { AwesomeButton } from '@rcaferati/vue-awesome-button';
import '@rcaferati/vue-awesome-button/styles.css';
import '@rcaferati/vue-awesome-button/themes/theme-blue.css';
</script>

<template>
  <AwesomeButton theme="blue" type="primary">
    Button
  </AwesomeButton>
</template>

Styling With Theme CSS

The Vue package does not use theme mapping objects. Themes are plain CSS files scoped by the theme prop.

Blue theme example

import '@rcaferati/vue-awesome-button/styles.css';
import '@rcaferati/vue-awesome-button/themes/theme-blue.css';
<AwesomeButton theme="blue" type="primary">
  Button
</AwesomeButton>

Multiple themes in the same app

Import each theme CSS file once, then switch themes per button instance.

import '@rcaferati/vue-awesome-button/styles.css';
import '@rcaferati/vue-awesome-button/themes/theme-blue.css';
import '@rcaferati/vue-awesome-button/themes/theme-rickiest.css';
<template>
  <AwesomeButton theme="blue" type="primary">
    Blue
  </AwesomeButton>

  <AwesomeButton theme="rickiest" type="secondary">
    Rickiest
  </AwesomeButton>
</template>

Bundled themes

  • theme-amber.css
  • theme-blue.css
  • theme-bojack.css
  • theme-bruce.css
  • theme-c137.css
  • theme-eric.css
  • theme-flat.css
  • theme-indigo.css
  • theme-red.css
  • theme-rickiest.css

Plugin Registration

Use the plugin if you want global component registration.

import { createApp } from 'vue';
import App from './App.vue';
import { AwesomeButtonPlugin } from '@rcaferati/vue-awesome-button';
import '@rcaferati/vue-awesome-button/styles.css';
import '@rcaferati/vue-awesome-button/themes/theme-blue.css';

createApp(App).use(AwesomeButtonPlugin).mount('#app');

After registration, AwesomeButton, AwesomeButtonProgress, and AwesomeButtonSocial are available in templates without local imports.


AwesomeButton

Basic button behavior

If href is not provided, AwesomeButton renders button-like behavior and emits press on successful activation.

<script setup lang="ts">
import { AwesomeButton } from '@rcaferati/vue-awesome-button';
import '@rcaferati/vue-awesome-button/styles.css';
import '@rcaferati/vue-awesome-button/themes/theme-blue.css';

function handleSave() {
  // do something
}
</script>

<template>
  <AwesomeButton theme="blue" type="primary" @press="handleSave">
    Save changes
  </AwesomeButton>
</template>

Anchor mode (href provided)

If href is provided, the component renders anchor-like behavior and lets native navigation happen. Normal Vue attrs are forwarded to the root element.

<script setup lang="ts">
import { AwesomeButton } from '@rcaferati/vue-awesome-button';
import '@rcaferati/vue-awesome-button/styles.css';
import '@rcaferati/vue-awesome-button/themes/theme-blue.css';
</script>

<template>
  <AwesomeButton
    theme="blue"
    type="link"
    href="https://github.com/rcaferati"
    target="_blank"
    rel="noreferrer noopener"
  >
    Open website
  </AwesomeButton>
</template>

Icons with slots

Use before and after slots to render icons. For icon-only buttons, pass the icon with a named slot and omit default slot text.

<script setup lang="ts">
import { AwesomeButton } from '@rcaferati/vue-awesome-button';
import '@rcaferati/vue-awesome-button/styles.css';
import '@rcaferati/vue-awesome-button/themes/theme-blue.css';
</script>

<template>
  <div style="display: grid; gap: 12px;">
    <AwesomeButton theme="blue" type="primary" size="medium">
      <template #before>
        <svg aria-hidden="true" viewBox="0 0 24 24" width="16" height="16">
          <path d="M12 3l1.9 4.8L19 9.2l-4 3.4 1.3 5.2L12 15l-4.3 2.8L9 12.6 5 9.2l5.1-1.4L12 3z" />
        </svg>
      </template>

      Continue

      <template #after>
        <svg aria-hidden="true" viewBox="0 0 24 24" width="16" height="16">
          <path d="M5 12h14" />
          <path d="m13 5 7 7-7 7" />
        </svg>
      </template>
    </AwesomeButton>

    <AwesomeButton theme="blue" type="primary" size="medium" aria-label="Play">
      <template #before>
        <svg aria-hidden="true" viewBox="0 0 24 24" width="16" height="16">
          <path d="M8 5v14l11-7z" />
        </svg>
      </template>
    </AwesomeButton>
  </div>
</template>

Sizes and auto width

  • size="small" | "medium" | "large" uses fixed button dimensions.
  • :size="null" uses content-driven auto width.
  • Fixed-size changes and measured auto-width changes animate by default. Use :animate-size="false" to opt out.
  • Switching between fixed size and auto width is intentionally instant in this release.
<script setup lang="ts">
import { AwesomeButton } from '@rcaferati/vue-awesome-button';
import '@rcaferati/vue-awesome-button/styles.css';
import '@rcaferati/vue-awesome-button/themes/theme-blue.css';
</script>

<template>
  <div style="display: grid; gap: 12px;">
    <AwesomeButton theme="blue" size="small" type="primary">
      Small
    </AwesomeButton>
    <AwesomeButton theme="blue" size="medium" type="primary">
      Medium
    </AwesomeButton>
    <AwesomeButton theme="blue" size="large" type="primary">
      Large
    </AwesomeButton>
    <AwesomeButton theme="blue" :size="null" type="primary">
      Auto width grows with content
    </AwesomeButton>
    <AwesomeButton theme="blue" size="large" type="primary" :animate-size="false">
      Instant fixed-size change
    </AwesomeButton>
  </div>
</template>

Text transition (string labels only)

Use text-transition when the default slot resolves to a plain string and you want label changes to animate.

<script setup lang="ts">
import { ref } from 'vue';
import { AwesomeButton } from '@rcaferati/vue-awesome-button';
import '@rcaferati/vue-awesome-button/styles.css';

const expanded = ref(false);
</script>

<template>
  <AwesomeButton
    type="primary"
    :size="null"
    text-transition
    @press="expanded = !expanded"
  >
    {{ expanded ? 'Processing settlement report' : 'Generate report' }}
  </AwesomeButton>
</template>

AwesomeButton Slots, Emits, and Props

Slots

SlotDescription
beforeContent rendered before the main label, commonly an icon
defaultMain label/content
afterContent rendered after the main label, commonly an icon
extraExtra wrapper content used by wrappers such as progress

Emits

EmitPayloadDescription
pressMouseEvent | KeyboardEvent | PointerEventSuccessful activation/release
pressedMouseEvent | KeyboardEvent | PointerEventPress-in interaction started
releasedHTMLElement | nullRelease cycle cleared

Props

PropTypeDefaultDescription
typestring"primary"Visual variant key, such as primary, secondary, danger, link, or a social visual variant
size"small" | "medium" | "large" | null"medium"Size key or null for auto width
activebooleanfalseControlled pressed/active visual state
disabledbooleanfalseDisables interactions
visiblebooleantrueToggles visible state class
placeholderbooleanfalseRenders placeholder/skeleton state
animateSizebooleantrueAnimates fixed-size and measured auto-width changes
textTransitionbooleanfalseAnimates string-only label changes
betweenbooleanfalseUses space-between layout for content
ripplebooleanfalseEnables ripple effect on successful activation
moveEventsbooleantrueEnables pointer move position classes
hrefstring | nullnullEnables anchor-like mode
asstring | Component"button"Root element/component when href is not set
themeThemeName | nullnullApplies a bundled theme marker class, such as blue

Normal Vue attrs such as class, style, aria-*, target, and rel are forwarded to the rendered root element.


AwesomeButtonProgress

AwesomeButtonProgress wraps AwesomeButton and manages a guarded progress lifecycle. Its press emit receives a second callback argument:

  • next(true) starts the success flow
  • next(false, 'Failed') starts the error flow with an optional label override

Basic success flow

<script setup lang="ts">
import { AwesomeButtonProgress } from '@rcaferati/vue-awesome-button';
import '@rcaferati/vue-awesome-button/styles.css';
import '@rcaferati/vue-awesome-button/themes/theme-blue.css';

const sleep = (ms: number) => new Promise((resolve) => window.setTimeout(resolve, ms));

async function handleVerify(
  _event: MouseEvent | KeyboardEvent | PointerEvent,
  next: (endState?: boolean, errorLabel?: string | null) => void
) {
  await sleep(900);
  next(true);
}
</script>

<template>
  <AwesomeButtonProgress
    theme="blue"
    type="primary"
    loading-label="Verifying..."
    result-label="Verified!"
    :release-delay="500"
    @press="handleVerify"
  >
    Verify
  </AwesomeButtonProgress>
</template>

Error flow

<script setup lang="ts">
import { AwesomeButtonProgress } from '@rcaferati/vue-awesome-button';
import '@rcaferati/vue-awesome-button/styles.css';

const sleep = (ms: number) => new Promise((resolve) => window.setTimeout(resolve, ms));

async function handlePublish(
  _event: MouseEvent | KeyboardEvent | PointerEvent,
  next: (endState?: boolean, errorLabel?: string | null) => void
) {
  await sleep(900);
  next(false, 'Failed');
}
</script>

<template>
  <AwesomeButtonProgress
    type="danger"
    loading-label="Publishing..."
    result-label="Done!"
    @press="handlePublish"
  >
    Publish
  </AwesomeButtonProgress>
</template>

Text-only progress and custom bar timing

Use :show-progress-bar="false" to keep the loading/result text flow while hiding the dark loading bar. Use progress-loading-time to control how long the progress bar takes to advance during the loading phase.

<script setup lang="ts">
import { AwesomeButtonProgress } from '@rcaferati/vue-awesome-button';
import '@rcaferati/vue-awesome-button/styles.css';

const sleep = (ms: number) => new Promise((resolve) => window.setTimeout(resolve, ms));

async function handleSync(
  _event: MouseEvent | KeyboardEvent | PointerEvent,
  next: (endState?: boolean, errorLabel?: string | null) => void
) {
  await sleep(900);
  next(true);
}
</script>

<template>
  <AwesomeButtonProgress
    type="primary"
    loading-label="Syncing..."
    result-label="Synced!"
    :show-progress-bar="false"
    :progress-loading-time="1500"
    @press="handleSync"
  >
    Sync account
  </AwesomeButtonProgress>
</template>

With icon (before slot)

<script setup lang="ts">
import { AwesomeButtonProgress } from '@rcaferati/vue-awesome-button';
import '@rcaferati/vue-awesome-button/styles.css';
import '@rcaferati/vue-awesome-button/themes/theme-blue.css';

const sleep = (ms: number) => new Promise((resolve) => window.setTimeout(resolve, ms));

async function handleVerify(
  _event: MouseEvent | KeyboardEvent | PointerEvent,
  next: (endState?: boolean, errorLabel?: string | null) => void
) {
  await sleep(900);
  next(true);
}
</script>

<template>
  <AwesomeButtonProgress
    theme="blue"
    type="primary"
    size="medium"
    loading-label="Verifying..."
    result-label="Verified!"
    @press="handleVerify"
  >
    <template #before>
      <svg aria-hidden="true" viewBox="0 0 24 24" width="16" height="16">
        <path d="M12 2l8 4v6c0 5-3.4 8.7-8 10-4.6-1.3-8-5-8-10V6l8-4z" />
      </svg>
    </template>

    Verify signature
  </AwesomeButtonProgress>
</template>

AwesomeButtonProgress specific props

AwesomeButtonProgress accepts all AwesomeButton props except public active, and replaces the base press payload with the progress version below.

PropTypeDefaultDescription
loadingLabelstring"Wait.."Loading phase label
resultLabelstring"Success!"Success label
releaseDelaynumber500Delay before reset after progress end
showProgressBarbooleantrueHides the dark loading bar when false, while keeping the progress flow
progressLoadingTimenumber6000Loading-phase progress bar duration in milliseconds
@press(event, next) => void-Progress handler. Call next(true) or next(false, label?)

AwesomeButtonSocial

AwesomeButtonSocial wraps AwesomeButton and builds a share action when no custom press listener is provided.

Behavior summary

On activation, the component behaves like this:

  1. If you listen to @press, your handler is called and the built-in sharer logic is skipped
  2. If href is provided, the forwarded base anchor still navigates normally
  3. If there is no href and no custom sharer override, mobile-capable environments attempt navigator.share(...)
  4. Otherwise, the built-in sharer uses a type-specific web share URL, direct URL, or centered popup where supported

If you want a pure custom action with no native navigation, avoid combining @press with href.

Basic share example (LinkedIn)

<script setup lang="ts">
import { AwesomeButtonSocial } from '@rcaferati/vue-awesome-button';
import '@rcaferati/vue-awesome-button/styles.css';
import '@rcaferati/vue-awesome-button/themes/theme-blue.css';
</script>

<template>
  <AwesomeButtonSocial
    theme="blue"
    type="linkedin"
    :size="null"
    :sharer="{
      url: 'https://example.com',
      message: 'Check this out'
    }"
  >
    <template #before>
      <svg aria-hidden="true" viewBox="0 0 24 24" width="16" height="16">
        <path d="M4 3.5A2.5 2.5 0 1 1 4 8.5a2.5 2.5 0 0 1 0-5zM2 10h4v11H2V10zm7 0h4v1.6c.6-1 1.9-1.9 3.9-1.9 4.2 0 5 2.8 5 6.4V21h-4v-4.4c0-2.1 0-3.8-2.3-3.8-2.3 0-2.6 1.8-2.6 3.7V21H9V10z" />
      </svg>
    </template>

    LinkedIn
  </AwesomeButtonSocial>
</template>

github is a visual style in the bundled themes. If you want a GitHub-looking button that opens a profile or repo, use href.

<script setup lang="ts">
import { AwesomeButtonSocial } from '@rcaferati/vue-awesome-button';
import '@rcaferati/vue-awesome-button/styles.css';
import '@rcaferati/vue-awesome-button/themes/theme-blue.css';
</script>

<template>
  <AwesomeButtonSocial
    theme="blue"
    type="github"
    href="https://github.com/rcaferati"
    target="_blank"
    rel="noreferrer noopener"
  >
    <template #before>
      <svg aria-hidden="true" viewBox="0 0 24 24" width="16" height="16">
        <path d="M12 .5a12 12 0 0 0-3.8 23.4c.6.1.8-.3.8-.6v-2c-3.3.7-4-1.4-4-1.4-.6-1.4-1.3-1.8-1.3-1.8-1.1-.7.1-.7.1-.7 1.2.1 1.8 1.2 1.8 1.2 1.1 1.8 2.8 1.3 3.5 1 .1-.8.4-1.3.8-1.6-2.7-.3-5.5-1.3-5.5-5.9 0-1.3.5-2.4 1.2-3.2-.1-.3-.5-1.5.1-3.2 0 0 1-.3 3.3 1.2a11.5 11.5 0 0 1 6 0c2.3-1.5 3.3-1.2 3.3-1.2.7 1.7.2 2.9.1 3.2.8.8 1.2 1.9 1.2 3.2 0 4.6-2.8 5.6-5.5 5.9.4.4.8 1.1.8 2.2v3.3c0 .3.2.7.8.6A12 12 0 0 0 12 .5z" />
      </svg>
    </template>

    Open GitHub
  </AwesomeButtonSocial>
</template>

WhatsApp example

<script setup lang="ts">
import { AwesomeButtonSocial } from '@rcaferati/vue-awesome-button';
import '@rcaferati/vue-awesome-button/styles.css';
</script>

<template>
  <AwesomeButtonSocial
    type="whatsapp"
    :sharer="{
      phone: '5511999999999',
      message: 'Hello from AwesomeButton',
      url: 'https://example.com'
    }"
  >
    WhatsApp
  </AwesomeButtonSocial>
</template>

Instagram-style button (direct URL fallback)

instagram is treated as a direct URL fallback. It does not use a dedicated web popup sharer endpoint.

<script setup lang="ts">
import { AwesomeButtonSocial } from '@rcaferati/vue-awesome-button';
import '@rcaferati/vue-awesome-button/styles.css';
</script>

<template>
  <AwesomeButtonSocial
    type="instagram"
    :sharer="{
      url: 'https://example.com',
      message: 'Open this link'
    }"
  >
    Instagram
  </AwesomeButtonSocial>
</template>

href mode (bypass sharer logic)

If href is present, the component behaves like an anchor and does not execute the built-in share flow.

<script setup lang="ts">
import { AwesomeButtonSocial } from '@rcaferati/vue-awesome-button';
import '@rcaferati/vue-awesome-button/styles.css';
</script>

<template>
  <AwesomeButtonSocial
    type="github"
    href="https://github.com/rcaferati"
    target="_blank"
    rel="noreferrer noopener"
  >
    Open GitHub
  </AwesomeButtonSocial>
</template>

AwesomeButtonSocial specific props

AwesomeButtonSocial accepts all AwesomeButton props and adds the props below.

PropTypeDefaultDescription
sharerSharerConfig{}Share payload source, with page metadata fallbacks where available
dimensions{ width: number; height: number }{ width: 640, height: 480 }Popup window dimensions for share flows
sharer.urlstringcurrent page URLURL to share, falling back to window.location.href
sharer.messagestringpage titleShare message/text, falling back to document.title
sharer.imagestringog:image metaImage URL used by supported sharers, such as Pinterest
sharer.phonestringnullPhone for WhatsApp
sharer.userstringnullUsername for Messenger direct flow

Supported share type values

The built-in sharer recognizes:

  • facebook
  • twitter
  • pinterest
  • linkedin
  • reddit
  • whatsapp
  • messenger
  • mail
  • instagram

You can use other type values as visual styles if the CSS includes them. The bundled visual-only social styles include github, gplus, and youtube.


Icon-only buttons

Use a named icon slot and omit default slot text. Add an accessible label through normal Vue attrs.

<AwesomeButton type="primary" aria-label="Play">
  <template #before>
    <svg aria-hidden="true" viewBox="0 0 24 24" width="16" height="16">
      <path d="M8 5v14l11-7z" />
    </svg>
  </template>
</AwesomeButton>

Progress buttons

Use the progress @press contract and call next(true) or next(false, label?) when the work completes.

import type { ProgressNext } from '@rcaferati/vue-awesome-button';

async function save(_event: MouseEvent | KeyboardEvent | PointerEvent, next: ProgressNext) {
  try {
    await saveRecord();
    next(true);
  } catch {
    next(false, 'Failed');
  }
}

Social buttons

Use slots for icons. The social component does not require bundled icons to work.


Package Exports

  • @rcaferati/vue-awesome-button
  • @rcaferati/vue-awesome-button/AwesomeButton
  • @rcaferati/vue-awesome-button/AwesomeButtonProgress
  • @rcaferati/vue-awesome-button/AwesomeButtonSocial
  • @rcaferati/vue-awesome-button/plugin
  • @rcaferati/vue-awesome-button/styles.css
  • @rcaferati/vue-awesome-button/themes/theme-amber.css
  • @rcaferati/vue-awesome-button/themes/theme-blue.css
  • @rcaferati/vue-awesome-button/themes/theme-bojack.css
  • @rcaferati/vue-awesome-button/themes/theme-bruce.css
  • @rcaferati/vue-awesome-button/themes/theme-c137.css
  • @rcaferati/vue-awesome-button/themes/theme-eric.css
  • @rcaferati/vue-awesome-button/themes/theme-flat.css
  • @rcaferati/vue-awesome-button/themes/theme-indigo.css
  • @rcaferati/vue-awesome-button/themes/theme-red.css
  • @rcaferati/vue-awesome-button/themes/theme-rickiest.css

Author

Rafael Caferati
Website: https://caferati.dev
LinkedIn: https://linkedin.com/in/rcaferati
Instagram: https://instagram.com/rcaferati


License

MIT