Patterns
Shared Action Primitive
When to use: Build a product-specific action wrapper that still inherits VegaUI button styling.
Composition notes:
Wrap ButtonBase in a local component and keep variant, appearance, and type explicit.
Trade-offs: You gain flexibility, but also take responsibility for accessible naming, layout, and semantic correctness.
import { ButtonBase, type ButtonBaseProps, Icon } from '@vega-ui/react';
import { BoldIcon, ItalicIcon, UnderlineIcon } from '@vega-ui/icons';
import { useState } from 'react';
function ToolbarAction({ style, ...props }: ButtonBaseProps) {
return (
<ButtonBase
type='button'
variant='secondary'
appearance='transparent'
style={{ display: 'flex', padding: 'var(--spacing-3)', borderRadius: 'var(--radius-2)', ...style }}
{...props}Polymorphic Navigation Action
When to use: Render a navigation target that should visually look like a button.
Composition notes:
Use asChild with exactly one anchor or router link child.
Trade-offs: The result keeps link semantics, so keyboard behavior and announcements differ from a native button.
import { ButtonBase, Icon } from '@vega-ui/react';
import { UsersIcon } from '@vega-ui/icons';
export default function PolymorphicNavigationAction() {
return (
<ButtonBase asChild variant='secondary' appearance='ghost'>
<a
href='/settings/members'
style={{
display: 'inline-flex',
alignItems: 'center',
gap: 'var(--spacing-4)',Comparison
Quick Decision Rule
Choose ButtonBase only when the product needs the lowest shared action primitive. For normal product buttons, prefer Button or IconButton.
ButtonBase vs Button
Use ButtonBase when you need raw action styling with custom structure.
Use Button when product code should express a standard text button with the higher-level public API.
ButtonBase vs IconButton
Use ButtonBase when icon-only layout is custom or not the main intent.
Use IconButton when the action is primarily icon-first and should follow the icon-button size contract.
ButtonBase vs Link
Use ButtonBase when the interaction is an action.
Use Link when the interaction is navigation and should keep link semantics.
Choose This Component When
- a custom action component still needs VegaUI button styling
asChildis necessary for a router or anchor primitive- higher-level button layout APIs would get in the way
Do Not Choose This Component When
- the product just needs a standard button
- the interaction is clearly a text link
- consumers should not have to reason about low-level semantics and
type
Troubleshooting
The Button Submits A Form Unexpectedly
Symptom
Clicking the action submits the surrounding form even though the UI intended a normal button.
Likely Cause
type was not set explicitly and the browser default behaved like a submit action in that context.
How To Verify
- render the button inside the real form
- click it without any submit-specific intent
- confirm whether the form submit handler fires
Fix
Set type='button' for non-submit actions.
The Action Looks Right But Keyboard Semantics Feel Wrong
Symptom
The element looks like a button, but keyboard behavior or screen-reader output follows link semantics.
Likely Cause
asChild rendered an anchor or router link, so the final element kept link semantics.
How To Verify
- inspect the final DOM tag
- test keyboard activation with
EnterandSpace - check screen-reader announcements
Fix
Use a native button when the interaction is an action, not navigation.
Icon-Only Content Is Not Announced Clearly
Symptom
The action is visible, but assistive technology does not expose a meaningful name.
Likely Cause
The button only contains an icon and no aria-label was provided.
How To Verify
- inspect the accessible name in browser accessibility tools
- test the button with a screen reader
Fix
Add an explicit aria-label or visible text content.