React UI Components

April 20, 2026 ยท View on GitHub

@rcaferati/react-awesome-button provides three related React 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 8.2.0 release outputs, including base CSS, bundled theme CSS files, theme mapping modules, animated size transitions, and auto-width text choreography.


Preview

Awesome Button visual preview


Installation

npm install @rcaferati/react-awesome-button

Quick Start

Import the base stylesheet once, then render a button. This is the plain CSS path for buttons that do not pass a cssModule theme mapping.

import React from 'react';
import { AwesomeButton } from '@rcaferati/react-awesome-button';
import '@rcaferati/react-awesome-button/styles.css';

export default function Example() {
  return (
    <AwesomeButton type="primary" size="medium">
      Button
    </AwesomeButton>
  );
}

Styling With Theme CSS Modules

The React package uses two theme imports together:

  1. the theme CSS file, which contains the actual rules
  2. the theme mapping object, which is passed as cssModule

Blue theme example

import React from 'react';
import { AwesomeButton } from '@rcaferati/react-awesome-button';
import '@rcaferati/react-awesome-button/themes/theme-blue.css';
import themeBlue from '@rcaferati/react-awesome-button/themes/theme-blue';

export default function Example() {
  return (
    <AwesomeButton cssModule={themeBlue} type="primary" size="medium">
      Button
    </AwesomeButton>
  );
}

Plain and themed buttons in the same app

If you mix plain buttons and themed buttons, import both:

import '@rcaferati/react-awesome-button/styles.css';
import '@rcaferati/react-awesome-button/themes/theme-blue.css';

Bundled themes

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

AwesomeButton

Basic button behavior

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

import React from 'react';
import { AwesomeButton } from '@rcaferati/react-awesome-button';
import '@rcaferati/react-awesome-button/themes/theme-blue.css';
import themeBlue from '@rcaferati/react-awesome-button/themes/theme-blue';

export default function Example() {
  return (
    <AwesomeButton
      cssModule={themeBlue}
      type="primary"
      size="medium"
      onPress={() => {
        // do something
      }}>
      Save changes
    </AwesomeButton>
  );
}

Anchor mode (href provided)

If href is provided, the component renders anchor-like behavior and lets native navigation happen. Use containerProps for forwarded root element props such as target, rel, and aria-*.

import React from 'react';
import { AwesomeButton } from '@rcaferati/react-awesome-button';
import '@rcaferati/react-awesome-button/themes/theme-blue.css';
import themeBlue from '@rcaferati/react-awesome-button/themes/theme-blue';

export default function Example() {
  return (
    <AwesomeButton
      cssModule={themeBlue}
      type="link"
      href="https://github.com/rcaferati"
      containerProps={{ target: '_blank', rel: 'noreferrer noopener' }}>
      Open website
    </AwesomeButton>
  );
}

Icons

Use before and after to render icons. For icon-only buttons, pass the icon with before and omit children.

import React from 'react';
import { AwesomeButton } from '@rcaferati/react-awesome-button';
import '@rcaferati/react-awesome-button/themes/theme-blue.css';
import themeBlue from '@rcaferati/react-awesome-button/themes/theme-blue';

function StarIcon() {
  return (
    <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>
  );
}

function ArrowRightIcon() {
  return (
    <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>
  );
}

function PlayIcon() {
  return (
    <svg aria-hidden="true" viewBox="0 0 24 24" width="16" height="16">
      <path d="M8 5v14l11-7z" />
    </svg>
  );
}

export default function Example() {
  return (
    <div style={{ display: 'grid', gap: 12 }}>
      <AwesomeButton
        cssModule={themeBlue}
        type="primary"
        size="medium"
        before={<StarIcon />}
        after={<ArrowRightIcon />}>
        Continue
      </AwesomeButton>

      <AwesomeButton
        cssModule={themeBlue}
        type="primary"
        size="medium"
        before={<PlayIcon />}
        containerProps={{ 'aria-label': 'Play' }}
      />
    </div>
  );
}

