import { ButtonBase } from '@vega-ui/react';
<ButtonBase type='button' variant='secondary' appearance='ghost'>Reset filters</ButtonBase>- Browser support
- Newly available 2024
- Chrome 125
- Edge 125
- Firefox 128
- Safari 18
- Safari on iOS 18
- Bundle size
Summary
ButtonBase is the low-level action primitive behind Button and IconButton. It owns the shared button styling contract, focus treatment, and asChild composition without adding higher-level product layout rules.
Imports
import {
ButtonBase,
type ButtonBaseAppearance,
type ButtonBaseProps,
type ButtonBaseVariant,
} from '@vega-ui/react/ButtonBase';Exported Types
ButtonBasePropsButtonBaseVariant = 'primary' | 'secondary' | stringButtonBaseAppearance = 'fill' | 'outline' | 'ghost' | 'transparent' | string
Basic Usage
import { ButtonBase } from '@vega-ui/react';
import { useState } from 'react';
export default function BasicUsage() {
const [likes, setLikes] = useState(24);
return (
<ButtonBase
type='button'
variant='secondary'
appearance='ghost'
onClick={() => setLikes(likes + 1)}Variants
- Variant:
primary,secondary - Appearance:
fill,outline,ghost,transparent - Rendering mode: native button or
asChild
Edge Cases
typeshould be set explicitly for form actions.- If
asChildis used, the child must accept the forwarded props and keep correct semantics. - Icon-only content still needs an accessible name.
Common Mistakes
- Using
ButtonBasewhenButtonorIconButtonwould be clearer. - Forgetting that link semantics remain link semantics under
asChild. - Treating unsupported variant strings as if CSS already implements them.
Examples
Basic
Basic: variants and appearances
ButtonBase maps variant and appearance to colors and state styles but has no height or padding. Every example here adds its own spacing, which is exactly what Button and IconButton do on top of it.
import { ButtonBase, type ButtonBaseAppearance, type ButtonBaseVariant } from '@vega-ui/react';
const variants: ButtonBaseVariant[] = ['primary', 'secondary'];
const appearances: ButtonBaseAppearance[] = ['fill', 'outline', 'ghost', 'transparent'];
export default function VariantAppearanceMatrix() {
return (
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(2, minmax(0, 1fr))', gap: 'var(--spacing-4)', width: '100%' }}>
{appearances.map((appearance) => variants.map((variant) => (
<ButtonBase
key={`${variant}-${appearance}`}
type='button'Controlled/Stateful
Controlled/Stateful: filter chips with aria-pressed
Pill-shaped chips are a typical custom surface built on ButtonBase. Each chip is a toggle with aria-pressed, the parent owns the selected filters, and the list below reacts to them.
import { ButtonBase } from '@vega-ui/react';
import { useState } from 'react';
const tasks = [
{ title: 'Draft Q3 roadmap', status: 'open' },
{ title: 'Review onboarding copy', status: 'review' },
{ title: 'Fix billing export', status: 'open' },
{ title: 'Ship dark theme', status: 'done' },
{ title: 'Audit color contrast', status: 'review' },
];
const filters = ['open', 'review', 'done'];Form/Integration
Form/Integration: explicit button types in a search form
ButtonBase renders a native <button>, whose default type is submit. The Clear action sets type='button' so it does not submit the form; Search submits it and the query is read with FormData.
import { ButtonBase, TextField, TextFieldInput } from '@vega-ui/react';
import { FormEvent, useState } from 'react';
const actionStyle = {
flexShrink: 0,
paddingInline: 'var(--spacing-8)',
borderRadius: 'var(--radius-3)',
fontSize: 'var(--font-size-2)',
};
export default function SubmitButtonWithExplicitType() {
const [query, setQuery] = useState('');Layout/Overlay
Layout/Overlay: full-row actions in a settings list
ButtonBase does not impose layout, so a whole list row can become one action with an icon, two lines of text, and a chevron. textAlign: 'start' and width: '100%' turn the centered button into a row.
import { ButtonBase, Icon } from '@vega-ui/react';
import { BellIcon, ChevronRightIcon, LockIcon, UserIcon } from '@vega-ui/icons';
import { type ReactNode, useState } from 'react';
import './examples.css';
const rows: { id: string, label: string, hint: string, icon: ReactNode }[] = [
{ id: 'profile', label: 'Profile', hint: 'Name, photo, bio', icon: <UserIcon /> },
{ id: 'security', label: 'Security', hint: 'Password and two-factor', icon: <LockIcon /> },
{ id: 'notifications', label: 'Notifications', hint: 'Email and push', icon: <BellIcon /> },
];
export default function ListRowActions() {Disabled
Disabled: blocked while permissions are loading
The action stays disabled until the permission check resolves. The reason is shown in a status next to the button, because a disabled button cannot be focused to read a description.
import { ButtonBase, Spinner } from '@vega-ui/react';
import { useEffect, useState } from 'react';
export default function BlockedActionWhilePermissionsAreLoading() {
const [canTransfer, setCanTransfer] = useState<boolean | null>(null);
useEffect(() => {
const timer = setTimeout(() => setCanTransfer(true), 2000);
return () => clearTimeout(timer);
}, []);
const loading = canTransfer === null;Edge
Edge: custom variant string
variant accepts any string, but only primary and secondary have CSS. A custom value such as danger gets no colors until you set the --button-* variables yourself; its disabled look also needs your own CSS.
import type { CSSProperties } from 'react';
import { ButtonBase } from '@vega-ui/react';
import { useState } from 'react';
const danger = {
'--button-color': 'var(--color-white)',
'--button-color-hover': 'var(--color-white)',
'--button-color-active': 'var(--color-white)',
'--button-background-color': 'var(--color-red-accent-500)',
'--button-background-color-hover': 'var(--color-red-accent-600)',
'--button-background-color-active': 'var(--color-red-accent-700)',
padding: 'var(--spacing-3) var(--spacing-8)',Anatomy
Overview
ButtonBase is a single-part action primitive. It does not export structural child parts, but it can hand its behavior and styling to another element through asChild.
Required Parts
ButtonBase: root interactive element that owns styling, focus ring, disabled behavior, and data attributes
Optional Parts
- slotted child element via
asChild: use when the final semantic element should be a link or router primitive instead of a nativebutton
Composition Order
ButtonBase- optional child element when
asChildis enabled
Valid Composition Patterns
- native button for actions, submits, and resets
asChildwith one anchor or router link when the action must navigate- text-only, icon-only, or mixed content as long as the final accessible name is clear
Invalid Composition Patterns
- multiple direct children when relying on a single slotted semantic target
asChildwith a child that does not accept forwarded props- treating
ButtonBaseas a complete product button whenButtonorIconButtonis the clearer public API