Skip to content

Icon Button

Experimental Alpha

This library is a work-in-progress. We are releasing it early to gather feedback, but it is not ready for production.

An accessible icon-only button component that displays a single icon with proper accessibility labeling. Icon buttons are ideal for toolbars, navigation, and actions where space is limited or the icon alone clearly conveys the action.

TIP

If you don't want SVG scaling, consider placing SVG directly in an ag-button or other solution.

Examples

Vue
Lit
React
Live Preview

Basic Icon Buttons

Variants

Scaled Icons

Project raw SVGs directly into the button and prefer em-based sizing so icons scale with the button size.

  • Place the SVG directly inside <ag-icon-button> (don't wrap it).
  • Prefer one of two patterns for slotted SVGs:
    • em-based sizing so the SVG scales with the icon font-size: ag-icon-button::slotted(svg) { width: 1em; height: 1em; }
    • or let the SVG fill its container: width: 100%; height: 100%;
  • Adjust per-size tokens when needed: --ag-icon-button-font-size-* (icon) and --ag-icon-button-* (button dimensions).
Advanced

The icon container uses 1em by default and follows the per-size token values. If a projected SVG sets explicit pixel width/height, the SVG will honor those values. Prefer passing a size prop to icon libraries (e.g. lucide) when available.

Example overrides: ag-icon-button { --ag-icon-button-font-size-md: 1rem } or ag-icon-button[size="xl"] { --ag-icon-button-font-size-xl: 1.25rem; --ag-icon-button-xl: 3.5rem; }

Scaled Icons

Project raw SVGs directly into the button and prefer em-based sizing so icons scale with the button size.

  • Place the SVG directly inside <ag-icon-button> (don't wrap it).
  • Make slotted SVGs follow the icon font-size: ag-icon-button::slotted(svg){width:1em;height:1em} or ensure the SVG fills the icon container by setting width:100%; height:100% on the SVG (via attributes or CSS).
  • Adjust per-size tokens when needed: --ag-icon-button-font-size-* (icon) and --ag-icon-button-* (button dimensions).
Advanced

Example overrides: ag-icon-button { --ag-icon-button-font-size-md: 1rem } or ag-icon-button[size="xl"] { --ag-icon-button-font-size-xl: 1.25rem; --ag-icon-button-xl: 3.5rem; }

Toggle Buttons

CSS Parts Customization

Customize icon button appearance using CSS Shadow Parts without breaking encapsulation. The following are meant solely to show how to utilize CSS shadow parts to create custom toggle styles. They are NOT meant to represent best practices or good taste in toggle design!

View Vue Code
<template>
  <section>
    <div class="mbe4">
      <h2>Basic Icon Buttons</h2>
    </div>
    <div class="stacked-mobile mbe4">
      <VueIconButton label="Settings">
        <Settings
          :size="18"
          class="expand"
        />
      </VueIconButton>
      <VueIconButton label="Search">
        <Search
          :size="18"
          class="expand"
        />
      </VueIconButton>
      <VueIconButton label="Edit">
        <Edit
          :size="18"
          class="expand"
        />
      </VueIconButton>
      <VueIconButton label="Delete">
        <Trash2
          :size="18"
          class="expand"
        />
      </VueIconButton>
    </div>
    <div class="flex-inline mbe4">
      <h2>Variants</h2>
    </div>
    <div class="stacked-mobile mbe4">
      <VueIconButton
        label="Ghost (default)"
        variant="ghost"
      >
        <Star
          :size="18"
          class="expand"
        />
      </VueIconButton>
      <VueIconButton
        label="Primary"
        variant="primary"
      >
        <Star class="expand" />
      </VueIconButton>
      <VueIconButton
        label="Secondary"
        variant="secondary"
      >
        <Star class="expand" />
      </VueIconButton>
      <VueIconButton
        label="Success"
        variant="success"
      >
        <Check class="expand" />
      </VueIconButton>
      <VueIconButton
        label="Warning"
        variant="warning"
      >
        <AlertTriangle class="expand" />
      </VueIconButton>
      <VueIconButton
        label="Danger"
        variant="danger"
      >
        <Trash2 class="expand" />
      </VueIconButton>
      <VueIconButton
        label="Monochrome"
        variant="monochrome"
      >
        <Star class="expand" />
      </VueIconButton>
    </div>

    <div class="mbe4">
      <h2>Scaled Icons</h2>
      <p class="mbe1">Project raw SVGs directly into the button and prefer em-based sizing so icons scale with the button size.</p>
      <ul>
        <li>Place the SVG directly inside <code>&lt;ag-icon-button&gt;</code> (don't wrap it).</li>
        <li>
          Prefer one of two patterns for slotted SVGs:
          <ul>
            <li>em-based sizing so the SVG scales with the icon font-size: <code>ag-icon-button::slotted(svg) { width: 1em; height: 1em; }</code></li>
            <li>or let the SVG fill its container: <code>width: 100%; height: 100%;</code></li>
          </ul>
        </li>
        <li>Adjust per-size tokens when needed: <code>--ag-icon-button-font-size-*</code> (icon) and <code>--ag-icon-button-*</code> (button dimensions).</li>
      </ul>
      <details>
        <summary>Advanced</summary>
        <p>The icon container uses <code>1em</code> by default and follows the per-size token values. If a projected SVG sets explicit pixel <code>width/height</code>, the SVG will honor those values. Prefer passing a <code>size</code> prop to icon libraries (e.g. lucide) when available.</p>
        <p>Example overrides: <code>ag-icon-button { --ag-icon-button-font-size-md: 1rem }</code> or <code>ag-icon-button[size="xl"] { --ag-icon-button-font-size-xl: 1.25rem; --ag-icon-button-xl: 3.5rem; }</code></p>
      </details>
    </div>
    <div class="mbe4">
      <VueIconButton
        label="Extra Small"
        size="xs"
      >
        <Heart
          :size="14"
          class="expand"
        />
      </VueIconButton>
      <VueIconButton
        label="Small"
        size="sm"
      >
        <Heart class="expand" />
      </VueIconButton>
      <VueIconButton
        label="Medium"
        size="md"
      >
        <Heart class="expand" />
      </VueIconButton>
      <VueIconButton
        label="Large"
        size="lg"
      >
        <Heart class="expand" />
      </VueIconButton>
      <VueIconButton
        label="Extra Large"
        size="xl"
      >
        <Heart class="expand" />
      </VueIconButton>
    </div>

    <div class="mbe4">
      <VueIconButton
        label="Extra Small"
        variant="monochrome"
        size="xs"
        class="mie4"
      >
        <Star class="expand" />
      </VueIconButton>
      <VueIconButton
        label="Small"
        variant="monochrome"
        size="sm"
        class="mie4"
      >
        <Star class="expand" />
      </VueIconButton>
      <VueIconButton
        label="Medium"
        variant="monochrome"
        size="md"
        class="mie4"
      >
        <Star class="expand" />
      </VueIconButton>
      <VueIconButton
        label="Large"
        variant="monochrome"
        size="lg"
        class="mie4"
      >
        <Star class="expand" />
      </VueIconButton>
      <VueIconButton
        label="Extra Large"
        variant="monochrome"
        size="xl"
      >
        <Star class="expand" />
      </VueIconButton>
    </div>
    <div class="mbe4">
      <h2>Scaled Icons</h2>
      <p class="mbe1">Project raw SVGs directly into the button and prefer em-based sizing so icons scale with the button size.</p>
      <ul>
        <li>Place the SVG directly inside <code>&lt;ag-icon-button&gt;</code> (don't wrap it).</li>
        <li>Make slotted SVGs follow the icon font-size: <code>ag-icon-button::slotted(svg){width:1em;height:1em}</code> or ensure the SVG fills the icon container by setting <code>width:100%; height:100%</code> on the SVG (via attributes or CSS).</li>
        <li>Adjust per-size tokens when needed: <code>--ag-icon-button-font-size-*</code> (icon) and <code>--ag-icon-button-*</code> (button dimensions).</li>
      </ul>

      <details>
        <summary>Advanced</summary>
        <p>Example overrides: <code>ag-icon-button { --ag-icon-button-font-size-md: 1rem }</code> or <code>ag-icon-button[size="xl"] { --ag-icon-button-font-size-xl: 1.25rem; --ag-icon-button-xl: 3.5rem; }</code></p>
      </details>
    </div>

    <div class="mbe4">
      <h2>Toggle Buttons</h2>
    </div>
    <div class="stacked-mobile mbe4">
      <VueIconButton
        label="Toggle favorite"
        :pressed="isFavorite"
        @click="toggleFavorite"
      >
        <Heart
          :fill="isFavorite ? 'currentColor' : 'none'"
          class="expand"
        />
      </VueIconButton>
      <VueIconButton
        label="Toggle bookmark"
        :pressed="isBookmarked"
        @click="toggleBookmark"
      >
        <Bookmark
          class="expand"
          :fill="isBookmarked ? 'currentColor' : 'none'"
        />
      </VueIconButton>
      <VueIconButton
        label="Toggle notifications"
        :pressed="notificationsOn"
        @click="toggleNotifications"
      >
        <Bell
          class="expand"
          :fill="notificationsOn ? 'currentColor' : 'none'"
        />
      </VueIconButton>
    </div>

    <div class="mbe4">
      <h2>CSS Parts Customization</h2>
      <p
        class="mbe2"
        style="color: var(--ag-text-secondary); font-size: 0.875rem;"
      >
        Customize icon button appearance using CSS Shadow Parts without breaking encapsulation. The following are meant solely to show how to utilize CSS shadow parts to create custom toggle styles. They are NOT meant to represent best practices or good taste in toggle design!
      </p>
    </div>
    <div class="stacked-mobile mbe4">
      <VueIconButton
        class="custom-gradient-button"
        label="Gradient button"
      >
        <Heart class="expand" />
      </VueIconButton>
      <VueIconButton
        class="custom-gradient-button"
        label="Gradient star"
      >
        <Star class="expand" />
      </VueIconButton>
      <VueIconButton
        class="custom-gradient-button"
        label="Gradient bookmark"
      >
        <Bookmark class="expand" />
      </VueIconButton>
    </div>
  </section>
</template>

<script>
import VueIconButton from "agnosticui-core/icon-button/vue";
import { VueIcon } from "agnosticui-core/icon/vue";
import {
  Settings,
  Search,
  Edit,
  Trash2,
  Heart,
  Star,
  Check,
  AlertTriangle,
  Bell,
  Bookmark,
  Zap,
} from "lucide-vue-next";

export default {
  name: "IconButtonExamples",
  components: {
    VueIcon,
    VueIconButton,
    Settings,
    Search,
    Edit,
    Trash2,
    Heart,
    Star,
    Check,
    AlertTriangle,
    Bell,
    Bookmark,
    Zap,
  },
  data() {
    return {
      isFavorite: false,
      isBookmarked: false,
      notificationsOn: false,
    };
  },
  methods: {
    toggleFavorite() {
      this.isFavorite = !this.isFavorite;
    },
    toggleBookmark() {
      this.isBookmarked = !this.isBookmarked;
    },
    toggleNotifications() {
      this.notificationsOn = !this.notificationsOn;
    },
  },
};
</script>

<style scoped>
/* Ensure all SVGs expand to full width/height */
.expand {
  width: 100%;
  height: 100%;
  max-width: 100%;
  max-height: 100%;
}

/* CSS Parts customization examples */

/* Gradient button style */
.custom-gradient-button::part(ag-icon-button) {
  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
  color: white;
  border-radius: 12px;
  padding: 12px;
  transition: all 0.3s ease;
}

.custom-gradient-button::part(ag-icon-button):hover {
  transform: translateY(-2px);
  box-shadow: 0 8px 16px rgba(102, 126, 234, 0.4);
}

.custom-gradient-button::part(ag-icon-button):active {
  transform: translateY(0);
  box-shadow: 0 4px 8px rgba(102, 126, 234, 0.3);
}
</style>
Live Preview
View Lit / Web Component Code
import { LitElement, html } from 'lit';
import 'agnosticui-core/icon-button';

export class IconButtonLitExamples extends LitElement {
  constructor() {
    super();
    this.isFavorite = false;
    this.isBookmarked = false;
    this.notificationsOn = false;
  }

  // Render in light DOM to access global utility classes
  createRenderRoot() {
    return this;
  }

  toggleFavorite() {
    this.isFavorite = !this.isFavorite;
    this.requestUpdate();
  }

  toggleBookmark() {
    this.isBookmarked = !this.isBookmarked;
    this.requestUpdate();
  }

  toggleNotifications() {
    this.notificationsOn = !this.notificationsOn;
    this.requestUpdate();
  }

  render() {
    return html`
      <style>
        /* Ensure all SVGs expand to full width/height */
        .expand {
          width: 100%;
          height: 100%;
          max-width: 100%;
          max-height: 100%;
        }

        /* CSS Parts customization examples */

        /* Gradient button style */
        .custom-gradient-button::part(ag-icon-button) {
          background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
          color: white;
          border-radius: 12px;
          padding: 12px;
          transition: all 0.3s ease;
        }

        .custom-gradient-button::part(ag-icon-button):hover {
          transform: translateY(-2px);
          box-shadow: 0 8px 16px rgba(102, 126, 234, 0.4);
        }

        .custom-gradient-button::part(ag-icon-button):active {
          transform: translateY(0);
          box-shadow: 0 4px 8px rgba(102, 126, 234, 0.3);
        }
      </style>

      <section>
        <div class="mbe4">
          <h2>Basic Icon Buttons</h2>
        </div>
        <div class="stacked-mobile mbe4">
          <ag-icon-button label="Settings">
            <svg class="expand" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
              <circle cx="12" cy="12" r="3"></circle>
              <path d="M12 1v6m0 6v6M5.6 5.6l4.2 4.2m4.2 4.2l4.2 4.2M1 12h6m6 0h6M5.6 18.4l4.2-4.2m4.2-4.2l4.2-4.2"></path>
            </svg>
          </ag-icon-button>
          <ag-icon-button label="Search">
            <svg class="expand" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
              <circle cx="11" cy="11" r="8"></circle>
              <path d="m21 21-4.35-4.35"></path>
            </svg>
          </ag-icon-button>
          <ag-icon-button label="Edit">
            <svg class="expand" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
              <path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"></path>
              <path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"></path>
            </svg>
          </ag-icon-button>
          <ag-icon-button label="Delete">
            <svg class="expand" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
              <path d="M3 6h18"></path>
              <path d="M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6"></path>
              <path d="M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2"></path>
            </svg>
          </ag-icon-button>
        </div>

        <div class="flex-inline mbe4">
          <h2>Variants</h2>
        </div>
        <div class="stacked-mobile mbe4">
          <ag-icon-button label="Ghost (default)" variant="ghost">
            <svg class="expand" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
              <polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"></polygon>
            </svg>
          </ag-icon-button>
          <ag-icon-button label="Primary" variant="primary">
            <svg class="expand" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
              <polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"></polygon>
            </svg>
          </ag-icon-button>
          <ag-icon-button label="Secondary" variant="secondary">
            <svg class="expand" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
              <polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"></polygon>
            </svg>
          </ag-icon-button>
          <ag-icon-button label="Success" variant="success">
            <svg class="expand" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
              <polyline points="20 6 9 17 4 12"></polyline>
            </svg>
          </ag-icon-button>
          <ag-icon-button label="Warning" variant="warning">
            <svg class="expand" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
              <path d="m21.73 18-8-14a2 2 0 0 0-3.46 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3Z"></path>
              <line x1="12" y1="9" x2="12" y2="13"></line>
              <line x1="12" y1="17" x2="12.01" y2="17"></line>
            </svg>
          </ag-icon-button>
          <ag-icon-button label="Danger" variant="danger">
            <svg class="expand" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
              <path d="M3 6h18"></path>
              <path d="M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6"></path>
              <path d="M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2"></path>
            </svg>
          </ag-icon-button>
          <ag-icon-button label="Monochrome" variant="monochrome">
            <svg class="expand" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
              <polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"></polygon>
            </svg>
          </ag-icon-button>
        </div>

        <div class="mbe4">
          <h2>Scaled Icons</h2>
          <p class="mbe1">Project raw SVGs directly into the button and prefer em-based sizing so icons scale with the button size.</p>
          <ul>
            <li>Place the SVG directly inside <code>&lt;ag-icon-button&gt;</code> (don't wrap it).</li>
            <li>
              Prefer one of two patterns for slotted SVGs:
              <ul>
                <li>em-based sizing so the SVG scales with the icon font-size: <code>ag-icon-button::slotted(svg) { width: 1em; height: 1em; }</code></li>
                <li>or let the SVG fill its container: <code>width: 100%; height: 100%;</code></li>
              </ul>
            </li>
            <li>Adjust per-size tokens when needed: <code>--ag-icon-button-font-size-*</code> (icon) and <code>--ag-icon-button-*</code> (button dimensions).</li>
          </ul>
          <details>
            <summary>Advanced</summary>
            <p>The icon container uses <code>1em</code> by default and follows the per-size token values. If a projected SVG sets explicit pixel <code>width/height</code>, the SVG will honor those values. Prefer passing a <code>size</code> prop to icon libraries (e.g. lucide) when available.</p>
            <p>Example overrides: <code>ag-icon-button { --ag-icon-button-font-size-md: 1rem }</code> or <code>ag-icon-button[size="xl"] { --ag-icon-button-font-size-xl: 1.25rem; --ag-icon-button-xl: 3.5rem; }</code></p>
          </details>
        </div>
        <div class="mbe4">
          <ag-icon-button label="Extra Small" size="xs">
            <svg class="expand" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
              <path d="M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z"></path>
            </svg>
          </ag-icon-button>
          <ag-icon-button label="Small" size="sm">
            <svg class="expand" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
              <path d="M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z"></path>
            </svg>
          </ag-icon-button>
          <ag-icon-button label="Medium" size="md">
            <svg class="expand" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
              <path d="M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z"></path>
            </svg>
          </ag-icon-button>
          <ag-icon-button label="Large" size="lg">
            <svg class="expand" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
              <path d="M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z"></path>
            </svg>
          </ag-icon-button>
          <ag-icon-button label="Extra Large" size="xl">
            <svg class="expand" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
              <path d="M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z"></path>
            </svg>
          </ag-icon-button>
        </div>

        <div class="mbe4">
          <ag-icon-button label="Extra Small" variant="monochrome" size="xs" class="mie4">
            <svg class="expand" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
              <polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"></polygon>
            </svg>
          </ag-icon-button>
          <ag-icon-button label="Small" variant="monochrome" size="sm" class="mie4">
            <svg class="expand" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
              <polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"></polygon>
            </svg>
          </ag-icon-button>
          <ag-icon-button label="Medium" variant="monochrome" size="md" class="mie4">
            <svg class="expand" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
              <polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"></polygon>
            </svg>
          </ag-icon-button>
          <ag-icon-button label="Large" variant="monochrome" size="lg" class="mie4">
            <svg class="expand" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
              <polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"></polygon>
            </svg>
          </ag-icon-button>
          <ag-icon-button label="Extra Large" variant="monochrome" size="xl">
            <svg class="expand" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
              <polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"></polygon>
            </svg>
          </ag-icon-button>
        </div>

        <div class="mbe4">
          <h2>Scaled Icons</h2>
          <p class="mbe1">Project raw SVGs directly into the button and prefer em-based sizing so icons scale with the button size.</p>
          <ul>
            <li>Place the SVG directly inside <code>&lt;ag-icon-button&gt;</code> (don't wrap it).</li>
            <li>Make slotted SVGs follow the icon font-size: <code>ag-icon-button::slotted(svg){width:1em;height:1em}</code> or ensure the SVG fills the icon container by setting <code>width:100%; height:100%</code> on the SVG (via attributes or CSS).</li>
            <li>Adjust per-size tokens when needed: <code>--ag-icon-button-font-size-*</code> (icon) and <code>--ag-icon-button-*</code> (button dimensions).</li>
          </ul>

          <details>
            <summary>Advanced</summary>
            <p>Example overrides: <code>ag-icon-button { --ag-icon-button-font-size-md: 1rem }</code> or <code>ag-icon-button[size="xl"] { --ag-icon-button-font-size-xl: 1.25rem; --ag-icon-button-xl: 3.5rem; }</code></p>
          </details>
        </div>

        <div class="mbe4">
          <h2>Toggle Buttons</h2>
        </div>
        <div class="stacked-mobile mbe4">
          <ag-icon-button
            label="Toggle favorite"
            .pressed=${this.isFavorite}
            @click=${this.toggleFavorite}
          >
            <svg class="expand" viewBox="0 0 24 24" fill=${this.isFavorite ? 'currentColor' : 'none'} stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
              <path d="M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z"></path>
            </svg>
          </ag-icon-button>
          <ag-icon-button
            label="Toggle bookmark"
            .pressed=${this.isBookmarked}
            @click=${this.toggleBookmark}
          >
            <svg class="expand" viewBox="0 0 24 24" fill=${this.isBookmarked ? 'currentColor' : 'none'} stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
              <path d="m19 21-7-5-7 5V5a2 2 0 0 1 2-2h10a2 2 0 0 1 2 2v16z"></path>
            </svg>
          </ag-icon-button>
          <ag-icon-button
            label="Toggle notifications"
            .pressed=${this.notificationsOn}
            @click=${this.toggleNotifications}
          >
            <svg class="expand" viewBox="0 0 24 24" fill=${this.notificationsOn ? 'currentColor' : 'none'} stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
              <path d="M6 8a6 6 0 0 1 12 0c0 7 3 9 3 9H3s3-2 3-9"></path>
              <path d="M10.3 21a1.94 1.94 0 0 0 3.4 0"></path>
            </svg>
          </ag-icon-button>
        </div>

        <div class="mbe4">
          <h2>CSS Parts Customization</h2>
          <p class="mbe2" style="color: var(--ag-text-secondary); font-size: 0.875rem;">
            Customize icon button appearance using CSS Shadow Parts without breaking encapsulation. The following are meant solely to show how to utilize CSS shadow parts to create custom toggle styles. They are NOT meant to represent best practices or good taste in toggle design!
          </p>
        </div>
        <div class="stacked-mobile mbe4">
          <ag-icon-button class="custom-gradient-button" label="Gradient button">
            <svg class="expand" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
              <path d="M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z"></path>
            </svg>
          </ag-icon-button>
          <ag-icon-button class="custom-gradient-button" label="Gradient star">
            <svg class="expand" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
              <polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"></polygon>
            </svg>
          </ag-icon-button>
          <ag-icon-button class="custom-gradient-button" label="Gradient bookmark">
            <svg class="expand" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
              <path d="m19 21-7-5-7 5V5a2 2 0 0 1 2-2h10a2 2 0 0 1 2 2v16z"></path>
            </svg>
          </ag-icon-button>
        </div>
      </section>
    `;
  }
}

// Register the custom element
customElements.define('icon-button-lit-examples', IconButtonLitExamples);

Interactive Preview: Click the "Open in StackBlitz" button below to see this example running live in an interactive playground.

View React Code
import { useState } from "react";
import { ReactIconButton } from "agnosticui-core/icon-button/react";

export default function IconButtonReactExamples() {
  const [isFavorite, setIsFavorite] = useState(false);
  const [isBookmarked, setIsBookmarked] = useState(false);
  const [notificationsOn, setNotificationsOn] = useState(false);

  const toggleFavorite = () => {
    setIsFavorite(!isFavorite);
  };

  const toggleBookmark = () => {
    setIsBookmarked(!isBookmarked);
  };

  const toggleNotifications = () => {
    setNotificationsOn(!notificationsOn);
  };

  return (
    <section>
      <style>{`
        /* Ensure all SVGs expand to full width/height */
        .expand {
          width: 100%;
          height: 100%;
          max-width: 100%;
          max-height: 100%;
        }

        /* CSS Parts customization examples */

        /* Gradient button style */
        .custom-gradient-button::part(ag-icon-button) {
          background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
          color: white;
          border-radius: 12px;
          padding: 12px;
          transition: all 0.3s ease;
        }

        .custom-gradient-button::part(ag-icon-button):hover {
          transform: translateY(-2px);
          box-shadow: 0 8px 16px rgba(102, 126, 234, 0.4);
        }

        .custom-gradient-button::part(ag-icon-button):active {
          transform: translateY(0);
          box-shadow: 0 4px 8px rgba(102, 126, 234, 0.3);
        }
      `}</style>

      <div className="mbe4">
        <h2>Basic Icon Buttons</h2>
      </div>
      <div className="stacked-mobile mbe4">
        <ReactIconButton label="Settings">
          <svg className="expand" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
            <circle cx="12" cy="12" r="3"></circle>
            <path d="M12 1v6m0 6v6M5.6 5.6l4.2 4.2m4.2 4.2l4.2 4.2M1 12h6m6 0h6M5.6 18.4l4.2-4.2m4.2-4.2l4.2-4.2"></path>
          </svg>
        </ReactIconButton>
        <ReactIconButton label="Search">
          <svg className="expand" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
            <circle cx="11" cy="11" r="8"></circle>
            <path d="m21 21-4.35-4.35"></path>
          </svg>
        </ReactIconButton>
        <ReactIconButton label="Edit">
          <svg className="expand" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
            <path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"></path>
            <path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"></path>
          </svg>
        </ReactIconButton>
        <ReactIconButton label="Delete">
          <svg className="expand" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
            <path d="M3 6h18"></path>
            <path d="M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6"></path>
            <path d="M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2"></path>
          </svg>
        </ReactIconButton>
      </div>

      <div className="flex-inline mbe4">
        <h2>Variants</h2>
      </div>
      <div className="stacked-mobile mbe4">
        <ReactIconButton label="Ghost (default)" variant="ghost">
          <svg className="expand" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
            <polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"></polygon>
          </svg>
        </ReactIconButton>
        <ReactIconButton label="Primary" variant="primary">
          <svg className="expand" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
            <polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"></polygon>
          </svg>
        </ReactIconButton>
        <ReactIconButton label="Secondary" variant="secondary">
          <svg className="expand" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
            <polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"></polygon>
          </svg>
        </ReactIconButton>
        <ReactIconButton label="Success" variant="success">
          <svg className="expand" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
            <polyline points="20 6 9 17 4 12"></polyline>
          </svg>
        </ReactIconButton>
        <ReactIconButton label="Warning" variant="warning">
          <svg className="expand" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
            <path d="m21.73 18-8-14a2 2 0 0 0-3.46 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3Z"></path>
            <line x1="12" y1="9" x2="12" y2="13"></line>
            <line x1="12" y1="17" x2="12.01" y2="17"></line>
          </svg>
        </ReactIconButton>
        <ReactIconButton label="Danger" variant="danger">
          <svg className="expand" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
            <path d="M3 6h18"></path>
            <path d="M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6"></path>
            <path d="M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2"></path>
          </svg>
        </ReactIconButton>
        <ReactIconButton label="Monochrome" variant="monochrome">
          <svg className="expand" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
            <polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"></polygon>
          </svg>
        </ReactIconButton>
      </div>

      <div className="mbe4">
        <h2>Scaled Icons</h2>
        <p className="mbe1">Project raw SVGs directly into the button and prefer em-based sizing so icons scale with the button size.</p>
        <ul>
          <li>Place the SVG directly inside <code>&lt;ag-icon-button&gt;</code> (don't wrap it).</li>
          <li>
            Prefer one of two patterns for slotted SVGs:
            <ul>
              <li>em-based sizing so the SVG scales with the icon font-size: <code>ag-icon-button::slotted(svg) {"{ width: 1em; height: 1em; }"}</code></li>
              <li>or let the SVG fill its container: <code>width: 100%; height: 100%;</code></li>
            </ul>
          </li>
          <li>Adjust per-size tokens when needed: <code>--ag-icon-button-font-size-*</code> (icon) and <code>--ag-icon-button-*</code> (button dimensions).</li>
        </ul>
        <details>
          <summary>Advanced</summary>
          <p>The icon container uses <code>1em</code> by default and follows the per-size token values. If a projected SVG sets explicit pixel <code>width/height</code>, the SVG will honor those values. Prefer passing a <code>size</code> prop to icon libraries (e.g. lucide) when available.</p>
          <p>Example overrides: <code>ag-icon-button {"{ --ag-icon-button-font-size-md: 1rem }"}</code> or <code>ag-icon-button[size="xl"] {"{ --ag-icon-button-font-size-xl: 1.25rem; --ag-icon-button-xl: 3.5rem; }"}</code></p>
        </details>
      </div>
      <div className="mbe4">
        <ReactIconButton label="Extra Small" size="xs">
          <svg className="expand" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
            <path d="M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z"></path>
          </svg>
        </ReactIconButton>
        <ReactIconButton label="Small" size="sm">
          <svg className="expand" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
            <path d="M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z"></path>
          </svg>
        </ReactIconButton>
        <ReactIconButton label="Medium" size="md">
          <svg className="expand" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
            <path d="M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z"></path>
          </svg>
        </ReactIconButton>
        <ReactIconButton label="Large" size="lg">
          <svg className="expand" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
            <path d="M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z"></path>
          </svg>
        </ReactIconButton>
        <ReactIconButton label="Extra Large" size="xl">
          <svg className="expand" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
            <path d="M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z"></path>
          </svg>
        </ReactIconButton>
      </div>

      <div className="mbe4">
        <ReactIconButton label="Extra Small" variant="monochrome" size="xs" className="mie4">
          <svg className="expand" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
            <polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"></polygon>
          </svg>
        </ReactIconButton>
        <ReactIconButton label="Small" variant="monochrome" size="sm" className="mie4">
          <svg className="expand" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
            <polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"></polygon>
          </svg>
        </ReactIconButton>
        <ReactIconButton label="Medium" variant="monochrome" size="md" className="mie4">
          <svg className="expand" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
            <polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"></polygon>
          </svg>
        </ReactIconButton>
        <ReactIconButton label="Large" variant="monochrome" size="lg" className="mie4">
          <svg className="expand" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
            <polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"></polygon>
          </svg>
        </ReactIconButton>
        <ReactIconButton label="Extra Large" variant="monochrome" size="xl">
          <svg className="expand" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
            <polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"></polygon>
          </svg>
        </ReactIconButton>
      </div>

      <div className="mbe4">
        <h2>Scaled Icons</h2>
        <p className="mbe1">Project raw SVGs directly into the button and prefer em-based sizing so icons scale with the button size.</p>
        <ul>
          <li>Place the SVG directly inside <code>&lt;ag-icon-button&gt;</code> (don't wrap it).</li>
          <li>Make slotted SVGs follow the icon font-size: <code>ag-icon-button::slotted(svg){"{width:1em;height:1em}"}</code> or ensure the SVG fills the icon container by setting <code>width:100%; height:100%</code> on the SVG (via attributes or CSS).</li>
          <li>Adjust per-size tokens when needed: <code>--ag-icon-button-font-size-*</code> (icon) and <code>--ag-icon-button-*</code> (button dimensions).</li>
        </ul>

        <details>
          <summary>Advanced</summary>
          <p>Example overrides: <code>ag-icon-button {"{ --ag-icon-button-font-size-md: 1rem }"}</code> or <code>ag-icon-button[size="xl"] {"{ --ag-icon-button-font-size-xl: 1.25rem; --ag-icon-button-xl: 3.5rem; }"}</code></p>
        </details>
      </div>

      <div className="mbe4">
        <h2>Toggle Buttons</h2>
      </div>
      <div className="stacked-mobile mbe4">
        <ReactIconButton
          label="Toggle favorite"
          pressed={isFavorite}
          onClick={toggleFavorite}
        >
          <svg className="expand" viewBox="0 0 24 24" fill={isFavorite ? 'currentColor' : 'none'} stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
            <path d="M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z"></path>
          </svg>
        </ReactIconButton>
        <ReactIconButton
          label="Toggle bookmark"
          pressed={isBookmarked}
          onClick={toggleBookmark}
        >
          <svg className="expand" viewBox="0 0 24 24" fill={isBookmarked ? 'currentColor' : 'none'} stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
            <path d="m19 21-7-5-7 5V5a2 2 0 0 1 2-2h10a2 2 0 0 1 2 2v16z"></path>
          </svg>
        </ReactIconButton>
        <ReactIconButton
          label="Toggle notifications"
          pressed={notificationsOn}
          onClick={toggleNotifications}
        >
          <svg className="expand" viewBox="0 0 24 24" fill={notificationsOn ? 'currentColor' : 'none'} stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
            <path d="M6 8a6 6 0 0 1 12 0c0 7 3 9 3 9H3s3-2 3-9"></path>
            <path d="M10.3 21a1.94 1.94 0 0 0 3.4 0"></path>
          </svg>
        </ReactIconButton>
      </div>

      <div className="mbe4">
        <h2>CSS Parts Customization</h2>
        <p className="mbe2" style={{ color: 'var(--ag-text-secondary)', fontSize: '0.875rem' }}>
          Customize icon button appearance using CSS Shadow Parts without breaking encapsulation. The following are meant solely to show how to utilize CSS shadow parts to create custom toggle styles. They are NOT meant to represent best practices or good taste in toggle design!
        </p>
      </div>
      <div className="stacked-mobile mbe4">
        <ReactIconButton className="custom-gradient-button" label="Gradient button">
          <svg className="expand" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
            <path d="M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z"></path>
          </svg>
        </ReactIconButton>
        <ReactIconButton className="custom-gradient-button" label="Gradient star">
          <svg className="expand" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
            <polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"></polygon>
          </svg>
        </ReactIconButton>
        <ReactIconButton className="custom-gradient-button" label="Gradient bookmark">
          <svg className="expand" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
            <path d="m19 21-7-5-7 5V5a2 2 0 0 1 2-2h10a2 2 0 0 1 2 2v16z"></path>
          </svg>
        </ReactIconButton>
      </div>
    </section>
  );
}
Open in StackBlitz

Usage

TIP

The framework examples below import AgnosticUI as an npm package. Alternatively, you can use the CLI for complete control, AI/LLM visibility, and full code ownership:

bash
npx ag init --framework FRAMEWORK # react, vue, lit, svelte, etc.
npx ag add IconButton

The CLI copies source code directly into your project, giving you full visibility and control. After running npx ag add, you'll receive exact import instructions.

Vue
vue
<template>
  <section>
    <VueIconButton label="Settings">
      <Settings :size="18" />
    </VueIconButton>

    <VueIconButton
      label="Delete"
      variant="danger"
    >
      <Trash :size="18" />
    </VueIconButton>
    <VueIconButton
      label="Save"
      variant="primary"
    >
      <Save :size="18" />
    </VueIconButton>

    <VueIconButton
      label="Small"
      size="sm"
    >
      <Heart :size="16" />
    </VueIconButton>
    <VueIconButton
      label="Large"
      size="lg"
    >
      <Heart :size="20" />
    </VueIconButton>

    <VueIconButton
      label="Toggle favorite"
      :pressed="isFavorite"
      @click="toggleFavorite"
    >
      <Heart
        :size="18"
        :fill="isFavorite ? 'currentColor' : 'none'"
      />
    </VueIconButton>

    <VueIconButton
      label="Close"
      unicode="×"
    />

    <VueIconButton
      label="Disabled"
      disabled
    >
      <Save :size="18" />
    </VueIconButton>
    <VueIconButton
      label="Loading"
      loading
    >
      <Loader :size="18" />
    </VueIconButton>

    <VueIconButton
      label="Action button"
      @icon-button-click="handleClick"
      @icon-button-activate="handleActivate"
    >
      <Zap :size="18" />
    </VueIconButton>
  </section>
</template>

<script>
import VueIconButton from "agnosticui-core/icon-button/vue";
import { Settings, Trash, Save, Heart, Loader, Zap } from "lucide-vue-next";

export default {
  components: {
    VueIconButton,
    Settings,
    Trash,
    Save,
    Heart,
    Loader,
    Zap,
  },
  data() {
    return {
      isFavorite: false,
    };
  },
  methods: {
    toggleFavorite() {
      this.isFavorite = !this.isFavorite;
    },
    handleClick(event) {
      console.log("Button clicked:", event.detail.label, event.detail.pressed);
    },
    handleActivate(event) {
      console.log("Button activated via keyboard:", event.detail.label);
    },
  },
};
</script>
React
tsx
import { useState } from "react";
import { ReactIconButton } from "agnosticui-core/icon-button/react";
import { Settings, Trash, Save, Heart, Loader, Zap } from "lucide-react";

export default function IconButtonExample() {
  const [isFavorite, setIsFavorite] = useState(false);

  const toggleFavorite = () => {
    setIsFavorite(!isFavorite);
  };

  const handleClick = (event) => {
    console.log("Button clicked:", event.detail.label, event.detail.pressed);
  };

  const handleActivate = (event) => {
    console.log("Button activated via keyboard:", event.detail.label);
  };

  return (
    <section>
      <ReactIconButton label="Settings">
        <Settings size={18} />
      </ReactIconButton>

      <ReactIconButton label="Delete" variant="danger">
        <Trash size={18} />
      </ReactIconButton>
      <ReactIconButton label="Save" variant="primary">
        <Save size={18} />
      </ReactIconButton>

      <ReactIconButton label="Small" size="sm">
        <Heart size={16} />
      </ReactIconButton>
      <ReactIconButton label="Large" size="lg">
        <Heart size={20} />
      </ReactIconButton>

      <ReactIconButton
        label="Toggle favorite"
        pressed={isFavorite}
        onClick={toggleFavorite}
      >
        <Heart
          size={18}
          fill={isFavorite ? "currentColor" : "none"}
        />
      </ReactIconButton>

      <ReactIconButton label="Close" unicode="×" />

      <ReactIconButton label="Disabled" disabled>
        <Save size={18} />
      </ReactIconButton>
      <ReactIconButton label="Loading" loading>
        <Loader size={18} />
      </ReactIconButton>

      <ReactIconButton
        label="Action button"
        onIconButtonClick={handleClick}
        onIconButtonActivate={handleActivate}
      >
        <Zap size={18} />
      </ReactIconButton>
    </section>
  );
}
Lit (Web Components)
typescript
import { LitElement, html, css } from 'lit';
import { customElement, state } from 'lit/decorators.js';
import 'agnosticui-core/icon-button';

@customElement('icon-button-example')
export class IconButtonExample extends LitElement {
  @state() private isFavorite = false;

  static styles = css`
    :host {
      display: block;
    }
    section {
      display: flex;
      flex-wrap: wrap;
      gap: 0.5rem;
    }
  `;

  firstUpdated() {
    // Set up event listeners for icon buttons in the shadow DOM
    const favoriteBtn = this.shadowRoot?.querySelector('#favorite-toggle') as any;

    favoriteBtn?.addEventListener('click', () => {
      this.isFavorite = !this.isFavorite;
      favoriteBtn.pressed = this.isFavorite;

      const heartIcon = favoriteBtn.querySelector('svg');
      if (heartIcon) {
        heartIcon.setAttribute('fill', this.isFavorite ? 'currentColor' : 'none');
      }
    });

    const actionBtn = this.shadowRoot?.querySelector('#action-btn');
    actionBtn?.addEventListener('icon-button-click', (e: Event) => {
      const customEvent = e as CustomEvent;
      console.log('Button clicked:', customEvent.detail.label, customEvent.detail.pressed);
    });
    actionBtn?.addEventListener('icon-button-activate', (e: Event) => {
      const customEvent = e as CustomEvent;
      console.log('Button activated via keyboard:', customEvent.detail.label);
    });

    const callbackBtn = this.shadowRoot?.querySelector('#callback-btn') as any;
    if (callbackBtn) {
      callbackBtn.onIconButtonClick = (e: CustomEvent) => {
        console.log('Callback click:', e.detail.label);
      };
    }
  }

  render() {
    return html`
      <section>
        <ag-icon-button label="Settings">
          <svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor">
            <circle cx="12" cy="12" r="3"></circle>
            <path d="M12 1v6m0 6v6M5.6 5.6l4.2 4.2m4.2 4.2l4.2 4.2M1 12h6m6 0h6M5.6 18.4l4.2-4.2m4.2-4.2l4.2-4.2"></path>
          </svg>
        </ag-icon-button>

        <ag-icon-button label="Delete" variant="danger">
          <svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor">
            <path d="M3 6h18M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6"></path>
          </svg>
        </ag-icon-button>
        <ag-icon-button label="Save" variant="primary">
          <svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor">
            <path d="M19 21H5a2 2 0 01-2-2V5a2 2 0 012-2h11l5 5v11a2 2 0 01-2 2z"></path>
          </svg>
        </ag-icon-button>

        <ag-icon-button label="Small" size="sm">
          <svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor">
            <path d="M20.84 4.61a5.5 5.5 0 00-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 00-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 000-7.78z"></path>
          </svg>
        </ag-icon-button>
        <ag-icon-button label="Large" size="lg">
          <svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor">
            <path d="M20.84 4.61a5.5 5.5 0 00-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 00-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 000-7.78z"></path>
          </svg>
        </ag-icon-button>

        <ag-icon-button id="favorite-toggle" label="Toggle favorite">
          <svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor">
            <path d="M20.84 4.61a5.5 5.5 0 00-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 00-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 000-7.78z"></path>
          </svg>
        </ag-icon-button>

        <ag-icon-button label="Close" unicode="×"></ag-icon-button>

        <ag-icon-button label="Disabled" disabled>
          <svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor">
            <path d="M19 21H5a2 2 0 01-2-2V5a2 2 0 012-2h11l5 5v11a2 2 0 01-2 2z"></path>
          </svg>
        </ag-icon-button>
        <ag-icon-button label="Loading" loading>
          <svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor">
            <path d="M12 2v4m0 12v4M4.93 4.93l2.83 2.83m8.48 8.48l2.83 2.83M2 12h4m12 0h4M4.93 19.07l2.83-2.83m8.48-8.48l2.83-2.83"></path>
          </svg>
        </ag-icon-button>

        <ag-icon-button id="action-btn" label="Action button">
          <svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor">
            <path d="M13 2L3 14h9l-1 8 10-12h-9l1-8z"></path>
          </svg>
        </ag-icon-button>

        <ag-icon-button id="callback-btn" label="Callback button">
          <svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor">
            <circle cx="12" cy="12" r="10"></circle>
            <path d="M12 8v8m-4-4h8"></path>
          </svg>
        </ag-icon-button>
      </section>
    `;
  }
}

Note: When using icon-button components within a custom element's shadow DOM, set up event listeners in the component's lifecycle (e.g., firstUpdated()) rather than using DOMContentLoaded, as document.querySelector() cannot access elements inside shadow DOM. Use this.shadowRoot.querySelector() instead.

Props

PropTypeDefaultDescription
labelstringRequired. Accessible name for the button. Used for aria-label to ensure screen readers can announce the button's purpose.
size'xs' | 'sm' | 'md' | 'lg' | 'xl''md'Size of the button. Controls both button dimensions and icon sizing.
variant'primary' | 'secondary' | 'success' | 'warning' | 'danger' | 'ghost''ghost'Visual style variant. Ghost is transparent by default. Primary, success, warning, and danger have filled backgrounds with white icons and get darker on hover. Secondary uses a neutral gray background.
type'button' | 'submit' | 'reset''button'Button type attribute. Use 'submit' for form submissions or 'reset' for form resets.
iconstring''Icon identifier for icon systems. Can be used with icon libraries that support string identifiers.
unicodestring''Unicode character for simple icons (e.g., '×', '☰', '+'). Alternative to using custom icons via slots.
disabledbooleanfalseDisables the button, preventing interaction and applying reduced opacity.
pressedbooleanfalseIndicates pressed state for toggle buttons. Sets aria-pressed and applies visual pressed styling.
loadingbooleanfalseShows loading indicator and prevents interaction. Useful for async operations.
ariaLabelstringAdditional ARIA label that overrides the label prop if provided.
ariaDescribedbystring''ID reference for additional descriptive text. Points to an element that provides extended description.

Events

EventFrameworkDetailDescription
icon-button-clickVue: @icon-button-click
React: onIconButtonClick
Lit: @icon-button-click or .onIconButtonClick
{ originalEvent: MouseEvent, label: string, pressed: boolean }Fired when the icon button is clicked. Provides the original mouse event and button state.
icon-button-activateVue: @icon-button-activate
React: onIconButtonActivate
Lit: @icon-button-activate or .onIconButtonActivate
{ originalEvent: KeyboardEvent, label: string, pressed: boolean }Fired when the button is activated via keyboard (Space or Enter keys). Provides the original keyboard event and button state.

Event Patterns

AgnosticUI IconButton supports three event handling patterns:

  1. addEventListener (Lit/Vanilla JS):
javascript
const iconButton = document.querySelector("ag-icon-button");
iconButton.addEventListener("icon-button-click", (e) => {
  console.log("Button clicked:", e.detail.label, "Pressed:", e.detail.pressed);
});
  1. Callback props (Lit/Vanilla JS):
javascript
const iconButton = document.querySelector("ag-icon-button");
iconButton.onIconButtonClick = (e) => {
  console.log("Button clicked:", e.detail.label);
};
  1. Framework event handlers (Vue/React):
vue
<VueIconButton
  @icon-button-click="handleClick"
  @icon-button-activate="handleActivate"
/>
tsx
<ReactIconButton
  onIconButtonClick={handleClick}
  onIconButtonActivate={handleActivate}
/>

All three patterns work identically thanks to the dual-dispatch system.

CSS Shadow Parts

The IconButton exposes CSS Shadow Parts that allow you to customize internal elements without breaking encapsulation:

PartDescriptionElement
ag-icon-has-slottedIcon wrapper when using slotted content<span>
ag-icon-unicodeIcon wrapper for unicode characters<span>
ag-iconIcon wrapper for icon identifier<span>
ag-icon-empty-slotEmpty icon slot placeholder<span>

Example Usage

css
ag-icon-button::part(ag-icon-unicode) {
  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
  -webkit-background-clip: text;
  -webkit-text-fill-color: transparent;
  background-clip: text;
}

ag-icon-button button {
  border: 2px solid #12623e;
  border-radius: 50%;
  box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
}

ag-icon-button::part(ag-icon):hover {
  transform: scale(1.1);
  transition: transform 0.2s ease;
}

Accessibility

The IconButton implements the WAI-ARIA Button Pattern:

  • Uses semantic <button> element for proper keyboard and screen reader support
  • Requires accessible name via the label prop (maps to aria-label)
  • Communicates toggle state via aria-pressed when pressed prop is used
  • Minimum 44px touch target for mobile accessibility (WCAG 2.1 Level AAA)
  • Keyboard accessible with Space and Enter key activation
  • Screen readers announce the button's label and state
  • Clear focus indicators for keyboard navigation
  • Disabled state communicated to assistive technologies

Accessible Label Requirement

The label prop is required for accessibility. Icon-only buttons need an accessible name so screen reader users can understand the button's purpose. While the label is not visually displayed, it's announced to screen readers via aria-label.

Good examples:

vue
<VueIconButton label="Save document">
  <Save :size="18" />
</VueIconButton>

<VueIconButton label="Delete item">
  <Trash :size="18" />
</VueIconButton>

Bad example:

vue
<VueIconButton>
  <Save :size="18" />
</VueIconButton>