Patterns
Primary Form Action
When to use:
- A form needs one clear primary action.
Composition notes:
- Keep the primary action visually stronger than nearby secondary actions.
- Use
type='submit'deliberately.
Trade-offs:
- Strong action clarity.
- Can become visually noisy if too many buttons compete for emphasis.
import { Button, Label, TextField, TextFieldInput } from '@vega-ui/react';
import { FormEvent, useState } from 'react';
export default function FormFooterActions() {
const [submitted, setSubmitted] = useState('');
const handleSubmit = (event: FormEvent<HTMLFormElement>) => {
event.preventDefault();
const data = new FormData(event.currentTarget);
setSubmitted(`${data.get('workspace')} (${data.get('slug')})`);
};
Secondary Action Pair
When to use:
- A local flow needs primary and secondary actions side by side.
Composition notes:
- Use a lower-emphasis treatment for cancel or back actions.
- Keep the button texts complementary rather than ambiguous.
Trade-offs:
- Easy to scan.
- Can become confusing if both buttons use the same emphasis.
import { Button } from '@vega-ui/react';
export default function ActionHierarchy() {
return (
<div style={{ display: 'flex', flexDirection: 'column', gap: 'var(--spacing-4)', width: '100%' }}>
<Button type='button' fullWidth>Publish article</Button>
<Button type='button' fullWidth variant='secondary' appearance='ghost'>Save draft</Button>
<Button type='button' fullWidth variant='secondary' appearance='ghost'>Preview</Button>
<Button type='button' fullWidth variant='secondary' appearance='transparent'>Discard</Button>
</div>
);
}Navigation With Button Visuals
When to use:
- Navigation needs to look like a button while staying semantically navigational.
Composition notes:
- Use
asChildwith a real anchor. - Preserve link semantics when the interaction truly navigates.
Trade-offs:
- Strong visual emphasis for navigation.
- Easy to misuse when a plain link would be clearer.
import { Button, Icon } from '@vega-ui/react';
import { ArrowRightIcon, ExternalLinkIcon } from '@vega-ui/icons';
import './examples.css';
export default function NavigationWithButtonVisuals() {
return (
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 'var(--spacing-4)' }}>
<Button asChild>
<a href='/onboarding/import'>
Import contacts
<Icon size='sm' className='directionalIcon'><ArrowRightIcon /></Icon>
</a>Comparison
Quick Decision Rule
Use Button for explicit actions initiated by the user.
Button vs Link
Use Button when:
- the action mutates state or submits data
Use Link when:
- the interaction primarily navigates
Main trade-off: button is action semantics, while link is navigation semantics.
Button vs IconButton
Use Button when:
- visible text improves clarity
Use IconButton when:
- the action is compact and already recognizable as an icon-only control
Main trade-off: button is clearer, while icon button is denser.
Button vs ButtonBase
Use Button when:
- product code needs a standard text action surface
Use ButtonBase when:
- a lower-level action primitive is needed for custom composition
Choose This Component When
- The primary job is action, not navigation.
- Visible text helps users understand the action.
- The UI benefits from a standard button surface.
- the component should be the default action choice rather than a low-level primitive
Do Not Choose This Component When
- The primary job is navigation.
- The action is icon-only and space-constrained.
- The interaction is really a text link in running copy.
Troubleshooting
Button Submits The Form Unexpectedly
Symptom
The button is inside a form, and clicking it submits the form.
Likely Cause
Button has no default type, so like a native <button> it is a submit button inside a form.
How To Verify
- Inspect the rendered
type. - Check whether the button was meant to submit the form.
Fix
- Set
type='button'when the button should not submit the form.
Polymorphic Button Behaves Strangely
Symptom
The control renders, but the child element does not support the forwarded props as expected.
Likely Cause
asChild was used with an incompatible child element.
How To Verify
- Inspect the rendered child element.
- Check whether it supports the intended props and semantics.
Fix
- Use a child that matches the intended semantics, such as an anchor for navigation.
- Keep plain button rendering when polymorphism is unnecessary.
Button Should Probably Be A Link
Symptom
The control works visually, but the interaction is really navigation.
Likely Cause
Button was chosen for a navigation pattern that should keep link semantics.
How To Verify
- Ask whether the primary job is action or navigation.
- Compare the flow with
LinkorButton asChild.
Fix
- Use
Linkfor plain text navigation. - Use
Button asChildwith a real anchor when button visuals are still needed.