Skip to content
v2.5.0
English
Русский
Documentation

Icon

Icon is the shared SVG wrapper for VegaUI icons and custom SVG content.
icon.tsx · 14 lines
import { Icon } from '@vega-ui/react';
import {
  Bell,
  Check,
  Heart,
  Star,
} from '@vega-ui/icons';

<div>
  <Icon><Check /></Icon>
  <Icon><Heart /></Icon>
  <Icon><Star /></Icon>
Browser support
Widely available
  • Chrome 111
  • Edge 111
  • Firefox 113
  • Safari 15.4
  • Safari on iOS 15.4
Bundle size
Tested in
6 tests

Summary

Icon is the shared SVG wrapper for VegaUI icons and custom SVG content. It normalizes size through tokens and renders through Slot, which lets the child SVG receive the merged props directly.

Imports

import { Icon, type IconProps, type IconSize } from '@vega-ui/react';

Exported Types

  • IconProps
  • IconSize

Basic Usage

basic-usage.tsx · 21 lines
import { Icon } from '@vega-ui/react';
import { CalendarIcon, MessageSquareIcon, PaperclipIcon } from '@vega-ui/icons';

export default function BasicUsage() {
  return (
    <ul style={{ display: 'flex', flexWrap: 'wrap', gap: 'var(--spacing-4) var(--spacing-10)', margin: 0, padding: 0, listStyle: 'none', color: 'var(--label-secondary)' }}>
      <li style={{ display: 'flex', alignItems: 'center', gap: 'var(--spacing-3)' }}>
        <Icon size='sm'><CalendarIcon /></Icon>
        Due May 14
      </li>
      <li style={{ display: 'flex', alignItems: 'center', gap: 'var(--spacing-3)' }}>
        <Icon size='sm'><MessageSquareIcon /></Icon>

Variants

  • Size: 4xs, 3xs, 2xs, xs, sm, md, lg, xl
  • Rendering: token size or explicit width / height
  • Content: VegaUI icon, custom SVG, or third-party SVG component

Edge Cases

  • Explicit width or height bypasses the token size data attribute.
  • Icon sets aria-hidden='true' by default, which is correct for decorative usage but not for standalone semantic graphics.
  • stroke='currentColor' means icon color comes from surrounding text or control color.

Common Mistakes

  • Using icon color alone to express state.
  • Letting icon size drift away from the surrounding control density.
  • Assuming every icon should be announced to assistive technology.

Examples

Basic

Basic: icons beside text

Icon adds aria-hidden='true' and stroke='currentColor' to its child SVG, so a glyph next to text is decorative and takes the text color of its parent.

info-row.tsx · 21 lines
import { Icon, Text } from '@vega-ui/react';
import { CircleCheckIcon, ShieldIcon, TriangleAlertIcon } from '@vega-ui/icons';

export default function InfoRow() {
  return (
    <ul style={{ display: 'flex', flexDirection: 'column', gap: 'var(--spacing-4)', margin: 0, padding: 0, listStyle: 'none', width: '100%' }}>
      <li style={{ display: 'flex', alignItems: 'flex-start', gap: 'var(--spacing-4)' }}>
        <Icon size='sm' style={{ flexShrink: 0, marginBlockStart: 'var(--spacing-3)', color: 'var(--color-green-accent-500)' }}><CircleCheckIcon /></Icon>
        <Text size={3}>SSO is enabled for this workspace</Text>
      </li>
      <li style={{ display: 'flex', alignItems: 'flex-start', gap: 'var(--spacing-4)' }}>
        <Icon size='sm' style={{ flexShrink: 0, marginBlockStart: 'var(--spacing-3)', color: 'var(--color-primary-500)' }}><ShieldIcon /></Icon>

Basic: size scale

size maps to fixed pixel sizes from 4xs (8px) to xl (28px). Pick the size that matches the text or control next to the icon.

sizes.tsx · 17 lines
import { Icon, type IconSize } from '@vega-ui/react';
import { StarIcon } from '@vega-ui/icons';

const sizes: IconSize[] = ['4xs', '3xs', '2xs', 'xs', 'sm', 'md', 'lg', 'xl'];

export default function Sizes() {
  return (
    <div style={{ display: 'flex', alignItems: 'flex-end', gap: 'var(--spacing-8)' }}>
      {sizes.map((size) => (
        <figure key={size} style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 'var(--spacing-3)', margin: 0 }}>
          <Icon size={size}><StarIcon /></Icon>
          <figcaption style={{ color: 'var(--label-secondary)', fontSize: 'var(--font-size-1)' }}>{size}</figcaption>

Controlled/Stateful

Controlled/Stateful: disclosure chevron

The chevron rotates with CSS when the section opens; the state itself is exposed by aria-expanded on the button, never by the icon.

disclosure-chevron.tsx · 29 lines
import { Button, Icon, Text } from '@vega-ui/react';
import { ChevronDownIcon } from '@vega-ui/icons';
import { useState } from 'react';
import './examples.css';

export default function DisclosureChevron() {
  const [expanded, setExpanded] = useState(false);

  return (
    <div style={{ display: 'flex', flexDirection: 'column', alignItems: 'flex-start', gap: 'var(--spacing-4)', width: '100%' }}>
      <Button
        type='button'

Form/Integration

Form/Integration: leading icon inside a text field

TextField is a flex container, so an Icon placed before TextFieldInput becomes an adornment. The field keeps its visible Label; the icon only decorates.

input-adornment.tsx · 25 lines
import { Icon, Label, TextField, TextFieldInput } from '@vega-ui/react';
import { AtSignIcon } from '@vega-ui/icons';
import { FormEvent, useState } from 'react';

export default function InputAdornment() {
  const [handle, setHandle] = useState('');

  const handleSubmit = (event: FormEvent<HTMLFormElement>) => {
    event.preventDefault();
    setHandle(String(new FormData(event.currentTarget).get('handle')));
  };

Layout/Overlay

Layout/Overlay: icons inside buttons

Leading and trailing icons sit in the button's flex gap. Inside Button the icon does not scale automatically, so set size to match the button size.

icon-inside-a-button.tsx · 21 lines
import { Button, Icon } from '@vega-ui/react';
import { ChevronDownIcon, DownloadIcon, PlusIcon } from '@vega-ui/icons';

export default function IconInsideAButton() {
  return (
    <div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 'var(--spacing-4)' }}>
      <Button type='button'>
        <Icon size='sm'><PlusIcon /></Icon>
        Add member
      </Button>
      <Button type='button' variant='secondary' appearance='ghost'>
        Export

Error

Error: icon in a validation message

The field validates on blur. The error text, not the icon, carries the meaning; the icon inherits the error color from HelperText and the message is linked with aria-describedby.

error-message-icon.tsx · 39 lines
import { HelperText, Icon, Label, TextField, TextFieldInput } from '@vega-ui/react';
import { CircleAlertIcon } from '@vega-ui/icons';
import { useState } from 'react';

export default function ErrorMessageIcon() {
  const [error, setError] = useState('');

  const validate = (input: HTMLInputElement) => {
    if (input.validity.valid) {
      setError('');
      return;
    }

Disabled

Disabled: icons in disabled controls

Because the glyph uses currentColor, it takes the disabled text color of its button without extra styles. The same works for muted helper copy.

muted-control-icon.tsx · 22 lines
import { Button, Icon, IconButton } from '@vega-ui/react';
import { LockIcon, Trash2Icon, UploadIcon } from '@vega-ui/icons';

export default function MutedControlIcon() {
  return (
    <div style={{ display: 'flex', flexDirection: 'column', alignItems: 'flex-start', gap: 'var(--spacing-6)', width: '100%' }}>
      <div style={{ display: 'flex', gap: 'var(--spacing-4)' }}>
        <Button type='button' disabled>
          <Icon size='sm'><UploadIcon /></Icon>
          Upload logo
        </Button>
        <IconButton type='button' variant='secondary' appearance='ghost' aria-label='Delete logo' disabled>

Edge

Edge: icon as the only status signal

In a status column there is no visible text, and the icon stays aria-hidden. A VisuallyHidden span gives screen readers the status in words.

standalone-meaningful-icon.tsx · 39 lines
import { Icon, VisuallyHidden } from '@vega-ui/react';
import { CircleCheckIcon, CircleXIcon } from '@vega-ui/icons';

const checks = [
  { name: 'Unit tests', passed: true },
  { name: 'Lint', passed: true },
  { name: 'Visual regression', passed: false },
];

export default function StandaloneMeaningfulIcon() {
  return (
    <table style={{ width: '100%', borderCollapse: 'collapse' }}>

Edge: directional icons in right-to-left layouts

Arrows point in the reading direction, so in dir='rtl' they must be mirrored. Flip only directional glyphs; icons like a check or a clock stay as they are.

rtl-directional-icon.tsx · 21 lines
import { Button, Icon } from '@vega-ui/react';
import { ArrowRightIcon } from '@vega-ui/icons';

export default function RtlDirectionalIcon() {
  return (
    <div style={{ display: 'flex', flexDirection: 'column', alignItems: 'flex-start', gap: 'var(--spacing-4)' }}>
      <div dir='ltr'>
        <Button type='button'>
          Continue
          <Icon size='sm'><ArrowRightIcon /></Icon>
        </Button>
      </div>

Edge: size outside the scale

For an illustration-sized icon, set --icon-size directly. The width and height props are not forwarded to the SVG at the moment, so do not rely on them.

custom-icon-size.tsx · 17 lines
import type { CSSProperties } from 'react';
import { Heading, Icon, Text } from '@vega-ui/react';
import { InboxIcon } from '@vega-ui/icons';

export default function CustomIconSize() {
  return (
    <div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 'var(--spacing-8)', width: '100%', textAlign: 'center' }}>
      <Icon strokeWidth={1.25} style={{ '--icon-size': '64px', color: 'var(--label-tertiary)' } as CSSProperties}>
        <InboxIcon />
      </Icon>
      <div style={{ display: 'flex', flexDirection: 'column', gap: 'var(--spacing-2)' }}>
        <Heading as='h3' size={4} fontWeight={600}>Inbox zero</Heading>

Anatomy

Overview

Icon is a single-part primitive that passes sizing and SVG props into its child through Slot.

Required Parts

Icon

Required. Owns the shared size token contract and SVG prop forwarding.

Optional Parts

There are no exported child parts.

Composition Order

  1. Icon
  2. one SVG child

Valid Composition Patterns

basic-usage.tsx · 21 lines
import { Icon } from '@vega-ui/react';
import { CalendarIcon, MessageSquareIcon, PaperclipIcon } from '@vega-ui/icons';

export default function BasicUsage() {
  return (
    <ul style={{ display: 'flex', flexWrap: 'wrap', gap: 'var(--spacing-4) var(--spacing-10)', margin: 0, padding: 0, listStyle: 'none', color: 'var(--label-secondary)' }}>
      <li style={{ display: 'flex', alignItems: 'center', gap: 'var(--spacing-3)' }}>
        <Icon size='sm'><CalendarIcon /></Icon>
        Due May 14
      </li>
      <li style={{ display: 'flex', alignItems: 'center', gap: 'var(--spacing-3)' }}>
        <Icon size='sm'><MessageSquareIcon /></Icon>

Invalid Composition Patterns

Non-SVG child content

The wrapper is meant to forward SVG-like props and sizing.

Standalone meaningful icon with no accompanying label or context

The default aria-hidden behavior may make the meaning inaccessible.