Rime

Button

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/button

Also installs: base, spinner.

Usage

import { Button } from "@/components/ui";

<Button onClick={save}>Save changes</Button>

Examples

Variants

One primary per view. Secondary switches to a white tile inside solid surfaces.

Sizes

sm for table rows and toolbars, md everywhere else, lg for the one action on an auth card.

With icons

Loading and disabled

Loading keeps the fill, dulls it and swaps the label. Disabled is a flat tint, not a faded gradient.
Link as button

shadcn names

shadcn's variant and size names work as aliases: default is primary, destructive is danger, outline is secondary; link is an inline text link; icon, icon-sm and icon-lg are square. buttonVariants is buttonClassName.

asChild

asChild renders the single child (a Next Link, an <a>) with the button's look, as shadcn does. href remains the shorter way for internal links.

API

Generated from the component's TypeScript types and JSDoc (scripts/gen-ui-registry.mjs), so it always matches the source.

Button

Button props
PropTypeDefaultDescription
variantButtonVariant"primary"primary = the one brand gradient per view. secondary = glass (white inside solid surfaces).
sizeButtonSize"md"
loadingbooleanShows a spinner, sets aria-busy and blocks further presses.
loadingLabelstringReplaces the label while loading, e.g. "Submitting…".
iconStartReactNode
iconEndReactNode
fullWidthboolean
classNamestring
childrenReactNode
hrefundefined | stringRenders a Next <Link>. Internal routes only.
asChildboolean | undefinedRender the single child element (a Next <Link>, an <a>) with the button's look, as shadcn's asChild. The child supplies its own content.
disabledboolean
prefetchboolean
replaceboolean
scrollboolean

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.

Button per-component tokens
TokenDefaultLive value
--button-casenone…
--button-danger-bgvar(--danger-gradient)…
--button-durvar(--dur)…
--button-easevar(--ease)…
--button-gap8px…
--button-ghost-inkvar(--ink-brand)…
--button-height-lgvar(--control-h-lg)…
--button-height-mdvar(--control-h-md)…
--button-height-smvar(--control-h-sm)…
--button-hover-glowvar(--glow-md-hover)…
--button-hover-overlayrgba(var(--rgb-white), 0.07)…
--button-lift0px…
--button-link-inkvar(--ink-brand)…
--button-outlinevar(--shadow-control)…
--button-outline-activeinset 0 0 0 1px rgba(var(--rgb-brand), 0.32)…
--button-outline-hover0 3px 8px rgba(var(--rgb-brand-deep), 0.1), inset 0 0 0 1px rgba(var(--rgb-brand), 0.32)…
--button-pad-lgcalc(24px * var(--density))…
--button-pad-mdcalc(18px * var(--density))…
--button-pad-smcalc(12px * var(--density))…
--button-press-overlayrgba(var(--rgb-shade), 0.14)…
--button-primary-bgvar(--brand-gradient)…
--button-primary-inkvar(--ink-inverse)…
--button-radiusvar(--r-sm) / var(--r-md) / var(--r-lg)…
--button-secondary-bgrgba(var(--rgb-white), 0.45)…
--button-secondary-hover-bgrgba(var(--rgb-white), 0.68)…
--button-secondary-hover-shadowinset 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-inkvar(--ink-heading)…
--button-size-lg15px…
--button-size-md14px…
--button-size-sm12.5px…
--button-tracking-0.005em…
--button-trim0em…
--button-weight700…

System tokens it reads

Button system tokens
TokenDefaultLive value
--blur-callouttokens.css…
--brand-deeptokens.css…
--brand-gradienttokens.css…
--control-h-lgtokens.css…
--control-h-mdtokens.css…
--control-h-smtokens.css…
--danger-filltokens.css…
--danger-gradienttokens.css…
--danger-inktokens.css…
--danger-linetokens.css…
--densitytokens.css…
--durtokens.css…
--easetokens.css…
--focus-gaptokens.css…
--focus-ringtokens.css…
--font-bodytokens.css…
--glass-edgetokens.css…
--glass-edge-strongtokens.css…
--glow-dangertokens.css…
--glow-danger-hovertokens.css…
--glow-mdtokens.css…
--glow-md-hovertokens.css…
--glow-smtokens.css…
--ink-brandtokens.css…
--ink-disabledtokens.css…
--ink-headingtokens.css…
--ink-inversetokens.css…
--ink-mutedtokens.css…
--r-2xstokens.css…
--r-lgtokens.css…
--r-mdtokens.css…
--r-smtokens.css…
--rgb-brandtokens.css…
--rgb-brand-deeptokens.css…
--rgb-contacttokens.css…
--rgb-dangertokens.css…
--rgb-shadetokens.css…
--rgb-whitetokens.css…
--shadow-controltokens.css…
--shadow-speculartokens.css…
--shadow-specular-darktokens.css…
--surface-solidtokens.css…
--wash-activetokens.css…
--wash-hovertokens.css…
--wash-subtletokens.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.md

Related: icon-button, modal