# Opt-in native buttons

ASButton keeps its existing ASTappable path when `as` is omitted. `as="button"` selects native button mode; `as="a"` and anchor-compatible components select [link mode](button-links.md). Native mode renders an HTML button directly and uses the same theme resolution and control-state styles. ASTappable and its pointer handling are unchanged.

For the corresponding ASIconButton mode, see [Native icon buttons](native-icon-buttons.md).

## Rendering fork

Choose the rendering mode with `as`; the theme controls its appearance.

```mermaid
flowchart TD
    A["ASButton props"] --> B{"as value"}
    B -->|"omitted"| C["ASTappableButton"]
    C --> D["ASButtonStyled / ASTappable"]
    D --> E["div / pointer events / onTapped"]
    B -->|"button"| F["ASNativeButton"]
    F --> G["ASNativeButtonStyled"]
    G --> H["button / browser activation / onClick"]
    B -->|"a or anchor-compatible component"| I["ASLinkButton"]
    I --> J["anchor / browser or router navigation"]
```

The dispatch lives in [ASButton.tsx](../src/asbutton/ASButton.tsx). The native implementation is [ASNativeButton.tsx](../src/asbutton/ASNativeButton.tsx). The link implementation is [ASLinkButton.tsx](../src/asbutton/ASLinkButton.tsx). Shared surface styling and the three styled roots live in [styles.ts](../src/asbutton/styles.ts).

| Contract             | Default: omit `as`                       | Native: `as="button"`                                  |
| -------------------- | ---------------------------------------- | ------------------------------------------------------ |
| DOM root             | ASTappable's `div`                       | HTML `button`                                          |
| Activation           | Existing pointer handling and debounce   | Browser click activation, including keyboard           |
| Application callback | `onTapped`                               | `onClick`; `onTapped` is disallowed by the native type |
| Form submission      | No native submit semantics               | `type="submit"`; defaults to `type="button"`           |
| Disabled/loading     | Existing inert and ARIA handling         | Native `disabled`, plus ARIA state                     |
| Public props         | `ASButtonProps`                          | `ASNativeButtonProps`                                  |
| Theme                | Provider, replacement, partial overrides | Same resolution and shared control-state styles        |

Native mode uses browser click events instead of ASTappable pointer events and debounce. Changing `as` after mounting replaces the component and resets its local toggle state.

## Usage and event flow

```tsx
<form onSubmit={handleSubmit}>
  <ASButton as="button" type="submit" isLoading={isSaving}>Save</ASButton>
  <ASButton as="button" onClick={handleCancel}>Cancel</ASButton>
</form>
```

- Set `type="submit"` for form submission; the default is `type="button"`.
- Use `onClick` instead of `onTapped`. Native buttons support mouse, touch, Enter, and Space activation.
- Keep submission logic in the form's `onSubmit`. A button click can occur before validation rejects a submission; `onClick` alone does not mean the form was submitted. Calling `preventDefault()` in `onClick` cancels submission.
- `disabled` and `isLoading` both disable the HTML button. Loading also shows a spinner, retains the accessible label and layout, and sets `aria-busy`.
- Button attributes such as `name`, `value`, `form`, `formNoValidate`, and `formAction` pass through. The ref and click event's `currentTarget` refer to an `HTMLButtonElement`.

### Selection and accessibility

Both the default path and native mode support `toggle` and `selected`. In native mode:

- With `toggle`, each click flips the selected state, and ASUI sets `aria-pressed` to match it.
- Without `toggle`, `selected` controls appearance only. If the button represents a pressed/unpressed choice controlled by your application, pass `aria-pressed` explicitly.

```tsx
<ASButton as="button" selected={isEnabled} aria-pressed={isEnabled}
  onClick={() => setIsEnabled(value => !value)}>
  Enable option
</ASButton>
```

### Visual differences from the default path

Native buttons render their text and icons directly. They do not use the default path's duplicated CSS label for the clip-text/label-hover effect. Backgrounds, borders, and focus states still use the shared button theme. If your design relies on that label effect, review its appearance when switching modes.

`ASButtonProps` remains the extendable default-path type. `ASNativeButtonProps` is the separate, extendable native type with button-specific attributes, refs, and event targets. ASButton accepts these shapes and `ASLinkButtonProps`, selected by `as`. See [button-styled links](button-links.md) for link semantics and typing.

## Consumer migration

For an existing submit control, preserve the form's `onSubmit`, field validation, and request state. Replace the control with `as="button" type="submit"` and forward its `name`, `value`, and `form` attributes where used. Keep saving logic in `onSubmit`; a submit button's `onClick` runs before validation/submission and is not proof that the form was submitted.

For an action that does not submit a form, use `as="button" onClick={handler}`. Existing pointer-oriented usages can continue using `onTapped` without changes. Do not mechanically rename every `onTapped` callback: native click events target `HTMLButtonElement`, while the default API retains its pointer-event contract.

## Verification fixture

The Storybook **ASButton → Native behavior → Form And Keyboard** fixture checks required-field validation, exactly-once activation/submission, default button type, cancellation, external form association, disabled/loading behavior, refs, and toggles. Type fixtures also verify existing prop extension and reject native `onTapped` or invalid button types.

Native button support shipped in 2.15.0 and was validated in the Phase 3 portfolio contact form. ASIconButton has a separate API.

## Verification

At the native-button checkpoint, production compilation and 36 regression tests passed. The Storybook TypeScript check includes native/default prop-extension and event/ref type fixtures. The native interaction story has 42 passing checks in Chrome and Safari, including disabled implicit submission from a form field. Real browser key presses in both browsers verified one click and one submission for Enter, Space, and Enter from the form input.