Sizes and auto width

  • size="small" | "medium" | "large" uses fixed button dimensions
  • size={null} uses content-driven auto width
  • fixed-size changes animate by default
  • measured auto-width changes animate by default
  • animateSize={false} opts out of both fixed-size and measured auto-width animation
  • fixed-to-auto and auto-to-fixed transitions are intentionally instant in this release

Size animation timing is 125ms cubic-bezier(0.3, 0.05, 0.2, 1).

import React from 'react';
import { AwesomeButton } from '@rcaferati/react-awesome-button';
import '@rcaferati/react-awesome-button/styles.css';

export default function Example() {
  return (
    <div style={{ display: 'grid', gap: 12 }}>
      <AwesomeButton type="primary" size="small">
        Small
      </AwesomeButton>
      <AwesomeButton type="primary" size="medium">
        Medium
      </AwesomeButton>
      <AwesomeButton type="primary" size="large">
        Large
      </AwesomeButton>
      <AwesomeButton type="primary" size={null}>
        Auto width grows with content
      </AwesomeButton>
      <AwesomeButton type="primary" size="large" animateSize={false}>
        Instant fixed-size change
      </AwesomeButton>
    </div>
  );
}

Text transition (string labels only)

Use textTransition when the button label is a plain string and you want label changes to animate.

When combined with size={null}, the current React behavior is:

  • larger labels grow width first, then animate the text
  • smaller labels animate the text first, then shrink the width

For the parity flow documented in the current stories, drive the label change from an external rerender rather than mutating the same button label from its own onPress.

import React from 'react';
import { AwesomeButton } from '@rcaferati/react-awesome-button';
import '@rcaferati/react-awesome-button/styles.css';

export default function Example() {
  const [expanded, setExpanded] = React.useState(false);
  const label = expanded ? 'Open analytics dashboard' : 'Open';

  return (
    <div style={{ display: 'grid', gap: 12 }}>
      <AwesomeButton type="primary" size={null} textTransition>
        {label}
      </AwesomeButton>

      <button type="button" onClick={() => setExpanded((value) => !value)}>
        Toggle label length
      </button>
    </div>
  );
}

AwesomeButton Props

PropTypeDefaultDescription
typestring"primary"Visual variant key, such as primary, secondary, danger, link, or a social visual variant
size"small" | "medium" | "large" | nullnullSize key or null for auto width
activebooleanfalseControlled pressed/active visual state
disabledbooleanfalseDisables interactions
visiblebooleantrueToggles visible state class
placeholderbooleantrueIf true and children is empty, renders placeholder/disabled state
animateSizebooleantrueAnimates fixed-size changes and measured auto-width changes
textTransitionbooleanfalseAnimates string-only label changes with a scrambling transition
betweenbooleanfalseUses space-between layout for content
ripplebooleanfalseEnables ripple effect on successful activation
moveEventsbooleantrueEnables pointer move position classes (left / middle / right)
hrefstring | nullnullEnables anchor-like mode
elementReact.ElementType | nullnullCustom forwarded root element component
rootElementstring"aws-btn"Base class prefix
classNamestring | nullnullExtra class names
styleReact.CSSProperties{}Inline styles for the root element
containerPropsobject{}Additional props forwarded to the root element
cssModuleRecord<string, string> | nullnullCSS module mapping object for themed usage
beforeReact.ReactNodenullContent rendered before the label
afterReact.ReactNodenullContent rendered after the label
extraReact.ReactNodenullExtra content rendered inside the wrapper
childrenReact.ReactNodenullMain label/content
onPress(event) => voidnullCalled on successful activation/release
onPressed(event) => voidnullCalled when the press-in transition completes
onReleased(element: HTMLElement) => voidnullCalled when the release cycle clears
onMouseDown(event) => voidnullPointer/mouse/touch down callback
onMouseUp(event) => voidnullPointer/mouse/touch up callback

AwesomeButtonProgress

AwesomeButtonProgress wraps AwesomeButton and manages a guarded progress lifecycle. Its onPress 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

import React from 'react';
import { AwesomeButtonProgress } from '@rcaferati/react-awesome-button';
import '@rcaferati/react-awesome-button/themes/theme-blue.css';
import themeBlue from '@rcaferati/react-awesome-button/themes/theme-blue';

