COMPONENTS/ACTIONS

Button.

A small action. A clear intention.

In practice

Use render={<a href="…" />} for links. Variants: default, secondary, outline, ghost, destructive and link. Sizes: default, sm, lg, icon.

Installation

Install Coal once. Components and TypeScript declarations are included. React 19 and React DOM 19 are peer dependencies.

Terminal · local package
npm install http://localhost:3100/downloads/chlohal-coal-ui-0.5.0.tgz

This installs the local package. It is not yet published on npm. See installation for stylesheet setup and integration.

Usage

example.tsx
"use client";
import * as React from "react";
import "@chlohal/coal-ui/styles.css";
import { ArrowUpRight, Plus } from "lucide-react";
import { Button } from "@chlohal/coal-ui";
export default function Example() {
    const [message, setMessage] = React.useState("");
    const notice = <p role="status">{message}</p>;
    const [saved, setSaved] = React.useState(false);
    return (<div>
          <Button onClick={() => setMessage("Your changes are saved.")}>
            Save changes <ArrowUpRight />
          </Button>
          <Button variant="outline" onClick={() => setMessage("Changes discarded.")}>
            Cancel
          </Button>
          <Button variant="ghost" size="icon" aria-label="Add item" onClick={() => setMessage("Item added.")}>
            <Plus />
          </Button>
          {notice}
        </div>);
}

Source

The original Coal implementation. Use the package export to integrate it.

packages/react/src/button.tsx
"use client";
import { Action, cn, type ActionProps } from "./internal.js";
export type ButtonProps = ActionProps & {
  loading?: boolean;
  variant?:
    | "default"
    | "destructive"
    | "outline"
    | "secondary"
    | "ghost"
    | "link";
  size?: "default" | "sm" | "lg" | "icon";
};
export function buttonVariants({
  variant = "default",
  size = "default",
  className,
}: Pick<ButtonProps, "variant" | "size" | "className"> = {}) {
  return cn(
    "coal-button",
    `coal-button--${variant}`,
    `coal-button--${size}`,
    className,
  );
}
export function Button({
  variant,
  size,
  className,
  loading = false,
  children,
  disabled,
  ...props
}: ButtonProps) {
  return (
    <Action
      className={buttonVariants({ variant, size, className })}
      {...props}
      {...(disabled !== undefined || loading
        ? { disabled: disabled || loading }
        : {})}
      aria-busy={loading || undefined}
    >
      {loading ? (
        <>
          <span className="coal-button-loading" aria-hidden="true" />
          {children ??
            (
              props.render?.props as
                | { children?: import("react").ReactNode }
                | undefined
            )?.children}
        </>
      ) : (
        children
      )}
    </Action>
  );
}

Accessibility checklist

  • Provide an accessible name for controls, including icon-only buttons.
  • Check keyboard navigation, visible focus and disabled states in your application.
  • Do not rely on color alone to communicate status.