Button
Triggers an action with visual variants for emphasis and context.
Playground
materialSets the button's visual emphasis.
sizeSets the control height and horizontal padding.
schemeApplies a semantic color scheme.
shapeUses a text button or a square icon button.
loadingShows progress while preserving the button label and width.
disabledPrevents interaction with the native button.
staticRemoves tactile scale feedback for motion-sensitive contexts.
Usage
import { Button } from "earthling-ui/button";
export function Example() {
return (
<>
<Button type="button">Create project</Button>
</>
);
}
- Use a short verb phrase that describes the action.
- The loading state keeps the label width stable, sets aria-busy, and disables the native button.
- Icon-only buttons still need an accessible name through aria-label.
Installation
Start with the project setup for React and Tailwind CSS 4. Both paths use the same component and theme.
npm install earthling-uiUse the component import shown above. Package updates keep the implementation current.
API reference
Declarations from the current library build, including inherited primitive props. Playground controls above show selected options; they are not the complete API.
// Generated by dts-bundle-generator v9.5.1
import { VariantProps } from 'class-variance-authority';
import { ComponentProps } from 'react';
export declare const buttonVariants: (props?: ({
material?: "paper" | "outline" | "ghost" | null | undefined;
scheme?: "default" | "primary" | "secondary" | "tertiary" | "neutral" | "muted" | "good" | "caution" | "bad" | null | undefined;
size?: "sm" | "md" | "lg" | null | undefined;
shape?: "pill" | "icon" | null | undefined;
} & import("class-variance-authority/types").ClassProp) | undefined) => string;
export interface ButtonProps extends ComponentProps<"button">, VariantProps<typeof buttonVariants> {
asChild?: boolean;
loading?: boolean;
/** Disables the tactile scale-on-press feedback when motion would distract. */
static?: boolean;
}
export declare const Button: import("react").ForwardRefExoticComponent<Omit<ButtonProps, "ref"> & import("react").RefAttributes<HTMLButtonElement>>;
export {};Runtime dependencies: @radix-ui/react-slot, class-variance-authority, cnfast.
Source
Use the eject command to copy this implementation with its required helpers and project-specific imports.
View Button implementation
"use client";
import { Slot } from "@radix-ui/react-slot";
import { cva, type VariantProps } from "class-variance-authority";
import { cn } from "@/utils/cn";
import { schemes } from "@/utils/variants";
import { type ComponentProps, forwardRef } from "react";
const buttonVariants = cva(
"relative inline-flex cursor-pointer items-center justify-center gap-2 rounded-(--radius-control) border border-transparent text-sm font-medium whitespace-nowrap ring-offset-background focus-visible:ring-2 focus-visible:ring-outline focus-visible:ring-offset-2 focus-visible:outline-hidden disabled:cursor-not-allowed disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 aria-invalid:border-bad aria-invalid:ring-bad/30",
{
variants: {
material: {
paper:
"bg-(--scheme-tint) text-(--scheme-foreground) shadow-xs hover:bg-(--scheme-tint)/85 aria-pressed:bg-(--scheme-tint)/70 aria-pressed:hover:bg-(--scheme-tint)/55",
outline:
"text-foreground border-(--scheme-tint)/30 hover:border-(--scheme-tint)/50 hover:bg-(--scheme-tint)/5 aria-pressed:bg-(--scheme-tint)/10 aria-pressed:hover:bg-(--scheme-tint)/15",
ghost:
"text-foreground hover:bg-(--scheme-tint)/5 aria-pressed:bg-(--scheme-tint)/10 aria-pressed:hover:bg-(--scheme-tint)/15",
},
scheme: schemes,
size: { sm: "h-9 px-3", md: "h-10 px-4 py-2", lg: "h-11 px-8" },
shape: { pill: "", icon: "px-0 aspect-square" },
},
defaultVariants: {
material: "paper",
scheme: "primary",
size: "md",
shape: "pill",
},
},
);
export interface ButtonProps
extends ComponentProps<"button">, VariantProps<typeof buttonVariants> {
asChild?: boolean;
loading?: boolean;
/** Disables the tactile scale-on-press feedback when motion would distract. */
static?: boolean;
}
const Button = forwardRef<HTMLButtonElement, ButtonProps>(
(
{
className,
material,
size,
scheme,
shape,
asChild = false,
loading = false,
static: isStatic = false,
disabled,
children,
...props
},
ref,
) => {
const Comp = asChild ? Slot : "button";
return (
<Comp
className={cn(
buttonVariants({ material, size, scheme, shape }),
!isStatic &&
"transition-transform duration-150 ease-out motion-reduce:transition-none active:scale-[0.96] motion-reduce:active:scale-100",
className,
)}
ref={ref}
data-scheme={scheme}
disabled={asChild ? undefined : disabled || loading}
aria-disabled={disabled || loading || undefined}
aria-busy={loading || undefined}
{...props}
>
{loading && !asChild ? (
<>
<span className="inline-flex items-center gap-[inherit] opacity-0">
{children}
</span>
<span
aria-hidden="true"
className="absolute size-4 animate-spin rounded-full border-2 border-current/30 border-t-current motion-reduce:animate-none"
/>
</>
) : (
children
)}
</Comp>
);
},
);
Button.displayName = "Button";
export { Button, buttonVariants };