HyIconButton
Usage examples on this page are written for Lit / plain HTML (
<hy-icon-button>). The same component ships asHyIconButtonin@whitespaceux/harmony-react(native React 19), with typed wrappers for Angular, Solid, Svelte, and Vue. The API reference below applies to all frameworks.
A compact, SSR-safe button component that displays only an icon, ideal for toolbars and space-constrained interfaces.
Key Features
- Dual mode: Works as both button and link (when href provided)
- Toggle button support with aria-pressed state management
- Five emphasis values: solid, soft, outlined, tinted, plain
- Two shapes: square (rounded corners) and round (circular)
- Full keyboard navigation support (Enter, Space, Escape)
- Comprehensive ARIA attributes for accessibility
- Custom events for interaction handling
- SSR-safe with proper hydration support
- High contrast and forced colors mode support
Accessibility
- Requires accessible label (label, aria-label, or aria-labelledby)
- Screen reader optimized with proper ARIA attributes
- Keyboard navigation with Enter, Space, and Escape keys
- Focus management with visible indicators
- Touch-friendly with proper target sizes
SSR Behavior
- Validation warnings only fire client-side
- Public methods (click, focus, blur) are safe to call at any time
- Properly hydrates with all attributes and state preserved
Examples
Basic Usage
<hy-icon-button name="settings" label="Open settings"></hy-icon-button>Soft emphasis
<hy-icon-button name="settings" label="Open settings" emphasis="soft"> </hy-icon-button>Outlined emphasis
<hy-icon-button name="settings" label="Open settings" emphasis="outlined"> </hy-icon-button>Round shape with soft emphasis
<hy-icon-button name="settings" label="Open settings" shape="round" emphasis="soft">
</hy-icon-button>Link Mode
<hy-icon-button
name="download"
href="/file.pdf"
download="document.pdf"
label="Download PDF"
></hy-icon-button>External Link with Security
<hy-icon-button
name="external-link"
href="https://example.com"
target="_blank"
label="Open external link"
>
</hy-icon-button>
<!-- Automatically adds rel="noreferrer noopener" for security -->Disabled State
<hy-icon-button name="delete" disabled label="Delete item (disabled)"> </hy-icon-button>Soft-disabled (tooltip explaining why stays reachable)
<hy-tooltip>
<hy-icon-button slot="trigger" name="delete" soft-disabled label="Remove font"> </hy-icon-button>
In use by a category — reassign it first.
</hy-tooltip>Toggle Button
<hy-icon-button
name="favorite-border"
label="Add to favorites"
pressed="false"
aria-describedby="fav-help"
>
</hy-icon-button>
<div id="fav-help">Click to toggle favorite status</div>Dropdown Trigger
<hy-icon-button
name="chevron-down"
label="Open menu"
aria-haspopup="menu"
aria-expanded="false"
aria-controls="dropdown-menu"
>
</hy-icon-button>
<div id="dropdown-menu" role="menu" hidden>Menu items...</div>Event Handling
const button = document.querySelector('hy-icon-button'); // Native click bubbles from the internal
<button>
— no custom event needed. // When disabled, click is gated (stopPropagation + preventDefault on
the host). button.addEventListener('click', (event) => { console.log('Button clicked:', event);
}); button.addEventListener('press-change', (event) => { console.log('Toggle state:',
event.detail.pressed); }); button.addEventListener('focus', (event) => { console.log('Button
focused'); }); button.addEventListener('escape', (event) => { console.log('Escape pressed - close
menu'); });
</button>Programmatic Control
const button = document.querySelector('hy-icon-button');
// Programmatically click
button.click();
// Programmatically focus
button.focus();
// Programmatically blur
button.blur();Toolbar with Mixed Appearances
<div class="toolbar" role="toolbar" aria-label="Document actions">
<hy-icon-button name="save" label="Save document"></hy-icon-button>
<hy-icon-button name="content-copy" label="Copy" emphasis="soft"></hy-icon-button>
<hy-icon-button name="share" label="Share" emphasis="outlined"></hy-icon-button>
</div>Click Gating
Native click from the internal <button> is composed: true and bubbles out of the shadow root naturally. When the component is disabled, a host-level listener calls stopPropagation() + preventDefault() so no click escapes.
Custom Events
press-change- Fired when toggle button's pressed state changesdetail.pressed: Boolean indicating new pressed state (true/false)detail.originalEvent: The original event that triggered the togglefocus- Fired when button receives focusdetail.originalEvent: The original FocusEventblur- Fired when button loses focusdetail.originalEvent: The original FocusEventescape- Fired when Escape key is pressed while focuseddetail.originalEvent: The original KeyboardEventUseful for closing associated menus or dialogs
Keyboard Support
Tab- Moves focus to/from the buttonShift + Tab- Moves focus in reverse orderEnter- Activates the button (works for both buttons and links)Space- Activates the button (buttons only, not links per HTML spec)Escape- Blurs the button and firesescapeevent
CSS Parts
### Theming via CSS custom properties
```css
hy-icon-button {
--hy-icon-button-stroke: var(--hy-stroke-md);
--hy-icon-button-height-md: var(--hy-control-height-lg);
}API
Properties
| Property | Attribute | Type | Default | Description |
|---|---|---|---|---|
name | name | string | undefined | — | The name of the icon to display. Must match an icon in the configured icon set. |
size | size | 'small' | 'medium' | 'large' | 'medium' | The size of the icon button. |
shape | shape | 'square' | 'round' | 'square' | Geometric shape of the icon-button hit area. - square - Slightly rounded corners for a softer rectangular look (default) - round - Full border-radius (50%) for circular shape |
emphasis | emphasis | 'solid' | 'soft' | 'outlined' | 'tinted' | 'plain' | 'plain' | Visual chrome weight (5-value emphasis axis — see .claude/rules/components.md). - solid - full role fill, on-color text - soft - role-tinted fill, role-colored text - outlined - role border, transparent fill - tinted - very faint role tint - plain - no chrome (default for icon-buttons — toolbar-style affordance) |
href | href | string | undefined | — | When set, renders the button as an <a> tag with this href instead of a <button>. The component automatically handles link-specific behavior and attributes. |
target | target | '_blank' | '_parent' | '_self' | '_top' | — | Specifies where to open the linked document. Only used when href is set. When set to _blank, automatically adds rel="noreferrer noopener" for security. - _blank - Opens in new tab/window - _self - Opens in same frame (default) - _parent - Opens in parent frame - _top - Opens in full window body |
download | download | string | undefined | — | Prompts the browser to download the linked file with this filename. Only used when href is set. |
label | label | string | '' | An accessible label that describes what the icon button does. Required for accessibility - if not provided, you must use aria-label or aria-labelledby. |
disabled | disabled | boolean | false | Disables the button, preventing interaction and applying disabled styling. When disabled: - Click events are prevented - Keyboard activation is blocked - tabindex is set to -1 to remove from tab order - aria-disabled="true" is set for screen readers |
softDisabled | soft-disabled | boolean | false | Soft-disables the button: interaction is blocked and disabled styling applies, but the button stays focusable and hoverable. Use instead of disabled when the button carries an explanation (e.g. a tooltip saying why it's unavailable) that keyboard and screen-reader users must be able to reach. Exposed as aria-disabled="true"; native disabled is NOT set. |
pressed | pressed | string | null | null | Indicates the pressed state of a toggle button. |
ariaDescribedby | aria-describedby | string | null | null | References the ID of an element that provides additional description. Useful for help text, expanded descriptions, or important context. |
ariaExpanded | aria-expanded | string | null | null | Indicates whether an element controlled by this button is expanded or collapsed. Used with collapsible content, dropdowns, accordions, or disclosure widgets. |
ariaControls | aria-controls | string | null | null | Indicates that this button controls another element or group of elements. Should reference the ID(s) of the controlled element(s). |
ariaHaspopup | aria-haspopup | string | null | null | Indicates that the button triggers a popup element. |
autofocus | autofocus | boolean | false | Marks this icon button as the auto-focus target. Read by hy-dialog (and other overlay containers) on show to choose initial focus. Also focuses on first connect when used outside an overlay, mirroring native HTML autofocus but routed through JS so SSR'd HTML doesn't pull focus during hydration. |
Events
| Event | Detail | Description |
|---|---|---|
press-change | — | Fired when a toggle button's pressed state changes. Detail: { pressed: boolean, originalEvent: MouseEvent | KeyboardEvent } |
focus | — | Fired when the button receives focus. Detail: { originalEvent: FocusEvent } |
blur | — | Fired when the button loses focus. Detail: { originalEvent: FocusEvent } |
escape | — | Fired when Escape is pressed while focused; useful for closing associated menus or dialogs. Detail: { originalEvent: KeyboardEvent } |
CSS Parts
| Part | Description |
|---|---|
base | The icon button's base wrapper element (button or anchor tag) |
CSS Custom Properties
| Property | Description |
|---|---|
--hy-icon-button-stroke | Border width for outlined appearance |
--hy-icon-button-height-sm | Outer height for small size variant. Per-component override in the form-control height contract (see .claude/rules/control-heights.md). The trigger is square: width = height = this value. Inner icon stays at --hy-icon-size-* independent of the outer height. Default: var(--hy-control-height-sm) |
--hy-icon-button-height-md | Outer height for medium size variant. Default: var(--hy-control-height-md) |
--hy-icon-button-height-lg | Outer height for large size variant. Default: var(--hy-control-height-lg) |
--hy-icon-button-hover-duration | Transition duration for hover and focus state feedback |
--hy-icon-button-hover-easing | Transition easing for hover and focus state feedback |
--hy-icon-button-foreground-default-rest | Resting icon color. This hook — NOT an outer --hy-icon-color write, which the component's internal declaration silently shadows — is the seam for recoloring the glyph from a wrapping component (alert/toast solid close x, copy-button feedback tint). Internal state colors (disabled, toggled-on) deliberately still win. Default: var(--hy-foreground-default-rest) |
Methods
click()
Simulates a click on the icon button.
Behavior:
- Respects disabled state - disabled buttons won't trigger clicks
- For toggle buttons, toggles the pressed state
- Native
clickbubbles naturally; optionally firespress-changefor toggles - Safe to call during SSR or before first render
const iconButton = document.querySelector('hy-icon-button');
// Trigger click programmatically
iconButton.click();
// Listen for native click
iconButton.addEventListener('click', (e) => {
console.log('Button clicked!', e);
});const toggleButton = document.querySelector('hy-icon-button[pressed]');
toggleButton.click(); // Toggles pressed state
toggleButton.addEventListener('press-change', (e) => {
console.log('New state:', e.detail.pressed);
});focus()
Sets focus on the icon button.
Behavior:
- Shows focus ring for keyboard navigation visibility
- Makes button available for keyboard interaction
- Fires
focusevent when focused - Safe to call during SSR or before first render
Parameters:
options- Optional focus optionsoptions.preventScroll- If true, prevents scrolling to the focused element
const iconButton = document.querySelector('hy-icon-button');
// Focus the button
iconButton.focus();
// Listen for focus event
iconButton.addEventListener('focus', () => {
console.log('Button focused');
});const iconButton = document.querySelector('hy-icon-button');
// Focus without scrolling viewport
iconButton.focus({ preventScroll: true });function openDialog() {
dialog.showModal();
// Focus first button in dialog
dialog.querySelector('hy-icon-button').focus();
}setTabIndex()
Projects a tabindex onto the inner button — called by parent roving-tabindex controllers so composite widgets (e.g. the markdown-editor's format toolbar) can keep a single tab stop.
blur()
Removes focus from the icon button.
Behavior:
- Hides the focus ring
- Removes button from keyboard navigation flow
- Fires
blurevent when blurred - Safe to call during SSR or before first render
const iconButton = document.querySelector('hy-icon-button');
// Remove focus from button
iconButton.blur();
// Listen for blur event
iconButton.addEventListener('blur', () => {
console.log('Button lost focus');
});const menuButton = document.querySelector('hy-icon-button[aria-haspopup="menu"]');
menuButton.addEventListener('escape', () => {
closeMenu();
menuButton.blur(); // Remove focus after closing
});