Button

Accessible React button for Saudi government platforms: nine variants, sizes, icons, a loading state, and asChild for router links. RTL and dark ready.

Button triggers an action. Pick a variant and size, add startIcon/endIcon or loading, and use asChild to render a link that looks like a button.

When to use

Use Button for an action that changes something: submit a form, open a dialog, confirm a step. For navigation to another page, use Link, or wrap a router link with asChild so it looks like a button but stays an anchor.

Reserve the primary variant for the one main action on a screen and use secondary or outline for the rest. The destructive variants flag irreversible actions such as deleting a record. In government forms, keep the primary action at the inline end of the row so it lands on the same side in Arabic and English.

Examples

Variants

Sizes

Icons & loading

Arabic label

As a link (asChild)

Props

Props declared by Button. Native attributes of the underlying element pass through.

PropTypeDefaultDescription
variant"primary" | "secondary" | "black" | "outline" | "ghost" | "destructive" | "destructive-outline" | "destructive-subtle" | "destructive-ghost"
size"sm" | "md" | "lg" | "icon" | "icon-md" | "icon-sm" | "icon-xs"
fullWidthboolean
onColorboolean
loadingboolean
startIconreact.ReactNodeLeading icon. For an icon-only button pass the glyph as children with an icon size and an aria-label.
endIconreact.ReactNode
iconFlipbooleanFlip start/end icons horizontally in RTL (for chevrons, arrows, etc.)
asChildbooleanRender as the single child element instead of a <button>. Lets consumers compose Button with router links (<Button asChild><Link href="…">…</Link></Button>) without <button><a> invalid nesting. The type and native disabled attributes are dropped in this mode (they're meaningless on non-button elements) , the disabled visual + a11y state still works via aria-disabled + tabIndex={-1}. button.css matches on [aria-disabled='true'] alongside :disabled so the styling applies on any element. While disabled or loading, clicks (and Enter, which fires a click on links) are blocked before the child's own handler runs, so a router link doesn't navigate.

Accessibility

Button renders a native <button>, so focus, Enter, and Space work without extra wiring. While loading is set, the button exposes aria-busy and ignores clicks.

Icon-only buttons need an aria-label, because the icon alone gives screen readers nothing to announce. In asChild mode the disabled state uses aria-disabled and removes the element from the tab order; you must still block the underlying action yourself. A visible focus ring shows in light and dark mode.