JGuang

Button

Button · Astryx

Preview
npm install @astryxdesign/core@0.7.1 @astryxdesign/theme-neutral@0.7.1 @stylexjs/stylex@0.19.0

Button

Button triggers an action when clicked. Use it for form submissions, confirmations, navigation, or any interaction that needs a clear call to action.

Installation

npm install @astryxdesign/core@0.7.1 @astryxdesign/theme-neutral@0.7.1 @stylexjs/stylex@0.19.0

Requires React 19+, base CSS and a Theme provider. See Setup.

Button

Button triggers an action when clicked. Use it for form submissions, confirmations, navigation, or any interaction that needs a clear call to action.

PropTypeDefaultDescription
label *stringAccessible label. Rendered as visible text by default; used as aria-label when isIconOnly is true.
variant'primary' | 'secondary' | 'ghost' | 'destructive''secondary'Visual style variant.
size'sm' | 'md' | 'lg''md'Size variant.
elevation'none' | 'low' | 'med' | 'high''none'Resting shadow depth for floating buttons (e.g. a FAB). none is the default flat button; low/med/high map to the shadow token scale. Ignored inside a ButtonGroup, where elevation is owned by the group.
type'button' | 'submit' | 'reset''button'HTML button type attribute.
namestringHTML name attribute for form submission.
valuestring | number | readonly string[]HTML value attribute for form submission.
formstringAssociates the button with a form element by ID.
isLoadingbooleanfalseShows a loading spinner and disables interaction. Announces "Loading" via a live region.
isInterruptiblebooleanfalseKeep the button clickable while a clickAction is pending: the spinner and aria-busy still show, but the button is not disabled and the action is not deduped, so a re-click lands and interrupts the in-flight action with a fresh one.
isDisabledbooleanfalseDisables the button. When a tooltip is present, uses aria-disabled instead of native disabled so the button stays focusable.
iconReactNodeIcon element rendered before the label text. An Astryx Icon with no explicit size defaults to sm for sm/md buttons and md for lg buttons.
isIconOnlybooleanfalseWhen true, renders as a square icon-only button with label as aria-label. Requires icon. Tip: for a dedicated icon-only button component, use IconButton from '@astryxdesign/core/IconButton' instead.
widthSizeValueWidth of the button. Numbers are treated as pixels, strings are used as-is (e.g., '100%' for a full-width button). By default the button sizes to its content, never grows past its container, and truncates its label with an ellipsis when a row is too narrow; icon-only buttons stay square.
childrenReactNodeOptional override for visible text. When provided, displayed instead of label, but label is still required (it provides the accessible name). For most cases, just use label alone: <Button label="Save" />.
endContentReactElement<IconProps> | ReactElement<BadgeProps>Trailing icon or badge rendered after the label. Ignored when isIconOnly is true. Color is inherited from the button variant.
tooltipstringTooltip text shown on hover.
onClick(e: MouseEvent) => voidStandard click handler (passed through from ButtonHTMLAttributes).
clickAction(e: MouseEvent) => void | Promise<void>Async click handler. Shows loading state while the returned promise is pending.
hrefstringWhen provided, renders the button as a link element (<a> or custom link component). The destination follows the shared navigation rule described on the Link href prop.
asComponentTypeCustom link component to use when href is provided (e.g. Next.js Link).
targetstringHTML target attribute when rendered as a link (e.g. "_blank").
relstringHTML rel attribute when rendered as a link (e.g. "noopener noreferrer").

Usage guidance

  • Reserve primary for the single most important action in the view. Use secondary or ghost for everything else based on emphasis.
  • Write labels that describe the action ("Save changes", "Delete account", "Send invite"), not vague labels like "OK" or "Click here".
  • Show a loading state for actions that take time, like saving or submitting, so the user knows it is working.
  • Always provide a label for icon-only buttons so screen readers can announce what the button does. Add a tooltip for sighted users.
  • For a dedicated icon-only button, use IconButton from '@astryxdesign/core/IconButton'. It is a separate component, not exported from '@astryxdesign/core/Button'.
  • Avoid: Place more than one primary button in the same view; this dilutes the visual hierarchy.
  • Avoid: Use the destructive variant without a confirmation step for irreversible actions like deleting data.
  • Avoid: Use a button for navigation. If it only takes the user to another page, use a link instead. Buttons are for actions like saving, deleting, or submitting.

Actions in narrow rows

import {useState} from 'react';
import {Button} from '@astryxdesign/core/Button';
import {Card} from '@astryxdesign/core/Card';
import {Heading} from '@astryxdesign/core/Heading';
import {HStack} from '@astryxdesign/core/HStack';
import {Icon} from '@astryxdesign/core/Icon';
import {IconButton} from '@astryxdesign/core/IconButton';
import {StackItem} from '@astryxdesign/core/Stack';
import {TextInput} from '@astryxdesign/core/TextInput';
import {VStack} from '@astryxdesign/core/VStack';

