The action control: primary gradient, glass secondary, ghost, and two danger forms, in three sizes.
Button is every pressable action that is not an icon on its own. Primary carries the brand gesture (the brand gradient with a soft glow) and appears at most once per view. Secondary is glass on the page and a white tile with a brand ring inside solid surfaces (a Card, a Modal, an auth card), switched automatically by the nearest data-surface="solid" ancestor. Ghost is for low-emphasis actions in rows and toolbars.
Danger is the irreversible action, in the danger gradient. dangerSecondary is its inverse (danger ink on the secondary's glass) for destructive steps that are not the last one, such as "Review deletion" before the final "Delete project".
Pass href to render a Next Link with the same look. A disabled or loading link drops its href and leaves the tab order, as a disabled button would. Loading shows a spinner, sets aria-busy, blocks presses and can swap the label (loadingLabel).
Installation
npx shadcn@latest add KezLahd/rime/buttonAlso installs: base, spinner.
Usage
import { Button } from "@/components/ui";
<Button onClick={save}>Save changes</Button>Examples
Variants
Sizes
With icons
Loading and disabled
shadcn names
asChild
API
Generated from the component's TypeScript types and JSDoc (scripts/gen-ui-registry.mjs), so it always matches the source.
Button
| Prop | Type | Default | Description |
|---|---|---|---|
| variant | ButtonVariant | "primary" | primary = the one brand gradient per view. secondary = glass (white inside solid surfaces). |
| size | ButtonSize | "md" | |
| loading | boolean | Shows a spinner, sets aria-busy and blocks further presses. | |
| loadingLabel | string | Replaces the label while loading, e.g. "Submitting…". | |
| iconStart | ReactNode | ||
| iconEnd | ReactNode | ||
| fullWidth | boolean | ||
| className | string | ||
| children | ReactNode | ||
| href | undefined | string | Renders a Next <Link>. Internal routes only. | |
| asChild | boolean | undefined | Render the single child element (a Next <Link>, an <a>) with the button's look, as shadcn's asChild. The child supplies its own content. | |
| disabled | boolean | ||
| prefetch | boolean | ||
| replace | boolean | ||
| scroll | boolean |
Also accepts the native attributes of AnchorHTMLAttributes, AriaAttributes, ButtonHTMLAttributes, DOMAttributes, HTMLAttributes.
Theme tokens
Per-component tokens are undeclared by default, so the default shown is the fallback the CSS uses. Set one at :root (or in theme.css) to restyle every instance. Live values are read from this page, so they follow the Rime Studio.
| Token | Default | Live value |
|---|---|---|
| --button-case | none | … |
| --button-danger-bg | var(--danger-gradient) | … |
| --button-dur | var(--dur) | … |
| --button-ease | var(--ease) | … |
| --button-gap | 8px | … |
| --button-ghost-ink | var(--ink-brand) | … |
| --button-height-lg | var(--control-h-lg) | … |
| --button-height-md | var(--control-h-md) | … |
| --button-height-sm | var(--control-h-sm) | … |
| --button-hover-glow | var(--glow-md-hover) | … |
| --button-hover-overlay | rgba(var(--rgb-white), 0.07) | … |
| --button-lift | 0px | … |
| --button-link-ink | var(--ink-brand) | … |
| --button-outline | var(--shadow-control) | … |
| --button-outline-active | inset 0 0 0 1px rgba(var(--rgb-brand), 0.32) | … |
| --button-outline-hover | 0 3px 8px rgba(var(--rgb-brand-deep), 0.1), inset 0 0 0 1px rgba(var(--rgb-brand), 0.32) | … |
| --button-pad-lg | calc(24px * var(--density)) | … |
| --button-pad-md | calc(18px * var(--density)) | … |
| --button-pad-sm | calc(12px * var(--density)) | … |
| --button-press-overlay | rgba(var(--rgb-shade), 0.14) | … |
| --button-primary-bg | var(--brand-gradient) | … |
| --button-primary-ink | var(--ink-inverse) | … |
| --button-radius | var(--r-sm) / var(--r-md) / var(--r-lg) | … |
| --button-secondary-bg | rgba(var(--rgb-white), 0.45) | … |
| --button-secondary-hover-bg | rgba(var(--rgb-white), 0.68) | … |
| --button-secondary-hover-shadow | inset 0 0 0 1px rgba(var(--rgb-white), 0.9), var(--shadow-specular), 0 4px 12px rgba(var(--rgb-brand-deep), 0.1) | … |
| --button-secondary-ink | var(--ink-heading) | … |
| --button-size-lg | 15px | … |
| --button-size-md | 14px | … |
| --button-size-sm | 12.5px | … |
| --button-tracking | -0.005em | … |
| --button-trim | 0em | … |
| --button-weight | 700 | … |
System tokens it reads
| Token | Default | Live value |
|---|---|---|
| --blur-callout | tokens.css | … |
| --brand-deep | tokens.css | … |
| --brand-gradient | tokens.css | … |
| --control-h-lg | tokens.css | … |
| --control-h-md | tokens.css | … |
| --control-h-sm | tokens.css | … |
| --danger-fill | tokens.css | … |
| --danger-gradient | tokens.css | … |
| --danger-ink | tokens.css | … |
| --danger-line | tokens.css | … |
| --density | tokens.css | … |
| --dur | tokens.css | … |
| --ease | tokens.css | … |
| --focus-gap | tokens.css | … |
| --focus-ring | tokens.css | … |
| --font-body | tokens.css | … |
| --glass-edge | tokens.css | … |
| --glass-edge-strong | tokens.css | … |
| --glow-danger | tokens.css | … |
| --glow-danger-hover | tokens.css | … |
| --glow-md | tokens.css | … |
| --glow-md-hover | tokens.css | … |
| --glow-sm | tokens.css | … |
| --ink-brand | tokens.css | … |
| --ink-disabled | tokens.css | … |
| --ink-heading | tokens.css | … |
| --ink-inverse | tokens.css | … |
| --ink-muted | tokens.css | … |
| --r-2xs | tokens.css | … |
| --r-lg | tokens.css | … |
| --r-md | tokens.css | … |
| --r-sm | tokens.css | … |
| --rgb-brand | tokens.css | … |
| --rgb-brand-deep | tokens.css | … |
| --rgb-contact | tokens.css | … |
| --rgb-danger | tokens.css | … |
| --rgb-shade | tokens.css | … |
| --rgb-white | tokens.css | … |
| --shadow-control | tokens.css | … |
| --shadow-specular | tokens.css | … |
| --shadow-specular-dark | tokens.css | … |
| --surface-solid | tokens.css | … |
| --wash-active | tokens.css | … |
| --wash-hover | tokens.css | … |
| --wash-subtle | tokens.css | … |
Accessibility
- Renders a real <button type="button"> (or a Next <Link>), so Enter and Space work and the role is right with no ARIA.
- Focus is a 2px ring with a white gap (--focus-ring), drawn with box-shadow so it follows the radius.
- Loading sets aria-busy and disables the button; keep the loadingLabel short and present tense ("Saving…").
- A link with disabled renders a non-focusable span with role="link" and aria-disabled, never a dead href.
- Square icon sizes have no text: give them aria-label (or use IconButton, which requires a label).
- Every button carries data-slot="button", data-variant and data-size, for styling from a CSS Module without new props.
- Primary text must hold 4.5:1 on the gradient's light stop; re-check it in the Studio's Contrast tab when a theme changes the gradient.
Do and don't
Do
- Use one primary per view: the action the screen exists for.
- Write labels as verbs plus the object: "Add team member", "Send question".
- Use dangerSecondary for the step that leads to a destructive confirm, and danger only on the confirm itself.
Don't
- Never scale a button on hover; hover deepens the glow (no lift: --button-lift is 0 in the default theme).
- Don't put two primary buttons side by side; demote one to secondary.
- Don't fade a disabled gradient; the flat tint is the disabled state.
- Don't use Button for navigation inside text; use a plain link.
For AI agents
This page as markdown, rendered from the same registry entry. Point an agent at it (or add .md to this page's URL), or at /llms.txt for the index.
Open button.mdRelated: icon-button, modal