const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));

export default function Example() {
  return (
    <AwesomeButtonProgress
      cssModule={themeBlue}
      type="primary"
      loadingLabel="Verifying..."
      resultLabel="Verified!"
      releaseDelay={500}
      onPress={async (_event, next) => {
        await sleep(900);
        next(true);
      }}>
      Verify
    </AwesomeButtonProgress>
  );
}

Error flow

import React from 'react';
import { AwesomeButtonProgress } from '@rcaferati/react-awesome-button';
import '@rcaferati/react-awesome-button/styles.css';

const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));

export default function Example() {
  return (
    <AwesomeButtonProgress
      type="danger"
      loadingLabel="Publishing..."
      resultLabel="Done!"
      onPress={async (_event, next) => {
        await sleep(900);
        next(false, 'Failed');
      }}>
      Publish
    </AwesomeButtonProgress>
  );
}

Text-only progress and custom bar timing

Use showProgressBar={false} to keep the loading/result text flow while hiding the dark loading bar. Use progressLoadingTime to control how long the progress bar takes to advance during the loading phase.

import React from 'react';
import { AwesomeButtonProgress } from '@rcaferati/react-awesome-button';
import '@rcaferati/react-awesome-button/styles.css';

const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));

export default function Example() {
  return (
    <AwesomeButtonProgress
      type="primary"
      loadingLabel="Syncing..."
      resultLabel="Synced!"
      showProgressBar={false}
      progressLoadingTime={1500}
      onPress={async (_event, next) => {
        await sleep(900);
        next(true);
      }}>
      Sync account
    </AwesomeButtonProgress>
  );
}

With icon (before)

import React from 'react';
import { AwesomeButtonProgress } from '@rcaferati/react-awesome-button';
import '@rcaferati/react-awesome-button/themes/theme-blue.css';
import themeBlue from '@rcaferati/react-awesome-button/themes/theme-blue';

function ShieldIcon() {
  return (
    <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>
  );
}

const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));

export default function Example() {
  return (
    <AwesomeButtonProgress
      cssModule={themeBlue}
      type="primary"
      size="medium"
      before={<ShieldIcon />}
      loadingLabel="Verifying..."
      resultLabel="Verified!"
      onPress={async (_event, next) => {
        await sleep(900);
        next(true);
      }}>
      Verify signature
    </AwesomeButtonProgress>
  );
}

AwesomeButtonProgress specific props

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

PropTypeDefaultDescription
onPress(event, next) => voidnullProgress handler. Call next(true) or next(false, label?)
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

AwesomeButtonSocial

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

Behavior summary

On activation, the current React behavior is:

  1. if you provide onPress, your handler is called and the built-in sharer logic is skipped
  2. if href is also present, native anchor navigation still happens because href is forwarded to the base button
  3. if there is no href, 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 onPress and href.

Basic share example (LinkedIn)

import React from 'react';
import { AwesomeButtonSocial } from '@rcaferati/react-awesome-button';
import '@rcaferati/react-awesome-button/themes/theme-blue.css';
import themeBlue from '@rcaferati/react-awesome-button/themes/theme-blue';

function LinkedinIcon() {
  return (
    <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>
  );
}

export default function Example() {
  return (
    <AwesomeButtonSocial
      cssModule={themeBlue}
      type="linkedin"
      before={<LinkedinIcon />}
      sharer={{
        url: 'https://example.com',
        message: 'Check this out',
      }}>
      LinkedIn
    </AwesomeButtonSocial>
  );
}

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

import React from 'react';
import { AwesomeButtonSocial } from '@rcaferati/react-awesome-button';
import '@rcaferati/react-awesome-button/themes/theme-blue.css';
import themeBlue from '@rcaferati/react-awesome-button/themes/theme-blue';

function GithubIcon() {
  return (
    <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>
  );
}

export default function Example() {
  return (
    <AwesomeButtonSocial
      cssModule={themeBlue}
      type="github"
      href="https://github.com/rcaferati"
      containerProps={{ target: '_blank', rel: 'noreferrer noopener' }}
      before={<GithubIcon />}>
      Open GitHub
    </AwesomeButtonSocial>
  );
}

