Button
Accessible action button with size, variant, and semantic tone support.
Composition
The preferred shape for composing the installed primitive from its exported parts.
Root
Single-slot surface with no nested parts.
ButtonThe refined source installs this primitive from src/registry/sources/refined/ui/button.tsx. Install it on its own with the command above, or inspect the source below.
npx shadcn@latest add http://127.0.0.1:3000/r/refined/button.jsonInstalled dependencies
Installed with this primitive because the slot depends on them; they are support files, not standalone sidebar items.
src/registry/sources/refined/control-variants.tsSupportsrc/registry/sources/refined/effects.cssEffectRaw code
Primary installed primitive source
"use client";import { Button as BaseButton } from "@base-ui/react/button";import { cva } from "class-variance-authority";import type { ComponentProps } from "react";import { isValidElement } from "react";import type { ButtonProps, ButtonTone, ButtonVariant } from "@/components/refined-ui/contracts";import { controlSize, controlSurfaceClasses } from "@/components/refined-ui/control-variants";import { cn } from "@/components/refined-ui/lib/cn";import { skinSlot } from "@/components/refined-ui/skin";// `variant` = visual structure (see ButtonProps contract); radius/size come from shared tokens so every control matches.const buttonVariant = cva( "relative isolate inline-flex shrink-0 cursor-pointer items-center justify-center overflow-visible whitespace-nowrap rounded-[var(--radius-control)] font-medium outline-none transition duration-[var(--duration-fast)] focus-visible:ring-2 focus-visible:ring-foreground/20 disabled:cursor-not-allowed disabled:opacity-45 active:scale-[0.98]", { variants: { variant: { solid: "bg-primary text-primary-foreground shadow-sm hover:bg-primary/90", surface: controlSurfaceClasses, ghost: "text-foreground hover:bg-foreground/6", quiet: "text-muted-foreground hover:bg-foreground/6 hover:text-foreground data-[active=true]:bg-foreground/8 data-[active=true]:text-foreground", }, }, defaultVariants: { variant: "quiet", }, },);// `tone` = color intent, layered after `variant` so it wins on the colored properties.function toneClasses(variant: ButtonVariant, tone: ButtonTone): string { if (tone === "neutral") return ""; const solid = variant === "solid"; if (tone === "primary") { return solid ? "bg-primary text-primary-foreground hover:bg-primary/90" : "text-primary hover:text-primary"; } return solid ? "bg-destructive text-destructive-foreground hover:bg-destructive/90" : "text-destructive hover:text-destructive";}// Refined skin slot. 100% Base UI: composition flows through Base UI Button's `render` prop.export function Button({ variant = "quiet", size = "sm", tone = "neutral", active = false, asChild = false, type = "button", disabled, render, nativeButton, className, children, color: _color, ...props}: ButtonProps) { // Skin resolves after library recipe, before caller's className: tailwind-merge order (not CSS specificity) decides the winner. const skinClasses = skinSlot("button", { variant, tone, size, active }); const classes = cn(buttonVariant({ variant }), controlSize({ size }), toneClasses(variant, tone), skinClasses, className); const child = asChild && isValidElement(children) ? children : null; // asChild rendering a non-<button> (e.g. a link) needs nativeButton told to Base UI to preserve button semantics. const isNativeButton = child ? child.type === "button" : nativeButton !== false; const renderProp: Pick<ComponentProps<typeof BaseButton>, "render" | "nativeButton"> = child ? { render: child, nativeButton: isNativeButton } : { render, nativeButton }; return ( <BaseButton {...(isNativeButton ? { type } : {})} disabled={disabled} data-ui="agent" data-slot="button" data-control="true" data-active={active ? "true" : "false"} data-variant={variant} data-tone={tone} data-size={size} className={classes} {...renderProp} {...props} > {child ? undefined : render ? ( children ) : ( <span data-ui="agent" data-slot="control-content" className={cn("relative z-[1] inline-flex min-w-0 items-center justify-center gap-[inherit]", skinSlot("control-content", {}))} > {children} </span> )} </BaseButton> );}Available extensions
Optional, separately installed items this surface can host — not part of the component's bundle.
CSS-driven control effects (top-shine, ripple, hover-circle) that follow every control app-wide through the emitted anatomy — portalled surfaces included.
npx shadcn@latest add http://127.0.0.1:3000/r/refined/control-effects.json// skin.config.tsx — a brand's controls either all ripple or none doexport const skin: RefinedSkin = { id: "my-brand", effects: ["ripple"],};// app layout — mirrors the resolved list on <html> for in-tree controls<ControlEffectsRuntime />// or scope effects to one subtree instead (caller-wins local override)<ControlEffectsRoot effects={["top-shine", "ripple"]}> <Toolbar /></ControlEffectsRoot>