Seed
Button
Triggers an action or event — submitting a form, opening a dialog, or performing an in-page operation. Comes in three variants to establish visual hierarchy.
Variants
Use primary for the single most important action on a page. Reserve secondary for supporting actions and tertiary for low-emphasis or destructive-recovery actions.
<Button variant="primary">Primary</Button>
<Button variant="secondary">Secondary</Button>
<Button variant="tertiary">Tertiary</Button>Sizes
Three sizes map to the button layout tokens. The default is md — reach for sm in data-dense tables or toolbars, and lg for prominent CTAs on marketing or onboarding surfaces.
<Button size="sm">Small</Button>
<Button size="md">Medium</Button>
<Button size="lg">Large</Button>Disabled state
The disabled attribute removes the button from the tab order and applies muted styling. Avoid disabling buttons without explaining why — prefer showing an error or tooltip instead.
<Button variant="primary" disabled>Primary</Button>
<Button variant="secondary" disabled>Secondary</Button>
<Button variant="tertiary" disabled>Tertiary</Button>Intent modes
Buttons inherit colour from the nearest data-intent ancestor. Wrap a section of your UI to change the palette — no prop changes needed on the button itself.
<div data-intent="success">
<Button variant="primary">Save</Button>
</div>
<div data-intent="danger">
<Button variant="primary">Delete</Button>
</div>Or pass the intent prop so the button sets data-intent on its root element — handy when you cannot wrap markup or need a different intent than the rest of the page.
<Button intent="success" variant="primary">Save</Button>
<Button intent="danger" variant="primary">Delete</Button>Installation
Button lives under packages/grove/src/seeds/Button.tsx and is re-exported from @twig/grove. Import matching CSS so styles apply — either @twig/grove/css/button alone or the full @twig/grove/css bundle.
import { Button } from '@twig/grove'
// Recommended — tokens, typography utilities, and all UI styles:
// @import '@twig/grove/css';
// Smaller bundles: import specific @twig/grove/css/* token sheets, then add:
// @import '@twig/grove/css/button';Props
| Prop | Type | Default | Description |
|---|---|---|---|
| variant | 'primary' | 'secondary' | 'tertiary' | 'primary' | Visual style of the button. Primary for the main call-to-action, secondary for supporting actions, tertiary for low-emphasis actions. |
| size | 'sm' | 'md' | 'lg' | 'md' | Controls padding and font size. Use sm in dense UI, lg for prominent entry-point actions. |
| intent | 'brand' | 'accent' | 'info' | 'success' | 'warning' | 'danger' | — | Sets data-intent on the button so semantic colour tokens resolve for this control (and descendants). Omit to inherit from an ancestor. |
| disabled | boolean | false | Disables the button. Removes it from the tab order and applies disabled styling. |
| ...props | React.ButtonHTMLAttributes<HTMLButtonElement> | — | All standard button attributes (onClick, type, aria-*, etc.) are forwarded to the underlying element. |
Usage with forms
Pass type="submit" for form submit buttons. The default browser type for <button> is submit, so explicitly set type="button" for non-form actions.
<form onSubmit={handleSubmit}>
<Button type="submit" variant="primary">Save changes</Button>
<Button type="button" variant="tertiary" onClick={onCancel}>Cancel</Button>
</form>Adding an icon
Pass icon elements as children alongside the label text. The button uses display: inline-flex with the gap from the component token, so icons align automatically.
<Button variant="primary">
<svg aria-hidden="true" width="16" height="16" viewBox="0 0 16 16">
<path d="M8 1v14M1 8h14" stroke="currentColor" strokeWidth="2" />
</svg>
Add item
</Button>CSS tokens
Button styles are driven by tokens defined in components.json and compiled to --sprouts-button-* CSS custom properties. You can override individual tokens at the component or page level.
/* Override button primary surface for a specific section */
.my-section {
--sprouts-button-color-primary-surface-default: var(--cornflower-500);
--sprouts-button-color-primary-surface-hover: var(--cornflower-600);
}Overview
Button uses a native <button> element, inheriting the implicit button ARIA role and all associated browser and assistive technology support — no extra markup needed.
Keyboard interactions
| Key | Action |
|---|---|
| Space | Activates the button |
| Enter | Activates the button |
| Tab | Moves focus to the next focusable element |
| Shift + Tab | Moves focus to the previous focusable element |
Focus management
A visible focus ring appears when a keyboard user focuses the button. The ring colour uses --atom-focus-ring-border-default from the component token, which inherits the active intent colour so it remains perceptible against all intent backgrounds.
/* The focus ring is applied via :focus-visible */
.grove-button:focus-visible {
outline: 2px solid var(--seeds-focus-ring-border-default);
outline-offset: 2px;
}Labelling
Button text is the accessible label. For icon-only buttons, add an aria-label and hide the icon from assistive technology with aria-hidden.
/* Text button — label is the text content */
<Button variant="secondary">Export CSV</Button>
/* Icon-only button — label via aria-label */
<Button variant="tertiary" aria-label="Close dialog">
<svg aria-hidden="true" .../>
</Button>Disabled state
Using the native disabled attribute removes the button from the tab order and prevents all interaction. Browsers and screen readers announce the disabled state automatically. If users need to discover why a button is disabled, consider keeping it enabled and showing an error on interaction instead.
Checklist
- Uses a native <button> element — no ARIA role needed
- Activated by both Space and Enter keys
- Visible focus ring for keyboard and switch-device users
- Disabled state uses the native disabled attribute, not aria-disabled
- Button text or aria-label provides a meaningful accessible name
- Icon-only buttons include aria-label; icons are aria-hidden
- type="button" is set for buttons outside a form to prevent accidental submission