Skip to main content

Button

Stable

Triggers actions or submits forms. Six variants, four sizes, loading state, icon support, and asChild for link rendering — all built in.

InteractiveForm elementVhyxSeal

Interactive example

Click to trigger loading state

Import

tsx
import { Button } from '@vhyxui/react'

Variants

Six semantic variants covering every use case.

All 6 variants

Sizes

Four sizes mapping to --vhyx-size-* height tokens.

xs, sm, md, lg

States

Loading
Disabled
Icon + text
Icon only — requires aria-label
asChild — renders as anchor

Props

PropTypeDefaultDescription
variant'primary' | 'secondary' | 'outline' | 'ghost' | 'destructive' | 'link''primary'Visual style of the button.
size'xs' | 'sm' | 'md' | 'lg''md'Size of the button, maps to --vhyx-size-* height tokens.
loadingbooleanfalseWhen true, shows a spinner and disables interaction.
iconReact.ReactNodeOptional icon element rendered alongside button text.
iconPosition'left' | 'right''left'Side the icon appears on.
iconOnlybooleanfalseRenders icon only. Requires aria-label.
asChildbooleanfalseRenders as the child element via Slot. Use for navigation links.
contractPartial<ComponentContract>VhyxSeal contract override — merged with the default.
disabledbooleanDisables the button (from HTMLButtonElement).
onClickReact.MouseEventHandler<HTMLButtonElement>Click handler (from HTMLButtonElement).
classNamestringAdditional CSS classes appended to the button.

Button also accepts all standard HTMLButtonElement attributes.

Accessibility

  • Renders as native <button> — no role override needed.
  • aria-busy set automatically when loading=true.
  • Focus ring via :focus-visible — visible on keyboard, hidden on mouse.
  • iconOnly warns in development if aria-label is missing.
  • asChild prevents nested interactive elements — correct DOM structure.
  • disabled attribute propagated — no separate aria-disabled needed.

Keyboard navigation

KeyAction
EnterorSpaceActivates the button (triggers onClick).
TabMoves focus to the next focusable element.
Shift + TabMoves focus to the previous focusable element.

Agent contract

Default VhyxSeal contract shipped with every Button. The destructive variant auto-upgrades safetyLevel, destructive, and requiresConfirmation.

Default contract
{
  "type": "action",
  "intent": "trigger-action",
  "description": "Triggers an action or submits a form when activated by the user",
  "requires": [],
  "requiredPermissions": [],
  "consequence": "Triggers the associated action",
  "affects": [],
  "reversible": false,
  "safetyLevel": "low",
  "requiresConfirmation": false,
  "destructive": false,
  "contractVersion": "0.0.1",
  "fingerprint": "vhyxs_6ecebcad"
}

Override fields via the contract prop — merged with the default:

tsx
<Button contract={{ intent: 'submit-order', requiresConfirmation: true }}>
  Place Order
</Button>

Theming

Override these CSS tokens to theme the Button without touching component code.

--vhyx-color-accentPrimary variant background
--vhyx-color-accent-hoverPrimary hover state
--vhyx-color-accent-activePrimary active state
--vhyx-color-dangerDestructive variant background
--vhyx-color-danger-hoverDestructive hover state
--vhyx-size-xs/sm/md/lgButton height per size
--vhyx-radius-mdBorder radius
--vhyx-shadow-focusFocus ring
--vhyx-duration-instantActive press scale transition
--vhyx-weight-mediumFont weight
css
:root {
  --vhyx-color-accent: #7c3aed;        /* purple primary buttons */
  --vhyx-color-accent-hover: #6d28d9;
  --vhyx-radius-md: 9999px;            /* pill-shaped buttons */
}

Examples

Form submit with loading state

Controlled loading — typical form submission pattern

Confirm / Cancel action group

Paired primary + outline

Destructive action

Delete pattern — contract auto-upgrades to safetyLevel: high