Build · New

Button

The main action control, in five styles and four sizes, with a danger variant and a loading state. Its directional icons mirror in Arabic on their own.

Preview

Mirrored is the same English content laid out right to left: it separates a mirroring bug from a translation one.

Installation

SoonInstalling from the registry opens at launch. Until then the command below is what it will be.
npx shadcn@latest add @ritla/button

Dependencies it brings: @base-ui/react, @phosphor-icons/react

Usage

import { ArrowRightIcon } from "@phosphor-icons/react/ssr";
import { Button } from "@/components/ui/button";
<Button variant="outline">Cancel</Button>
<Button iconTrailing={ArrowRightIcon}>Continue</Button>
<Button isDanger>Delete project</Button>

Examples

Styles

filled for the main action, one per view. tonal for a strong secondary, outline for alternatives like Cancel, ghost for low-emphasis actions, inverted-ghost on colored surfaces. isDanger turns any style red.

Sizes

lg 56px (default), md 48 for forms, sm 40 for tables and filters, xs 32 for dense UI. The tap target stays at least 48px at every size.

States

Hover, pressed and focus come from the pointer and keyboard. loading puts the spinner in the leading slot and keeps focus on the button; disabled switches it off. Press Save.

Icons

iconLeading and iconTrailing are optional. “Next” uses ArrowRightIcon in both languages and flips in Arabic to point forward; the download icon never mirrors.

Action order

The main action at the inline end, the alternative before it. In Arabic the whole row mirrors, so the main action stays at the end (the left). Destructive actions get a specific label, never “OK”.

API reference

Button

Any other Base UI Button prop passes through (onClick, type…).

PropTypeDefaultDescription
variant"filled" | "tonal" | "outline" | "ghost" | "inverted-ghost""filled"Figma's Style property (style is taken by React).
size"lg" | "md" | "sm" | "xs""lg"Figma's Size: 56, 48, 40 and 32px.
isDangerbooleanfalseFigma's isDanger: red for destructive actions, with a red focus ring.
childrenReactNodeFigma's Label: the button text.
iconLeadingIconComponentIcon leading: a Phosphor or Kanz icon at the inline start.
iconTrailingIconComponentIcon trailing: an icon at the inline end.
loadingbooleanfalseState = Loading: spinner in the leading slot, aria-busy, the button stays focusable and ignores presses.
disabledbooleanfalseState = Disabled.
renderReactElementAnother element with the Button look, usually a link (<a> or <Link>). It keeps its own semantics, so it isn't announced as a button.
classNamestringExtra classes merged with cn(), such as w-full.

Accessibility

Keyboard

Keys are written for left-to-right. In right-to-left the arrow keys swap: the one pointing toward the reading direction moves forward.

KeyAction
TabMoves focus to the button and shows the focus ring (red with isDanger).
EnterSpaceActivates it. While loading, focus stays and the action doesn't repeat.

ARIA

  • A native <button> (or <a> with render), so no role is needed.
  • Loading sets aria-busy="true" and aria-disabled="true" instead of disabled, so focus isn't lost and screen readers hear the state. Change the label too (“Saving”).
  • Icons are decorative; the label is the name. Icon-only buttons belong to the Icon Button component.
  • The tap target is at least 48px even at sm and xs (WCAG 2.5.8).

Arabic and RTL notes

Mirrors

  • The leading icon sits at the inline start (right in Arabic) and the trailing one at the end, with no RTL prop.
  • Arrows and carets flip through <Icon>: “Next” points left in Arabic.
  • Action order mirrors: the main action is at the inline end in both directions.

Does not mirror

  • The loading spinner turns clockwise in both languages.
  • Checkmarks, download icons and logos inside buttons never mirror.
Numerals
Numbers inside a label (“Delete 3 files”) follow the numerals setting through formatNumber, not the button.
Arabic typography
Labels use body-16 (body-14 at xs) in Semibold; the button height is fixed and Arabic labels fit without clipping.
Mixed-direction text
A Latin name inside an Arabic label (“تسجيل الدخول عبر Google”) goes in <bdi> so punctuation doesn't jump.

All components ›