WhatsApp example

import React from 'react';
import { AwesomeButtonSocial } from '@rcaferati/react-awesome-button';
import '@rcaferati/react-awesome-button/styles.css';

export default function Example() {
  return (
    <AwesomeButtonSocial
      type="whatsapp"
      sharer={{
        phone: '5511999999999',
        message: 'Hello from AwesomeButton',
        url: 'https://example.com',
      }}>
      WhatsApp
    </AwesomeButtonSocial>
  );
}

Instagram-style button (direct URL fallback)

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

import React from 'react';
import { AwesomeButtonSocial } from '@rcaferati/react-awesome-button';
import '@rcaferati/react-awesome-button/styles.css';

export default function Example() {
  return (
    <AwesomeButtonSocial
      type="instagram"
      sharer={{
        url: 'https://example.com',
        message: 'Open this link',
      }}>
      Instagram
    </AwesomeButtonSocial>
  );
}

href mode (bypass built-in sharer logic)

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

import React from 'react';
import { AwesomeButtonSocial } from '@rcaferati/react-awesome-button';
import '@rcaferati/react-awesome-button/styles.css';

export default function Example() {
  return (
    <AwesomeButtonSocial
      type="github"
      href="https://github.com/rcaferati"
      containerProps={{ target: '_blank', rel: 'noreferrer noopener' }}>
      Open GitHub
    </AwesomeButtonSocial>
  );
}

AwesomeButtonSocial specific props

AwesomeButtonSocial accepts all AwesomeButton props and adds the props below.

PropTypeDefaultDescription
sharerobject{}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 the document title
sharer.imagestringog:image metaImage URL used by supported sharers such as Pinterest
sharer.phonestringnullPhone number for WhatsApp
sharer.userstringnullUsername for Messenger direct flow

Supported sharer type values

The built-in sharer recognizes:

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

You can still use other type values as visual styles if your theme includes them, but the built-in sharer URLs do not exist for unsupported types.


Icon-only buttons

Use before and omit children:

<AwesomeButton before={<svg aria-hidden="true" viewBox="0 0 24 24" width="16" height="16"><path d="M8 5v14l11-7z" /></svg>} />

Auto-width label choreography

For size={null} + textTransition, prefer an external rerender-driven label change:

const label = expanded ? 'Open analytics dashboard' : 'Open';
<AwesomeButton size={null} textTransition>{label}</AwesomeButton>

Progress buttons

Use:

onPress={async (_event, next) => {
  // do work
  next(true);
}}

Do not try to control the progress state manually through active.

Social buttons

Use before for icons, href for visual-only social styles like github, and avoid combining onPress with href unless you explicitly want native navigation to continue.


Package Exports

Root package

import {
  AwesomeButton,
  AwesomeButtonProgress,
  AwesomeButtonSocial,
} from '@rcaferati/react-awesome-button';

Component subpaths

import AwesomeButton from '@rcaferati/react-awesome-button/AwesomeButton';
import AwesomeButtonProgress from '@rcaferati/react-awesome-button/AwesomeButtonProgress';
import AwesomeButtonSocial from '@rcaferati/react-awesome-button/AwesomeButtonSocial';

Base stylesheet

import '@rcaferati/react-awesome-button/styles.css';

Theme CSS files

import '@rcaferati/react-awesome-button/themes/theme-blue.css';

Theme mapping modules

import themeBlue from '@rcaferati/react-awesome-button/themes/theme-blue';

Published subpaths

  • @rcaferati/react-awesome-button
  • @rcaferati/react-awesome-button/AwesomeButton
  • @rcaferati/react-awesome-button/AwesomeButtonProgress
  • @rcaferati/react-awesome-button/AwesomeButtonSocial
  • @rcaferati/react-awesome-button/styles.css
  • @rcaferati/react-awesome-button/themes/theme-*.css
  • @rcaferati/react-awesome-button/themes/theme-*
  • @rcaferati/react-awesome-button/package.json

Author

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


License

MIT