import { Icon, IconButton } from '@vega-ui/react';
import { Heart, Star } from '@vega-ui/icons';
<div style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
<IconButton aria-label='Add to favorites'><Icon><Heart /></Icon></IconButton>
<IconButton variant='secondary' appearance='ghost' aria-label='Star project'><Icon><Star /></Icon></IconButton>
</div>- Browser support
- Newly available 2024
- Chrome 125
- Edge 125
- Firefox 128
- Safari 18
- Safari on iOS 18
- Bundle size
Summary
IconButton is the icon-only action variant built on the same button foundation as Button. It is the compact action layer when visible text would add noise rather than clarity.
Imports
import {
IconButton,
type IconButtonProps,
type IconButtonSize,
} from '@vega-ui/react';Exported Types
IconButtonPropsIconButtonSize
Basic Usage
import { Icon, IconButton, Text } from '@vega-ui/react';
import { EllipsisIcon, PencilIcon, Share2Icon } from '@vega-ui/icons';
import { useState } from 'react';
export default function BasicUsage() {
const [lastAction, setLastAction] = useState('');
return (
<div style={{ display: 'flex', flexDirection: 'column', gap: 'var(--spacing-2)', width: '100%' }}>
<header style={{ display: 'flex', alignItems: 'center', gap: 'var(--spacing-2)' }}>
<Text size={4} fontWeight={600} style={{ flex: 1, minWidth: 0 }}>Brand guidelines</Text>
<IconButton type='button' size='sm' variant='secondary' appearance='transparent' aria-label='Rename document' onClick={() => setLastAction('Rename')}>Variants
- Sizes:
xs,sm,md,lg,xl - Variants and appearances inherited from the button foundation
- Composition: default button or
asChild
Edge Cases
- Always provide an
aria-labelbecause visible text is usually absent. - Keep icon glyph size visually balanced with the button size token.
- Use regular
Buttonwhen visible text would improve clarity. - If the icon meaning depends on surrounding context too heavily, a text button is usually the safer choice.
Common Mistakes
- Omitting
aria-label. - Putting long text inside
IconButton. - Using icon-only actions where the meaning is not already recognizable.
Examples
Basic
Basic: header actions with explicit names
Every icon-only action needs its own aria-label: the component never adds one. Inside IconButton an Icon without size scales with the button through --icon-size.
import { Icon, IconButton, Text } from '@vega-ui/react';
import { EllipsisIcon, PencilIcon, Share2Icon } from '@vega-ui/icons';
import { useState } from 'react';
export default function BasicUsage() {
const [lastAction, setLastAction] = useState('');
return (
<div style={{ display: 'flex', flexDirection: 'column', gap: 'var(--spacing-2)', width: '100%' }}>
<header style={{ display: 'flex', alignItems: 'center', gap: 'var(--spacing-2)' }}>
<Text size={4} fontWeight={600} style={{ flex: 1, minWidth: 0 }}>Brand guidelines</Text>
<IconButton type='button' size='sm' variant='secondary' appearance='transparent' aria-label='Rename document' onClick={() => setLastAction('Rename')}>Basic: sizes
size sets the square side from xs to xl; the nested icon follows at one third of the button size.
import { Icon, IconButton, type IconButtonSize } from '@vega-ui/react';
import { SettingsIcon } from '@vega-ui/icons';
const sizes: IconButtonSize[] = ['xs', 'sm', 'md', 'lg', 'xl'];
export default function Sizes() {
return (
<div style={{ display: 'flex', alignItems: 'center', gap: 'var(--spacing-4)' }}>
{sizes.map((size) => (
<IconButton key={size} type='button' size={size} aria-label={`Settings, size ${size}`}>
<Icon><SettingsIcon /></Icon>
</IconButton>Controlled/Stateful
Controlled/Stateful: favorite toggle with aria-pressed
The label stays "Favorite" and the state lives in aria-pressed, so a screen reader announces "Favorite, toggle button, pressed". Changing the label on every click would hide the state instead.
import { Icon, IconButton } from '@vega-ui/react';
import { HeartIcon } from '@vega-ui/icons';
import { useState } from 'react';
export default function ParentOwnedFavoriteToggle() {
const [favorite, setFavorite] = useState(false);
const count = favorite ? 129 : 128;
return (
<div style={{ display: 'flex', alignItems: 'center', gap: 'var(--spacing-4)', width: '100%' }}>
<IconButton
type='button'Controlled/Stateful: copy with temporary feedback
The icon swaps to a check for two seconds after copying, and a status region announces "Copied", because an icon change alone is silent for screen readers.
import { Icon, IconButton, VisuallyHidden } from '@vega-ui/react';
import { CheckIcon, CopyIcon } from '@vega-ui/icons';
import { useEffect, useState } from 'react';
const inviteLink = 'https://app.example.com/invite/7Fq2-K9xD';
export default function CopyToClipboard() {
const [copied, setCopied] = useState(false);
useEffect(() => {
if (!copied) return;
Form/Integration
Form/Integration: clear and submit actions next to an input
The clear button uses type='button' so it does not submit the form, and returns focus to the input. The search button is a real type='submit'; the query is read with FormData.
import type { CSSProperties } from 'react';
import { Icon, IconButton, Label, TextField, TextFieldInput } from '@vega-ui/react';
import { SearchIcon, XIcon } from '@vega-ui/icons';
import { FormEvent, useRef, useState } from 'react';
export default function InlineClearAction() {
const input = useRef<HTMLInputElement>(null);
const [query, setQuery] = useState('quarterly report');
const [submitted, setSubmitted] = useState('');
const clear = () => {
setQuery('');Layout/Overlay
Layout/Overlay: close button in a dialog
The standard overlay close: a 24×24 round ghost button inside DialogClose asChild, positioned with logical insets so it flips in right-to-left layouts.
import type { CSSProperties } from 'react';
import {
Button,
Dialog,
DialogClose,
DialogContent,
DialogTrigger,
Heading,
Icon,
IconButton,
Text,
} from '@vega-ui/react';Layout/Overlay: tooltip for sighted users
aria-label names the button for assistive technology; a Tooltip shows the same name and the shortcut to mouse and keyboard users. The tooltip is linked as a description, so keep extra details there.
import { Icon, IconButton, Tooltip, TooltipArrow, TooltipContent, TooltipTrigger } from '@vega-ui/react';
import { AlignCenterIcon, AlignLeftIcon, AlignRightIcon } from '@vega-ui/icons';
import { type ReactNode, useState } from 'react';
const alignments: { value: string, label: string, shortcut: string, icon: ReactNode }[] = [
{ value: 'left', label: 'Align left', shortcut: 'Ctrl+Shift+L', icon: <AlignLeftIcon /> },
{ value: 'center', label: 'Align center', shortcut: 'Ctrl+Shift+E', icon: <AlignCenterIcon /> },
{ value: 'right', label: 'Align right', shortcut: 'Ctrl+Shift+R', icon: <AlignRightIcon /> },
];
export default function TooltipLabel() {
const [alignment, setAlignment] = useState('left');Error
Error: remove action that fails
Removing a locked file fails: the error is rendered under the row and linked to its button with aria-describedby. The accessible name includes the file name, so ten identical trash icons are still distinguishable.
import { HelperText, Icon, IconButton } from '@vega-ui/react';
import { FileTextIcon, Trash2Icon } from '@vega-ui/icons';
import { useState } from 'react';
const initialFiles = [
{ name: 'contract-signed.pdf', locked: true },
{ name: 'brief-v3.docx', locked: false },
{ name: 'moodboard.png', locked: false },
];
export default function FailedRemoveAction() {
const [files, setFiles] = useState(initialFiles);Disabled
Disabled: toolbar disabled by a fieldset
<Fieldset disabled> disables every IconButton inside it natively, which suits read-only documents where the whole toolbar is unavailable at once.
import { Button, Fieldset, FieldsetLegend, Icon, IconButton } from '@vega-ui/react';
import { BoldIcon, ItalicIcon, LinkIcon, ListIcon } from '@vega-ui/icons';
import { useState } from 'react';
export default function BlockedIconAction() {
const [locked, setLocked] = useState(true);
return (
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'flex-start', gap: 'var(--spacing-8)', width: '100%' }}>
<Fieldset disabled={locked} appearance='outlined' style={{ width: '100%', boxSizing: 'border-box' }}>
<FieldsetLegend>{locked ? 'Formatting (read-only document)' : 'Formatting'}</FieldsetLegend>
<div style={{ display: 'flex', gap: 'var(--spacing-2)' }}>Edge
Edge: zoom controls at their limits
The buttons are disabled at the first and last zoom step, computed from the list of steps. When a button disables itself while focused, the browser drops focus, so place the value between the buttons where the user can still see it.
import { Icon, IconButton } from '@vega-ui/react';
import { MinusIcon, PlusIcon } from '@vega-ui/icons';
import { useState } from 'react';
const steps = [25, 50, 75, 100, 150, 200, 400];
export default function ZoomControls() {
const [index, setIndex] = useState(3);
const atMin = index === 0;
const atMax = index === steps.length - 1;
return (Anatomy
Overview
IconButton is a single icon-only action surface built on ButtonBase. Its anatomy is one interactive root plus icon content with no visible text label.
Required Parts
IconButton
Required. Owns size and button-base visual props such as variant and appearance.
Optional Parts
IconButton does not expose public child parts, but consumers usually compose it with Icon.
Composition Order
IconButton- icon content
Valid Composition Patterns
import { Icon, IconButton, Text } from '@vega-ui/react';
import { EllipsisIcon, PencilIcon, Share2Icon } from '@vega-ui/icons';
import { useState } from 'react';
export default function BasicUsage() {
const [lastAction, setLastAction] = useState('');
return (
<div style={{ display: 'flex', flexDirection: 'column', gap: 'var(--spacing-2)', width: '100%' }}>
<header style={{ display: 'flex', alignItems: 'center', gap: 'var(--spacing-2)' }}>
<Text size={4} fontWeight={600} style={{ flex: 1, minWidth: 0 }}>Brand guidelines</Text>
<IconButton type='button' size='sm' variant='secondary' appearance='transparent' aria-label='Rename document' onClick={() => setLastAction('Rename')}>Invalid Composition Patterns
Icon-only button without accessible label
The action becomes unclear for assistive technology and often for sighted users too.
Long text content inside IconButton
If the action needs visible words, use Button instead.