Logo VitNode
Documentation

Button

Buttons for every kind of action, with variants by weight, sizes, a loading state and disabled tooltips.

Preview

Usage

import { Button } from '@vitnode/core/components/ui/button'
<Button>
  <SendIcon />
  Post reply
</Button>

A press dips the button to active:scale-97, so every click feels acknowledged. Inside a Button Group it darkens instead, and menu triggers stay put.

Variants

Pick the variant by the weight of the action, not your favourite colour:

  • default - the one primary action on the screen.
  • secondary, outline, ghost - supporting actions, loudest to quietest.
  • link - an action that behaves like navigation.
  • success - approves or completes something.
  • warning - risky but reversible, like unpublishing.
  • destructive - deletes data for good.

Colour never carries the meaning alone. A green button still says "Approve".

Sizes

Icon-only sizes (icon, icon-xs, icon-sm, icon-lg) require an aria-label, and TypeScript will remind you. On touch screens, icon, icon-sm and icon-xs get an invisible 44px hit area.

Loading

isLoading swaps the content for a spinner and disables the button. Loading always wins: disabled={false} won't re-enable it, and screen readers hear "Loading" instead of your aria-label.

<Button isLoading={isPending}>Save changes</Button>

Disabled with a reason

A greyed-out button with no explanation is a riddle. Add disabledTooltip to say why.

<Button disabled disabledTooltip="You need the Publish permission">
  Publish
</Button>

The button then uses aria-disabled, so it stays focusable, clicks do nothing, and screen readers read the reason as its description. The tooltip shows only while disabled is true and hides during isLoading.

Use render to compose the button with another element. Set nativeButton={false} when that element isn't a <button>.

import { Link } from '@tanstack/react-router'
<Button nativeButton={false} render={<Link to="/" />}>
  Home
</Button>

Props

Prop

Type

API Reference

Base UI - Button