# ButtonBase Guides

## 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.

```tsx
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}
    />
  );
}

export default function SharedActionPrimitive() {
  const [bold, setBold] = useState(false);
  const [italic, setItalic] = useState(false);
  const [underline, setUnderline] = useState(false);

  return (
    <div
      style={{
        display: 'flex',
        flexDirection: 'column',
        width: '100%',
        border: '1px solid var(--separator-opaque)',
        borderRadius: 'var(--radius-5)',
        background: 'var(--surface-primary)',
        overflow: 'hidden',
      }}
    >
      <div
        role='toolbar'
        aria-label='Text formatting'
        style={{
          display: 'flex',
          gap: 'var(--spacing-2)',
          padding: 'var(--spacing-3)',
          borderBlockEnd: '1px solid var(--separator-opaque)',
        }}
      >
        <ToolbarAction aria-label='Bold' aria-pressed={bold} appearance={bold ? 'ghost' : 'transparent'} onClick={() => setBold(!bold)}>
          <Icon size='sm'><BoldIcon /></Icon>
        </ToolbarAction>
        <ToolbarAction aria-label='Italic' aria-pressed={italic} appearance={italic ? 'ghost' : 'transparent'} onClick={() => setItalic(!italic)}>
          <Icon size='sm'><ItalicIcon /></Icon>
        </ToolbarAction>
        <ToolbarAction aria-label='Underline' aria-pressed={underline} appearance={underline ? 'ghost' : 'transparent'} onClick={() => setUnderline(!underline)}>
          <Icon size='sm'><UnderlineIcon /></Icon>
        </ToolbarAction>
      </div>
      <p
        style={{
          margin: 0,
          padding: 'var(--spacing-8)',
          fontWeight: bold ? 700 : 400,
          fontStyle: italic ? 'italic' : 'normal',
          textDecoration: underline ? 'underline' : 'none',
        }}
      >
        Release notes for version 2.4
      </p>
    </div>
  );
}
```

### 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.

```tsx
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)',
          padding: 'var(--spacing-3) var(--spacing-8)',
          borderRadius: 'var(--radius-3)',
          fontSize: 'var(--font-size-2)',
        }}
      >
        <Icon size='sm'><UsersIcon /></Icon>
        Manage members
      </a>
    </ButtonBase>
  );
}
```

## 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
- `asChild` is 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 `Enter` and `Space`
- 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.
