Components / Controls
Button
Triggers an action: save, export, delete, open. One Button is one action. If clicking it takes the user somewhere, use Link instead.
When to useLink to "When to use"
solid+accent: the one main action in a view (Save, Create). Aim for at most one per screen region.outline+neutral(default): secondary actions next to it (Cancel, Back).soft: supporting actions that should still read as buttons in dense UI (toolbars, cards).ghost: low-emphasis actions in already busy chrome (table rows, panel headers).tone="danger": destructive actions. Pair with a confirmation when the action cannot be undone.
Use something else when:
- the button has an icon and no visible text: IconButton, which requires a label and shows it as a tooltip
- it navigates: Link
- it picks one of a few options: SegmentedControl
- it turns a setting on or off: Switch
UsageLink to "Usage"
import { Button } from "@arshad-shah/cynosure";
import { download, trash } from "@arshad-shah/cynosure/icons";
<Button variant="solid" tone="accent" icon={download} onClick={save}>Export</Button>
<Button>Cancel</Button>
<Button variant="soft" tone="danger" icon={trash}>Delete</Button>
<Button loading={saving}>Save</Button>
<Button type="submit">Send</Button>Sizes and iconsLink to "Sizes and icons"
sm, md and lg match TextField, Select and SearchField heights, so mixed rows line up. Icons sit at the start or end edge and flip in right-to-left layouts.
xs (20px, smaller text and icon) is for dense desktop chrome such as toolbars and inspectors. It matches the xs of TextField, Select, IconButton and SegmentedControl. Only the look is small: the button is still 24px to press, and 44px on coarse pointers.
StatesLink to "States"
- Loading: shows LoadingDots, sets
aria-busyand ignores clicks. It keeps its width, so the layout doesn't jump. - Pressed:
pressedmakes it a toggle button (aria-pressed). - Disabled: dims the button and blocks activation. Prefer explaining why an action is unavailable over silently disabling it.
Links and the current pageLink to "Links and the current page"
With href the button is a real link (target says where it opens; only safe URLs are linked). A link takes target, aria-label, aria-current, className, style, data-testid and onClick; the other button attributes do not apply to it.
aria-current marks the current item of a set and is set on the element in both forms: aria-current="page" on a link to the page being shown, aria-current="step" on the button of the current step.
<Button href="/reports" aria-current="page" variant="soft" tone="accent">Reports</Button>
<Button href="/billing">Billing</Button>PropsLink to "Props"
| Prop | What it does | Type | Default |
|---|---|---|---|
childrenRequired | The button's label. | ReactNode | |
variant | Fill style. | ButtonVariant | |
tone | Semantic colour. | Tone | |
color | A one-off colour (any CSS colour), for a brand button such as "Get it on Google Play". Overrides tone. The text on a solid fill is white or black, whichever reads; on the other variants the colour is darkened or lightened until it reads on the surface. Prefer a tone or the theme's accent. | string | |
size | Height and text size of the button. Default "md". xs (about 20px) is for dense desktop chrome: it looks small and is still 24px to press (44px on coarse pointers). | DenseSize | "md" |
icon | Leading icon (inline-start, RTL aware). | IconData | |
iconEnd | Trailing icon (inline-end). | IconData | |
loading | Shows LoadingDots, sets aria-busy and ignores activation while keeping the button's width. | boolean | |
fullWidth | Stretch to the container's width. | boolean | |
href | A link that looks like a button (it navigates: a real <a>, so it opens in a new tab, can be copied, and so on). Only safe URLs are linked. The forwarded ref is not attached to a link (use linkRef). A link takes target, id, aria-label, aria-current, className, style, data-testid and onClick; the other button attributes do not apply to it. | string | |
target | With href: where it opens, e.g. _blank. | string | |
linkRef | With href: receives the link element (the forwarded ref is for the button element). | Ref<HTMLAnchorElement> | |
square | Square: as wide as it is tall, with no padding, for one icon as the content. Prefer IconButton, which sets it and adds the required label and its tooltip. Default false. | boolean | false |
confirm | Ask before acting, in place: the first press arms the button and shows this text ("Press again to delete"), a second press within 4 seconds acts. Escape, leaving the button or the time running out disarms it. | ReactNode | |
keepFocus | A pointer press leaves focus where it was (an on-screen keypad beside a field keeps the field's caret). Keyboard users still reach it with Tab. | boolean | |
aria-current | Marks the button or link as the current item of a set: "page" for the page being shown (a link to it in a navigation bar or in Pagination), or "step", "location", "date", "time" or true. Set on the button or link element. Default: not set. | boolean | "false" | "true" | "page" | "step" | "location" | "date" | "time" | not set |
className | Added to the button (or link) element, for layout. | string | |
style | Layout only. Restyle through --cy-button-* tokens. | CSSProperties | |
data-testid | Set on the button (or link) element, to find it in tests. | string | |
type | Defaults to "button" so a Cynosure button never submits a form by accident. | "button" | "submit" | "reset" | "button" |
pressed | Toggle state; mirrored to aria-pressed and data-pressed. | boolean |
Generated from the type definitions in @arshad-shah/cynosure 0.5.0.
Behaviour and accessibilityLink to "Behaviour and accessibility"
| Input | Result |
|---|---|
| Click, tap, Enter, Space | Activates (not while loading or disabled) |
| Tab | Focus with a visible ring, also in forced-colors mode |
- Forms:
typedefaults tobutton, so it never submits a form by accident. Passtype="submit"to submit. - Contrast: every variant and tone keeps text at 4.5:1 or better in light, dark and high-contrast themes (tested).
- Long labels: truncate with an ellipsis instead of wrapping.
- Current item:
aria-currentis announced with the name ("Reports, current page"). It does not change the look; pick the variant and tone yourself.
TouchLink to "Touch"
The hit area is at least 24px, growing to 44px on coarse pointers (phones and tablets) without changing the visible size.
ThemingLink to "Theming"
Tokens: --cy-button-height-{xs,sm,md,lg}, --cy-button-radius, --cy-button-padding-inline, --cy-button-padding-inline-xs, --cy-button-gap, --cy-button-font-weight, --cy-button-max-width, --cy-button-solid-shadow, --cy-button-soft-mix, --cy-button-outline-mix, --cy-button-press-scale, and --cy-button-hover-bg and --cy-button-hover-ink (the hover fill and text colour, for one button whose hover differs from its variant, such as TitleBar's close control).
createTheme({ accent: "#2563eb", components: { button: { radius: "999px", fontWeight: "600" } } });className and style are for layout only (margin, placement). Change the look through tokens so every product stays consistent.
RelatedLink to "Related"
IconButton · LoadingDots · Link · Toolbar
These examples run Cynosure 0.5.0 (commit 50ce30b), the version on npm.