Icon Button
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
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%;
- em-based sizing so the SVG scales with the icon font-size:
- 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 settingwidth: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><ag-icon-button></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><ag-icon-button></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><ag-icon-button></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><ag-icon-button></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><ag-icon-button></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><ag-icon-button></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>
);
}
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:
npx ag init --framework FRAMEWORK # react, vue, lit, svelte, etc.
npx ag add IconButtonThe 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
<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
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)
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
| Prop | Type | Default | Description |
|---|---|---|---|
label | string | — | Required. 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. |
icon | string | '' | Icon identifier for icon systems. Can be used with icon libraries that support string identifiers. |
unicode | string | '' | Unicode character for simple icons (e.g., '×', '☰', '+'). Alternative to using custom icons via slots. |
disabled | boolean | false | Disables the button, preventing interaction and applying reduced opacity. |
pressed | boolean | false | Indicates pressed state for toggle buttons. Sets aria-pressed and applies visual pressed styling. |
loading | boolean | false | Shows loading indicator and prevents interaction. Useful for async operations. |
ariaLabel | string | — | Additional ARIA label that overrides the label prop if provided. |
ariaDescribedby | string | '' | ID reference for additional descriptive text. Points to an element that provides extended description. |
Events
| Event | Framework | Detail | Description |
|---|---|---|---|
icon-button-click | Vue: @icon-button-clickReact: onIconButtonClickLit: @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-activate | Vue: @icon-button-activateReact: onIconButtonActivateLit: @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:
- addEventListener (Lit/Vanilla JS):
const iconButton = document.querySelector("ag-icon-button");
iconButton.addEventListener("icon-button-click", (e) => {
console.log("Button clicked:", e.detail.label, "Pressed:", e.detail.pressed);
});- Callback props (Lit/Vanilla JS):
const iconButton = document.querySelector("ag-icon-button");
iconButton.onIconButtonClick = (e) => {
console.log("Button clicked:", e.detail.label);
};- Framework event handlers (Vue/React):
<VueIconButton
@icon-button-click="handleClick"
@icon-button-activate="handleActivate"
/><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:
| Part | Description | Element |
|---|---|---|
ag-icon-has-slotted | Icon wrapper when using slotted content | <span> |
ag-icon-unicode | Icon wrapper for unicode characters | <span> |
ag-icon | Icon wrapper for icon identifier | <span> |
ag-icon-empty-slot | Empty icon slot placeholder | <span> |
Example Usage
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
labelprop (maps toaria-label) - Communicates toggle state via
aria-pressedwhenpressedprop 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:
<VueIconButton label="Save document">
<Save :size="18" />
</VueIconButton>
<VueIconButton label="Delete item">
<Trash :size="18" />
</VueIconButton>Bad example:
<VueIconButton>
<Save :size="18" />
</VueIconButton>