Button
The tactile button: seven colours, seven variants, a loading state, and links that look like buttons.
- Press it: every button sinks a little; the shadow variant glows in its colour.
- While isLoading, a spinner replaces the start content, the cursor waits and clicks do nothing.
- Disabled buttons drop out of the tab order; disabled links stay focusable but go nowhere.
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 throughasChild - 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
- Source
- src/components/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.
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#
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.
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>
);
}As a link#
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.
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#
Also takes every attribute of <button>, except color, which mean something else here.