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. Consumer remains responsible for preventing the underlying action when disabled (e.g., calling e.preventDefault() in the Link's onClick), since native disabled doesn't gate clicks on non-form-controls.

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.