Skip to content
Cynosurev0.5.0
SearchStorybookGitHubnpmColour mode: System. Switch to Light

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.

Variants and tones

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"

tsx
Copy
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.

Extra small for dense chrome

StatesLink to "States"

  • Loading: shows LoadingDots, sets aria-busy and ignores clicks. It keeps its width, so the layout doesn't jump.
  • Pressed: pressed makes it a toggle button (aria-pressed).
  • Disabled: dims the button and blocks activation. Prefer explaining why an action is unavailable over silently disabling it.

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.

tsx
Copy
<Button href="/reports" aria-current="page" variant="soft" tone="accent">Reports</Button>
<Button href="/billing">Billing</Button>
Current page in both forms

PropsLink to "Props"

Every prop of Button
PropWhat it doesTypeDefault
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.booleanfalse
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"

Behaviour and accessibility, table 1
InputResult
Click, tap, Enter, SpaceActivates (not while loading or disabled)
TabFocus with a visible ring, also in forced-colors mode
  • Forms: type defaults to button, so it never submits a form by accident. Pass type="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-current is 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).

ts
Copy
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.

IconButton · LoadingDots · Link · Toolbar

These examples run Cynosure 0.5.0 (commit 50ce30b), the version on npm.

Getting started
Theming
Icons
Accessibility
Code editors and highlighters
Using Cynosure without React
Build-tool adapters
Components
Accordion
AppShell
AspectRatio
Breadcrumbs
Calendar
Card
Container
Disclosure
Divider
DocumentViewport
Grid
Inline
PageHeader
PanZoomCanvas
ScrollArea
SideNav
SortableList
SplitPane
Stack
StatusBar
Tabs
TitleBar
Toolbar
Tree
VirtualList
ZoomControls
Announcer
Avatar
Badge
CodeBlock
DescriptionList
EmptyState
ErrorBoundary
Field
Heading
IconTile
Image
Kbd
List
LoadingDots
Notice
ProgressBar
Prose
Skeleton
Stat
Steps
Table
Text
Button
Checkbox
Checklist
ColorField
ColorSwatches
Combobox
CompareSlider
CopyButton
DateField
FileDrop
IconButton
KeyValueEditor
Link
NumberField
Pagination
PathField
RadioGroup
SearchField
SegmentedControl
Select
SelectionFrame
SignaturePad
Slider
Switch
TagInput
TextArea
TextField
ThemeToggle
TimeField
ButtonBase
ChoiceBase
DialogBase
FileInputBase
HiddenValue
Icon
InputBase
LinkBase
LiveRegion
TextAreaBase
ChatMessage
Choice
DataGrid
DeviceFrame
HighlightedText
HotkeyField
ListRow
OptionList
OverlayFrame
QRCode
RowActions
Section
SiteFooter
SiteHeader
SkipLink
SortButton
VisuallyHidden
CommandPalette
ContextMenu
Dialog
FloatingToolbar
HoverCard
Menu
Menubar
Popover
ProgressOverlay
Sheet
Toast
Tooltip
Changelog