XDSButton@xds/core · Button
Preview coming soon
Usage
Button triggers an action when clicked. Use it for form submissions, confirmations, navigation, or any interaction that needs a clear call to action.Best practices
| Guidance | Practices |
|---|---|
| Do | Reserve primary for the single most important action in the view. Use secondary or ghost for everything else based on emphasis. |
| Do | Write labels that describe the action — "Save changes", "Delete account", "Send invite" — not vague labels like "OK" or "Click here". |
| Do | Show a loading state for actions that take time, like saving or submitting, so the user knows it is working. |
| Do | Always provide a label for icon-only buttons so screen readers can announce what the button does. Add a tooltip for sighted users. |
| Don't | Place more than one primary button in the same view — this dilutes the visual hierarchy. |
| Don't | Use the destructive variant without a confirmation step for irreversible actions like deleting data. |
| Don't | 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. |
Anatomy
| Element | Description | |
|---|---|---|
| Icon | A leading icon that reinforces the label, like a trash icon on a Delete button. | |
| Label | required | 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. |
Import
tsimport {XDSButton} from '@xds/core/Button'
Props
| Prop | Type | Description |
|---|---|---|
labelrequired | string | Accessible label. Rendered as visible text by default; used as aria-label when isIconOnly is true. |
variant | 'primary' | 'secondary' | 'ghost' | 'destructive' (default: 'secondary') | Visual style variant. |
size | 'sm' | 'md' | 'lg' (default: 'md') | Size variant. |
type | 'button' | 'submit' | 'reset' (default: 'button') | HTML button type attribute. |
name | string | HTML name attribute for form submission. |
value | string | number | readonly string[] | HTML value attribute for form submission. |
form | string | Associates the button with a form element by ID. |
isLoading | boolean (default: false) | Shows a loading spinner and disables interaction. Announces "Loading" via a live region. |
isDisabled | boolean (default: false) | Disables the button. When a tooltip is present, uses aria-disabled instead of native disabled so the button stays focusable. |
icon | ReactNode | Icon element rendered before the label text. |
isIconOnly | boolean (default: false) | When true, renders as a square icon-only button with label as aria-label. Requires icon. |
children | ReactNode | Optional visible content. When provided, rendered instead of label as the visible text. |
endContent | ReactElement<XDSIconProps> | ReactElement<XDSBadgeProps> | Trailing icon or badge rendered after the label. Ignored when isIconOnly is true. Color is inherited from the button variant. |
tooltip | string | Tooltip text shown on hover. |
onClick | (e: MouseEvent) => void | Standard click handler (passed through from ButtonHTMLAttributes). |
clickAction | (e: MouseEvent) => void | Promise<void> | Async click handler. Shows loading state while the returned promise is pending. |
Examples
Common configurations, variations, and states.Button — End SlotButtons with a trailing badge showing a count or status. Use for notification counts, unread messages, or any button that needs a visual indicator.
tsx'use client';import {XDSButton} from '@xds/core/Button';import {XDSBadge} from '@xds/core/Badge';import {XDSStack} from '@xds/core/Layout';import {XDSText} from '@xds/core/Text';export default function ButtonWithEndSlot() {return (<XDSStack direction="vertical" gap={4}><XDSText type="supporting" color="secondary">Trailing badges for counts or status</XDSText><XDSStack direction="horizontal" gap={3} vAlign="center"><XDSButtonlabel="Messages"variant="primary"endContent={<XDSBadge variant="info" label={3} />}/><XDSButtonlabel="Notifications"variant="secondary"endContent={<XDSBadge variant="warning" label={12} />}/><XDSButtonlabel="Updates"variant="ghost"endContent={<XDSBadge variant="neutral" label="New" />}/></XDSStack></XDSStack>);}
Button — IconButtons with a leading icon that reinforces the label. Use when the icon helps the user identify the action faster, like a plus for
tsx'use client';import {XDSButton} from '@xds/core/Button';import {XDSIcon} from '@xds/core/Icon';import {XDSStack} from '@xds/core/Layout';import {XDSText} from '@xds/core/Text';import {ArrowDownTrayIcon,PencilSquareIcon,PlusIcon,TrashIcon,} from '@heroicons/react/24/outline';export default function ButtonWithIcon() {return (<XDSStack direction="vertical" gap={4}><XDSText type="supporting" color="secondary">Icons reinforce the action</XDSText><XDSStack direction="horizontal" gap={3} vAlign="center"><XDSButtonlabel="New item"variant="primary"icon={<XDSIcon icon={PlusIcon} />}/><XDSButtonlabel="Edit"variant="secondary"icon={<XDSIcon icon={PencilSquareIcon} />}/><XDSButtonlabel="Download"variant="ghost"icon={<XDSIcon icon={ArrowDownTrayIcon} />}/><XDSButtonlabel="Delete"variant="destructive"icon={<XDSIcon icon={TrashIcon} />}/></XDSStack></XDSStack>);}
Button — SizesSmall, medium, and large buttons side by side. Use small in dense UIs like toolbars, medium for most cases, and large for prominent CTAs.
tsx'use client';import {XDSButton} from '@xds/core/Button';import {XDSStack} from '@xds/core/Layout';import {XDSText} from '@xds/core/Text';const SIZES = [{size: 'sm' as const, label: 'Small'},{size: 'md' as const, label: 'Medium'},{size: 'lg' as const, label: 'Large'},];export default function ButtonSizeVariants() {return (<XDSStack direction="vertical" gap={4}><XDSStack direction="vertical" gap={1}><XDSText type="supporting" color="secondary">Primary</XDSText><XDSStack direction="horizontal" gap={3} vAlign="center">{SIZES.map(({size, label}) => (<XDSButton key={size} label={label} variant="primary" size={size} />))}</XDSStack></XDSStack><XDSStack direction="vertical" gap={1}><XDSText type="supporting" color="secondary">Secondary</XDSText><XDSStack direction="horizontal" gap={3} vAlign="center">{SIZES.map(({size, label}) => (<XDSButtonkey={size}label={label}variant="secondary"size={size}/>))}</XDSStack></XDSStack></XDSStack>);}
Button — VariantsAll 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.
tsx'use client';import {XDSButton} from '@xds/core/Button';import {XDSStack} from '@xds/core/Layout';import {XDSText} from '@xds/core/Text';const VARIANTS = [{variant: 'primary' as const, label: 'Primary'},{variant: 'secondary' as const, label: 'Secondary'},{variant: 'ghost' as const, label: 'Ghost'},{variant: 'destructive' as const, label: 'Destructive'},];export default function ButtonVariants() {return (<XDSStack direction="vertical" gap={4}><XDSStack direction="vertical" gap={1}><XDSText type="supporting" color="secondary">Default</XDSText><XDSStack direction="horizontal" gap={3} vAlign="center">{VARIANTS.map(({variant, label}) => (<XDSButton key={variant} label={label} variant={variant} />))}</XDSStack></XDSStack><XDSStack direction="vertical" gap={1}><XDSText type="supporting" color="secondary">Disabled</XDSText><XDSStack direction="horizontal" gap={3} vAlign="center">{VARIANTS.map(({variant, label}) => (<XDSButtonkey={variant}label={label}variant={variant}isDisabled/>))}</XDSStack></XDSStack><XDSStack direction="vertical" gap={1}><XDSText type="supporting" color="secondary">Loading</XDSText><XDSStack direction="horizontal" gap={3} vAlign="center">{VARIANTS.map(({variant, label}) => (<XDSButtonkey={variant}label={label}variant={variant}isLoading/>))}</XDSStack></XDSStack></XDSStack>);}
Showcase source
tsx'use client';import {XDSButton} from '@xds/core/Button';import {XDSStack} from '@xds/core/Layout';export default function ButtonShowcase() {return (<XDSStack direction="horizontal" gap={3} vAlign="center"><XDSButton label="Primary" variant="primary" /><XDSButton label="Secondary" variant="secondary" /><XDSButton label="Ghost" variant="ghost" /><XDSButton label="Destructive" variant="destructive" /></XDSStack>);}