Skip to content
cennge-ui
GitHubv0.3.1, changelog

Button

The tactile button: seven colours, seven variants, a loading state, and links that look like buttons.

  1. Press it: every button sinks a little; the shadow variant glows in its colour.
  2. While isLoading, a spinner replaces the start content, the cursor waits and clicks do nothing.
  3. Disabled buttons drop out of the tab order; disabled links stay focusable but go nowhere.
Live: the component itself, not a picture of it.

Field marks

Import
import { Button } from "cennge-ui";
Size
2.2 KB gzip for this import, its dependencies aside
Pattern
Native <button type="button">; your element through asChild
Keyboard
Enter Space
SSR
Rendered on the server in the Next.js check; hydrates without a warning
Cursor
Pointer when enabled, wait while loading
Tests
Unit tests; end-to-end in Chromium, Firefox and WebKit, on desktop and phone viewports: button

When to use#

A Button runs an action: save, publish, open a dialog. When it takes the reader somewhere instead, render a link with asChild, so it keeps the button's look but stays a real <a> that opens in a new tab and shows its address.

Similar#

  • ToggleGroup holds a state that stays pressed; a Button acts once.
  • Snippet is for a command to copy, not a button with code in it.

Colours#

primary takes the accent, so it follows data-accent; the others are fixed roles. Switch the accent at the top of the page and watch the primary button change.

colors.tsx
import { Button, type CenngeColor } from "cennge-ui";

const colors: CenngeColor[] = ["default", "primary", "secondary", "success", "warning", "danger", "info"];

export default function ButtonColors() {
  return (
    <div className="flex flex-wrap gap-3">
      {colors.map((color) => (
        <Button key={color} color={color}>
          {color[0]!.toUpperCase() + color.slice(1)}
        </Button>
      ))}
    </div>
  );
}

Sizes and radius#

sizes.tsx
import { Button } from "cennge-ui";

export default function ButtonSizes() {
  return (
    <div className="flex flex-wrap items-center gap-3">
      <Button size="sm">Small</Button>
      <Button size="md">Medium</Button>
      <Button size="lg">Large</Button>
      <Button radius="full" variant="flat" color="primary">
        Full radius
      </Button>
    </div>
  );
}

Loading#

isLoading shows a spinner, sets aria-busy and disables the button until the request ends, so a second click cannot send it twice. The pointer stays over it with a waiting cursor.

loading.tsx
import { useState } from "react";
import { Button, toast } from "cennge-ui";

export default function PublishButton() {
  const [isPublishing, setPublishing] = useState(false);

  async function publish() {
    setPublishing(true);
    // Stands in for the real request.
    await new Promise((resolve) => setTimeout(resolve, 1500));
    setPublishing(false);
    toast.success("Published", { description: "payload-cache@1.4.0 is on npm" });
  }

  return (
    <Button color="primary" isLoading={isPublishing} onClick={publish}>
      {isPublishing ? "Publishing" : "Publish"}
    </Button>
  );
}

With asChild the child element is rendered with the button's styles and states. A disabled link keeps its focus, so screen readers still find it, but neither a click nor Enter follows it.

as-link.tsx
import Link from "next/link";
import { Button } from "cennge-ui";

export default function ButtonAsLink() {
  return (
    <div className="flex flex-wrap gap-3">
      {/* The Link renders the <a>; the Button gives it its look and its disabled and loading states. */}
      <Button asChild variant="flat" color="primary">
        <Link href="/docs/installation/">Install</Link>
      </Button>
      <Button asChild variant="flat" isDisabled>
        <Link href="/docs/theming/">Theming</Link>
      </Button>
    </div>
  );
}

Props#

PropTypeDefault
color
"default""primary""secondary""success""warning""danger""info"
"default"
variant
"light""bordered""flat""faded""solid""shadow""ghost"
"solid"
size
"sm""md""lg"
"md"
radius
"none""sm""md""lg""full"
"md"
isIconOnly
booleanSquare button sized for a single icon. Give it an aria-label.
–
fullWidth
boolean
–
isDisabled
boolean
–
isLoading
booleanShows a spinner in place of startContent and blocks interaction.
–
startContent
React.ReactNode
–
endContent
React.ReactNode
–
spinner
React.ReactNodeReplaces the default spinner while isLoading.
–
asChild
booleanRender the child element (e.g. a router <Link>) with button styles and behaviour.
–

Also takes every attribute of <button>, except color, which mean something else here.