Refined UI

An opinionated, customizable superset of shadcn/ui

AgentsPrimitivesSkillsSkins
Guides
  • Overview
  • Get started
  • Shadcn compatibility
  • Architecture
  • Agent surface
Primitives
  • Accordion
  • Alert
  • Alert dialog
  • Aspect ratio
  • Autocomplete
  • Avatar
  • Badge
  • Button
  • Button group
  • Calendar
  • Card
  • Checkbox
  • Checkbox Group
  • Code
  • Code Diff
  • Collapsible
  • Color Picker
  • Combobox
  • Command
  • Date Picker
  • Dialog
  • Drawer
  • Empty
  • Field
  • Form
  • Gradient Editor
  • Hover card
  • Input
  • Input group
  • Input OTP
  • Item
  • Kbd
  • Markdown
  • Menu
  • Menubar
  • Meter
  • Native select
  • Navigation menu
  • Number Field
  • Pagination
  • Popover
  • Progress
  • Radio group
  • Resizable
  • Scroll area
  • Select
  • Sidebar
  • Skeleton
  • Slider
  • Spinner
  • Switch
  • Table
  • Table of contents
  • Tabs
  • Textarea
  • Toast
  • Toggle
  • Toolbar
  • Tooltip
  • Tree
  • Trigger Menu
  • Typography
Hooks
  • Use Chat Message
  • Use Chat Input
  • Use User Ask
  • Use Audio Recorder
  • Use Environment Variables
  • Use Copy To Clipboard
  • Use Tool Call
  • Use Sidebar Resize
Utils
  • cn
  • Skin
  • Contracts
  • Serialize
Extensions
  • Control Effects
  • View Transition
  • Send Aurora
  1. Primitives
  2. Button
Primitives

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.

Button

The 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.

Registry command
npx shadcn@latest add http://127.0.0.1:3000/r/refined/button.json
See registry manifest

Installed dependencies

Installed with this primitive because the slot depends on them; they are support files, not standalone sidebar items.

Control variantssrc/registry/sources/refined/control-variants.tsSupport
Effect utilitiessrc/registry/sources/refined/effects.cssEffect

Raw code

Primary installed primitive source

Button slot
src/registry/sources/refined/ui/button.tsx
"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.

ControlEffectsRoot-mounted

CSS-driven control effects (top-shine, ripple, hover-circle) that follow every control app-wide through the emitted anatomy — portalled surfaces included.

Effects
Install
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 do
export 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>

On this page

  • Preview
  • Composition
  • Installation
  • Dependencies
  • Source
  • Extensions
Variant
Tone
Size
Icon only
State
Render prop
Render as link
Variant
Tone
Size
Icon only
State
Render prop
Render as link