Button
Starts an action, or takes the reader somewhere else. Five types, three tones, four sizes and two radii, the same as the Figma set, with a loading state.
Installation
npx @smicolon/fasla-ui@latest add buttonRun init once per project before adding components. See Installation.
Preview
Variants
variant sets the type. default, secondary and destructive are filled. outline has a border on the inside, ghost fills only when you hover it, and soft lays a light tint over the card surface. link is text, underlined in every state.
Tones
tone picks the colour within a type: primary, secondary or destructive. Each variant has its own default, so variant="outline" on its own is the neutral outline, and adding tone="destructive" makes it the destructive one.
defaultsoftoutlineghostlinkSizes
Four sizes: xs is 26px tall, sm 32px, md 36px and lg 40px. Icons are 16px at every size. For a button with only an icon, use the icon sizes on the Icon Button page.
Radius
radius="standard" rounds the corners by 8px, and radius="rounded" makes a pill. The focus ring follows the shape.
States
Hover, pressed and keyboard focus are real CSS states, not props: hover a button, press it, or tab to it. Filled buttons fade when you hover and press them. Soft, outline and ghost buttons deepen their tint instead, and a link keeps its underline and fades. Disabled dims every variant to half opacity.
Link or ghost
Both are quiet, but they mean different things. Use ghost for an action on the same page, such as closing a panel, editing a row or opening a menu. Use link to go somewhere else, such as Forgot password?, Learn more or View all, and render it as a real link with asChild.
With icons
Put an icon before or after the label, as a child. The button sizes it to 16px and gives it the label's colour. Icons follow the reading direction; flip one that points, such as an arrow, with rtl:-scale-x-100.
Loading
loading shows a spinner, disables the button and announces loadingLabel to screen readers. With asChild the child is only marked busy (aria-busy) and disabled, so show your own progress inside it.
Accessibility
- Renders a native
button, so Enter and Space press it. Settype="submit"ortype="button"as the form needs. - Keyboard focus draws a 2px ring, 2px outside the button, in the
ringcolour. - With
asChild, the button styles its only child, such as a link. A disabled child getsaria-disabledinstead ofdisabled. - While it is loading, the button is disabled and announces
loadingLabelas a status. - Direction is inherited from
dir, never a prop. Icons and label swap ends in right-to-left.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
tone | "primary" | "secondary" | "destructive" | — | The colour within the type. Each variant has its own default. |
variant | "default" | "outline" | "ghost" | "soft" | "link" | "default" | The type: filled, outline, ghost, soft or link. |
size | "xs" | "sm" | "md" | "lg" | "icon-xs" | "icon-sm" | "icon-md" | "icon-lg" | "md" | The height. The icon sizes are for icon buttons. |
radius | "standard" | "rounded" | "standard" | The corners: slightly rounded, or a pill. |
loading | boolean | false | Shows a spinner and disables the button. |
loadingLabel | string | "Loading" | Screen-reader text while loading. |
disabled | boolean | false | Disables the button. |
asChild | boolean | false | Styles its only child, such as a link, instead of rendering a button. |
onClick | (event) => void | — | Called when the button is pressed. |
className | string | — | Extra classes for the component. |
Kept for shadcn compatibility: secondary and destructive still work as variant values (Solid in those tones), and default and icon as size values (the same as md and icon-md).
Usage
Basic button
import { Button } from "@/components/ui/button"
export function SaveButton() {
return <Button type="submit">Save changes</Button>
}Outline in the destructive tone
import { Button } from "@/components/ui/button"
export function DeleteAccountButton() {
return (
<Button variant="outline" tone="destructive">
Delete account
</Button>
)
}A link to another page
import Link from "next/link"
import { Button } from "@/components/ui/button"
export function ForgotPasswordLink() {
return (
<Button variant="link" asChild>
<Link href="/forgot-password">Forgot password?</Link>
</Button>
)
}With an icon
import { UploadIcon } from "lucide-react"
import { Button } from "@/components/ui/button"
export function UploadButton() {
return (
<Button>
<UploadIcon aria-hidden="true" strokeWidth={1.5} />
Upload
</Button>
)
}Loading
import { Button } from "@/components/ui/button"
export function SavingButton() {
return (
<Button loading loadingLabel="Saving">
Saving…
</Button>
)
}