// On a phone (or any card narrower than its actions), a labelled Button
// shrinks with its row and truncates its label with an ellipsis instead of
// overflowing or being clipped by the card. The full label stays the
// accessible name. Icon-only buttons keep their square size.
function ReviewersCard() {
  const [emails, setEmails] = useState('');
  return (
    <Card maxWidth={560}>
      <VStack gap={4}>
        <HStack gap={2} vAlign="center">
          <StackItem size="fill">
            <Heading level={3} maxLines={1}>
              Design review
            </Heading>
          </StackItem>
          <Button label="Request review from the design team" size="sm" />
          <IconButton
            label="More review options"
            icon={<Icon icon="moreHorizontal" />}
            variant="ghost"
            size="sm"
          />
        </HStack>
        <TextInput
          label="Reviewer emails"
          placeholder="name@example.com"
          value={emails}
          onChange={setEmails}
        />
        {/* In a 320px form footer the long primary action truncates.
            StackItem size="static" keeps the short Cancel label whole. */}
        <HStack gap={2} hAlign="end">
          <StackItem size="static">
            <Button label="Cancel" variant="secondary" />
          </StackItem>
          <Button label="Send invitations to all 12 reviewers" variant="primary" />
        </HStack>
      </VStack>
    </Card>
  );
}

Theming API

{
  "targets": [
    {
      "className": "astryx-button",
      "visualProps": [
        "size",
        "variant",
        "elevation"
      ]
    }
  ],
  "vars": [
    {
      "name": "--_button-radius",
      "description": "Border radius",
      "default": "var(--radius-element)",
      "private": true
    },
    {
      "name": "--button-focus-offset",
      "description": "Focus ring outline offset",
      "default": "var(--focus-outline-offset)"
    },
    {
      "name": "--button-icon-only-aspect",
      "description": "Aspect ratio for icon-only buttons",
      "default": "1 / 1"
    }
  ],
  "derived": [
    {
      "property": "borderRadius",
      "vars": [
        "--_button-radius"
      ]
    }
  ]
}

Accessibility

  • Text label: Button text must have at least 4.5:1 contrast with the button background in every state. For Hover and Pointer down, measure the final background after the overlay is applied. — 4.5:1
  • Essential icon or spinner arc: An icon used instead of text must have at least 3:1 contrast with the button background. The moving spinner arc must also meet 3:1. An icon beside a visible label does not need its own check. — 3:1
  • Badge text: Badge text inside a button must have at least 4.5:1 contrast with the Badge background. Check all 14 built-in Badge colors in Rest, Hover, and Pointer down on page and surface backgrounds. This covers 336 pairs per mode. Check custom end content separately. — 4.5:1
  • Visible control boundary: The button edge needs 3:1 contrast only when users need it to see the control. A text-only button can rely on its label. — 3:1 if needed
  • Keyboard focus indicator: The focus outline needs at least 3:1 contrast with the area around the button. Check every style. Destructive buttons use a red outline. — 3:1
  • Disabled appearance: Disabled controls do not need to meet these contrast ratios. — Not required

Anatomy

  • Icon: A leading icon that reinforces the label, like a trash icon on a Delete button.
  • Label: The visible text describing the action. Also used as the accessible name.
  • End content: A trailing badge or icon after the label, like a notification count or dropdown arrow.
  • Spinner: Replaces the icon during loading to show the action is in progress.

Examples

  • Button — Variants: All four button variants side by side: primary, secondary, ghost, and destructive. A quick visual reference for choosing the right variant.
  • Button — Floating: A floating action button raised with elevation="med". Use elevation for buttons that hover above content, like a FAB.
  • Button — Sizes: Small, medium, and large buttons side by side. Use small in dense UIs like toolbars, medium for most cases, and large for prominent CTAs.
  • Button — Variants: All 4 button variants in default, disabled, and loading states. Use primary for the main action, secondary for most others, ghost for low-emphasis, and destructive for dangerous actions.
  • Button — End Slot: Buttons with a trailing badge showing a count or status. Use for notification counts, unread messages, or any button that needs a visual indicator.
  • Button — Icon: Buttons with a leading icon that reinforces the label. Use when the icon helps the user identify the action faster, like a plus for "New" or a trash can for "Delete".

Complete example dependencies

Some examples import optional icons. The per-example install command in the examples list identifies exactly what that example needs. For all examples on this page:

npm install @heroicons/react@2.2.0