Base components
Button
BetaSince 0.2.0
Runs an action, such as Save or Delete, in five variants and two sizes.
import { Button, IconButton } from "@anantya/uix";Overview
Use a button to run an action, such as saving a form or deleting an invoice. Give each view one primary button for its main action, and make the others secondary, ghost or danger. IconButton is a square button that shows only an icon, for actions whose icon is familiar, such as copy or edit.
To go to another page, use a link instead. When a link must look like a button, such as a call to action, render your router's link with asChild.
Playground
Note
The playground forButton is coming soon.Examples
Five variants: primary for the main action, secondary for the others, ghost for quiet
actions, danger for destructive ones and link to look like a link.
Code
import { Button } from "@anantya/uix";
export default function Variants() {
return (
<>
<Button>Save changes</Button>
<Button variant="secondary">Cancel</Button>
<Button variant="ghost">Edit</Button>
<Button variant="danger">Delete invoice</Button>
<Button variant="link">View details</Button>
</>
);
}Two sizes: md, 36px high, is the default, and sm, 32px, fits dense screens such as tables
and toolbars.
Code
import { Filter } from "@anantya/icons";
import { Button, IconButton } from "@anantya/uix";
export default function Sizes() {
return (
<>
<Button size="sm" variant="secondary">
Export
</Button>
<IconButton size="sm" variant="secondary" aria-label="Filter invoices">
<Filter />
</IconButton>
<Button variant="secondary">Export</Button>
<IconButton variant="secondary" aria-label="Filter invoices">
<Filter />
</IconButton>
</>
);
}An icon goes before the label, or after it with iconPosition="end".
Code
import { Add, ArrowRight } from "@anantya/icons";
import { Button } from "@anantya/uix";
export default function WithIcon() {
return (
<>
<Button icon={<Add />}>New invoice</Button>
<Button variant="secondary" icon={<ArrowRight />} iconPosition="end">
Next step
</Button>
</>
);
}While loading, it shows a spinner, ignores clicks and keeps focus. Click it to see.
Code
"use client";
import { Button } from "@anantya/uix";
import { useState } from "react";
export default function Loading() {
const [saving, setSaving] = useState(false);
const save = () => {
setSaving(true);
setTimeout(() => setSaving(false), 2000);
};
return (
<Button loading={saving} loadingText="Saving…" onClick={save}>
Save changes
</Button>
);
}A disabled button can't be clicked or reached with Tab, so say next to it what turns it on.
Add a customer to send this invoice.
Code
import { Button } from "@anantya/uix";
export default function Disabled() {
return (
<div className="flex flex-col items-center gap-2">
<Button disabled>Send invoice</Button>
<p className="text-sm text-muted-foreground">Add a customer to send this invoice.</p>
</div>
);
}IconButton takes the same five variants and two sizes, and its aria-label names the action.
Code
import { Add, Copy, Edit, Settings, Trash } from "@anantya/icons";
import { IconButton } from "@anantya/uix";
export default function IconButtons() {
return (
<>
<IconButton aria-label="Add customer">
<Add />
</IconButton>
<IconButton variant="secondary" aria-label="Invoice settings">
<Settings />
</IconButton>
<IconButton variant="ghost" aria-label="Edit invoice">
<Edit />
</IconButton>
<IconButton variant="danger" aria-label="Delete invoice">
<Trash />
</IconButton>
<IconButton variant="link" aria-label="Copy invoice number">
<Copy />
</IconButton>
</>
);
}With asChild, a link, such as a Next.js Link, gets the button's look and stays a link.
Code
import { ArrowRight } from "@anantya/icons";
import { Button } from "@anantya/uix";
import Link from "next/link";
export default function AsLink() {
return (
<Button asChild variant="secondary">
<Link href="/components">
Browse components
<ArrowRight />
</Link>
</Button>
);
}Props
Note
The props table forButton is coming soon.Accessibility
It follows the WAI-ARIA button pattern. It renders a <button type="button">, so it never submits a form by accident; pass type="submit" for a form's submit button.
| Key | Action |
|---|---|
| Tab | Moves focus to the next button. A disabled button is skipped; a loading one isn't. |
| Shift + Tab | Moves focus to the previous button. |
| Enter or Space | Runs its action, unless it is loading. A link rendered with asChild runs on Enter only, like any link. |
Screen readers
- A screen reader announces it as a button named by its label, such as "Save changes, button". IconButton is named by its
aria-label, and its icon is hidden from screen readers - With
asChild, it is announced as its child is, so a link stays a link - While loading, it is marked busy and unavailable, and keeps focus, so the reader doesn't lose their place
- A disabled button is announced as unavailable when the reader browses to it
Contrast
The lowest contrast of its text and icons at rest, on hover and while pressed, on the page and in a dialog. Text needs 4.5:1 and the focus outline 3:1.
| Variant | Light | Dark |
|---|---|---|
primary | 5.2:1 | 5.2:1 |
secondary | 11.9:1 | 7.3:1 |
ghost | 16.1:1 | 14.2:1 |
danger | 6.4:1 | 6.8:1 |
link | 4.7:1 | 5.6:1 |
| Focus outline | 5.2:1 | 6.7:1 |
A disabled button is exempt from contrast minimums, as WCAG allows for inactive controls. With forced colors on, such as Windows high contrast, primary, secondary and danger keep their edge as a border.
WCAG 2.2
- 1.4.3 Contrast (Minimum) and 1.4.11 Non-text Contrast, in light and dark
- 2.1.1 Keyboard: every action works from the keyboard, with Enter or Space, or Enter on a link
- 2.4.7 Focus Visible: a focus outline shows on keyboard focus
- 2.5.8 Target Size (Minimum): every button and IconButton is at least 32 by 32px
- 4.1.2 Name, Role, Value: a native button, named by its label or
aria-label, with its busy and disabled state
axe checks every example on this page in CI, in light and dark, in Chromium, Firefox and WebKit.
What your app still has to do
- Write a label that says what the button does, such as Delete invoice, and an IconButton
aria-labelthat names the action, not the icon - When the visible label is shortened, start the
aria-labelwith the visible words, so voice control users can say them - Tell the reader what happened after a loading button finishes, such as with
announce("Invoice sent") - Say on the screen why a button is disabled, or leave it enabled and explain what's missing when it is clicked
Do / Don't
Use one primary button per view, for the main action.
Don't make every action primary. Nothing stands out.
Name what the button does, such as Delete invoice.
Don't use a vague label such as OK or Yes. The reader can't tell what it does.
Name an IconButton after its action, such as Copy invoice number.
Don't name it after its icon, such as Clipboard. A screen reader user hears the icon, not the action.
Use a link, or a Button with asChild, to go to another page.
Don't navigate from a button's onClick. The reader can't open it in a new tab, and screen readers announce a button.
Changelog
Every change to Button since 0.2.0 is in the release notes.