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
- Seven role variants: neutral, brand, success, warning, danger, info, and
highlightfor the one action that stands out — the button's set; each (variant, emphasis) cell paints ashy-buttondoes - Two shapes: square (the action corner, shared with hy-button) 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
The glyph's color is recolored from a wrapping component (alert / toast solid close x, copy-button feedback tint) through the component's own foreground hooks, never through an outer --hy-icon-color write, which the component's internal declaration shadows; the internal state colors (disabled, toggled-on) still win.
Accessibility: built in
- 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
Accessibility: your part
- Write
labelas the action ("Delete row"), not the glyph ("Trash"). - Pair the button with an
hy-tooltipcarrying the same name, so sighted people can read it on hover and focus.
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
When to use
- A frequent action in a toolbar, a header or a table row, where space is short and the glyph is familiar: edit, close, more, copy.
- A row of compact actions next to each other on one item.
- An icon-shaped link, such as an external link or a download, with
href.
Do
- Use the same glyph for the same action everywhere.
- Keep the plain emphasis in a toolbar or a row of actions.
When not to use
- An action people could not guess from the glyph alone:
hy-buttonwith a visible label. - The main action of a view: a labeled
hy-button. The one floating action of a screen:hy-fab. - Navigation shown as text:
hy-link. Keephrefon the icon button for icon-shaped links such as an external link or a download. - A row of related on/off choices:
hy-toggle-group.
Examples
Basic Usage
<hy-icon-button name="settings" label="Open settings"></hy-icon-button>Solid brand — the primary action of a compact row (a builder's commit)
<hy-icon-button name="chevron-right" label="Add this override" variant="brand" emphasis="solid">
</hy-icon-button>Danger role on a plain toolbar glyph
<hy-icon-button name="trash" label="Delete row" variant="danger"></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="trash" disabled label="Delete item (disabled)"> </hy-icon-button>Soft-disabled (tooltip explaining why stays reachable)
<hy-tooltip>
<hy-icon-button slot="trigger" name="trash" soft-disabled label="Remove font"> </hy-icon-button>
In use by a category — reassign it first.
</hy-tooltip>Toggle Button
<hy-icon-button name="heart" 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');
});Programmatic Control
const button = document.querySelector('hy-icon-button');
// Programmatically click
button.click();
// Programmatically focus
button.focus();
// Programmatically blur
button.blur();A row of actions with mixed emphases
A labeled group; each button is its own tab stop. (A role="toolbar" would promise arrow keys between them, which a plain row does not provide — for one tab stop with arrow keys, put the buttons in an hy-toolbar.)
<div class="toolbar" role="group" aria-label="Document actions">
<hy-icon-button name="download" label="Save document"></hy-icon-button>
<hy-icon-button name="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
No form submission (type is React-only)
hy-icon-button has no type: its <button> lives in a shadow root, which cannot submit or reset the light-DOM form around it, so it always acts as type="button". Put a submit on hy-button (which proxies a submit) or a native <button type="submit">. The React HyIconButton renders its <button> in the page DOM, so there type="submit" | "reset" works — by design, the one prop the two sides do not share.
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
base- The button's base wrapper element (either<button>or<a>tag)
Theming via CSS custom properties
hy-icon-button {
--hy-icon-button-stroke-sm: var(--hy-stroke-md);
--hy-icon-button-control-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 - The action corner (--hy-radius-action-md, the rung hy-button reads), so it rounds with the buttons when a design system dials actions away from fields (default) - round - Full border-radius (50%) for circular shape |
variant | variant | 'neutral' | 'brand' | 'success' | 'warning' | 'danger' | 'info' | 'highlight' | 'neutral' | Semantic role — hy-button's set. Composes with emphasis: a variant="brand" emphasis="solid" icon button is the brand fill with on-brand ink, variant="danger" on the default plain is a red glyph with a danger tint on hover — exactly the button's cells. - neutral - default ink and the muted / neutral fills (default) - brand - the design system's identity - success / warning / danger - status roles - highlight - the one action that stands out on a screen: brand, plus the design system's gradients - info - an action inside a status container (alert / toast) |
emphasis | emphasis | 'solid' | 'soft' | 'outlined' | 'tinted' | 'plain' | 'plain' | Visual chrome weight (the shared 5-value emphasis axis). - solid - full role fill, on-color glyph - soft - role-tinted fill, role-colored glyph - 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> — unless it is disabled or soft-disabled, which render a <button> (an anchor navigates on Enter whatever aria-disabled says). 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. |
rel | rel | string | undefined | — | The link's rel. Only used when href is set. Unset, a target="_blank" link gets noreferrer noopener. |
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 | 'true' | 'false' | 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
The documented hooks. Every override hook the component reads — one per token it paints — is listed in @whitespaceux/harmony/theme-api.json, and @whitespaceux/harmony/component-bindings.json (the Harmony MCP get-bindings tool) says which part and state each one paints.
| Property | Default | Description |
|---|---|---|
--hy-icon-button-stroke-sm | var(--hy-stroke-sm) | Border width for outlined emphasis |
--hy-icon-button-background-brand-solid-rest | var(--hy-background-brand-solid-rest) | One of the generated color hooks (paint/icon-button.mjs): --hy-icon-button[-<part>]-<family>-<variant>-<emphasis-or-on>-<state> (family: background, foreground or border), one per cell and state, each falling back to the job token of the same name (the default cell is neutral plain). Components ▸ Add an override lists them all. |
--hy-icon-button-control-height-sm | var(--hy-control-height-sm) | Outer height for small size variant. Per-component override in the form-control height contract. The trigger is square: width = height = this value. Inner icon stays at --hy-icon-size-* independent of the outer height. |
--hy-icon-button-control-height-md | var(--hy-control-height-md) | Outer height for medium size variant. |
--hy-icon-button-control-height-lg | var(--hy-control-height-lg) | Outer height for large size variant. |
--hy-icon-button-motion-transition-hover-duration | var(--hy-motion-transition-hover-duration) | Transition duration for hover and focus state feedback |
--hy-icon-button-motion-transition-hover-easing | var(--hy-motion-transition-hover-easing) | Transition easing for hover and focus state feedback |
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
});