Button
StableA rounded button with a text label. It comes in five styles and three sizes, and can hold an icon.
Also known as btn, call to action, cta
Install with npm install @tattva/ui (setup), or copy the code from the kit in Downloads.
Variants
<div className="flex flex-wrap items-center gap-3">
<Button variant="primary">Primary</Button>
<Button variant="lime">Lime</Button>
<Button variant="secondary">Secondary</Button>
<Button variant="ghost">Ghost</Button>
<Button variant="danger">Delete chat</Button>
</div>Primary is the default. Use it once per screen. Lime is a small bright highlight with dark text. Ghost suits quiet actions in toolbars.
When to use it
The main way to take an action. Its style shows how much the action matters, so one strong button can lead a screen while quieter ones sit beside it.
Made of: Icon before · Label · Icon after
Use it for
- An action with a short verb label, like Send, Save or Regenerate.
- The one main action on a screen: use the primary or lime style.
- A second, quieter action next to the main one: use secondary or ghost.
- Deleting something, or anything that cannot be undone: use danger.
Not for
- An action shown by an icon alone: use IconButton.
- A setting that turns on or off right away: use Switch.
- Picking one option from a small set: use SegmentedControl.
Do and avoid
Do
- Use one primary button per screen, for the action you most want people to take.
- Use lime for one positive highlight, with dark text. Lime is only ever a background.
- Use secondary for the second action beside a primary one. Use ghost for quiet toolbar actions.
- Start labels with a verb that says what happens, like "Regenerate" or "Delete chat".
- Use busy, not disabled, while a press is being handled, so focus stays on the button.
Avoid
- Do not use the danger style unless something is deleted or cannot be undone.
- Do not put two primary buttons side by side.
- Do not make a button amber. Amber is only for when a person has to act.
- Do not use an icon alone in a Button. For icon-only buttons, use IconButton with a label.
On a phone
- On a touch screen every button keeps the size it is drawn at and has an invisible tap area at least 44px tall and wide, so sm and md stay small but are easy to tap.
- The label never wraps, so a long label can run past a narrow container. Keep labels short or shorten them on a phone.
- Hover colours do nothing on a phone, so the pressed state is the only feedback: the button sinks a little under the finger and comes back when it lifts.
Properties
| Prop | Type | Default | Description |
|---|---|---|---|
| variant | "primary" | "lime" | "secondary" | "ghost" | "danger" | "primary" | Visual weight of the button. Exported as ButtonVariant. |
| size | "sm" | "md" | "lg" | "md" | Height and padding: 32px, 36px or 44px. Exported as ButtonSize. |
| leading | ReactNode | none | Content rendered before the label, usually an icon. |
| trailing | ReactNode | none | Content rendered after the label. |
| busy | boolean | false | Work is running after a press. The label gives way to a spinner without changing the width, presses are ignored, and the button keeps focus and stays in the Tab order. Sets aria-busy. |
| busyLabel | string | "Working" | Read by screen readers while busy, for example "Saving". |
| type | "button" | "submit" | "reset" | "button" | Native button type. Set to "submit" explicitly inside forms. |
| ref | Ref<HTMLButtonElement> | none | The button element, for moving focus to it, for example onto the safe choice of a confirmation. |
| className | string | none | Merged after the variant and size classes, so it can override them. |
| children | ReactNode | none | The label. |
| ...rest | ButtonHTMLAttributes<HTMLButtonElement> | none | Passed to the underlying <button>, including onClick, disabled and aria-* attributes. |
"Show code under the preview gives the code for the example on the stage."
In Figma
In Figma, one Button switches all of this from its properties panel, across 90 variants. Each property sets the React prop beside it, so design and code use the same names.
| Figma property | React | Values |
|---|---|---|
| Variant | variant | Primary, Lime, Secondary, Ghost, Danger |
| Size | size | SM, MD, LG |
| State | Hover, Pressed and Focus come from the browser. Disabled sets disabled. Busy sets busy. | Default, Hover, Pressed, Focus, Disabled, Busy |
| Label | children | Any text |
| Leading icon | leading | On or off |
| Trailing icon | trailing | On or off |
| Leading icon swap | leading | Any Tattva icon |
| Trailing icon swap | trailing | Any Tattva icon |
States
States this part has beyond its default look. Build each one your screen can reach. States set by a property are in the Code tab.
- disabled
- Pass the native disabled attribute; it looks dimmed, ignores clicks and is skipped by keyboard.
- hover
- The background shifts to a darker or tinted shade when the pointer is over it.
- focus
- A visible focus ring appears when you reach it with the keyboard.
- pressed
- While held down it sinks to --press-scale in --dur-instant and comes back in --dur-fast when let go. Off under reduced motion.
- busy
- Pass busy: a spinner takes the place of the label at the same width, presses are ignored, aria-busy is set and busyLabel (default "Working") is read. It keeps focus and stays in the Tab order.
Tokens
Change one of these and every part that uses it follows.
--accent--accent-hover--on-accent--lime--lime-hover--on-lime--surface--bg--border--border-strong--hover--fg--fg-muted--danger--dur-instant--dur-fast--press-scale--focus-ring--radius-control
Built in
- It is a real button, so the Tab key reaches it and Enter and Space press it.
- A clear outline shows when someone tabs to it.
- A disabled button looks faded, cannot be pressed and is skipped by the Tab key.
- While busy it sets aria-busy, keeps focus and stays in the Tab order, and screen readers hear busyLabel after the label, which stays in the page but cannot be seen. Presses do nothing until busy is off.
- On press it sinks to --press-scale in --dur-instant and comes back in --dur-fast. IconButton has the same press.
- The press is off, and color changes are instant, for people who turn off motion in their system settings.
What you need to do
- Write a label that says what happens, such as "Delete chat", not "OK".
- Set type to "submit" when the button should send a form. It does not by default.
- If you put only an icon inside, add a name for screen readers, or use IconButton instead.
- While busy, set busyLabel to what is happening, such as "Saving". Turn busy off when the work ends, and show the result or the error on the page.
Built toward WCAG 2.2 AA and checked automatically; no independent audit yet. Read the accessibility guide