Skip to content

HyIconButton

Usage examples on this page are written for Lit / plain HTML (<hy-icon-button>). The same component ships as HyIconButton in @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

html
<hy-icon-button name="settings" label="Open settings"></hy-icon-button>

Soft emphasis

html
<hy-icon-button name="settings" label="Open settings" emphasis="soft"> </hy-icon-button>

Outlined emphasis

html
<hy-icon-button name="settings" label="Open settings" emphasis="outlined"> </hy-icon-button>

Round shape with soft emphasis

html
<hy-icon-button name="settings" label="Open settings" shape="round" emphasis="soft">
</hy-icon-button>
javascript
<hy-icon-button
  name="download"
  href="/file.pdf"
  download="document.pdf"
  label="Download PDF"
></hy-icon-button>
html
<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

html
<hy-icon-button name="delete" disabled label="Delete item (disabled)"> </hy-icon-button>

Soft-disabled (tooltip explaining why stays reachable)

html
<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

html
<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>
html
<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

html
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

javascript
const button = document.querySelector('hy-icon-button');

// Programmatically click
button.click();

// Programmatically focus
button.focus();

// Programmatically blur
button.blur();

Toolbar with Mixed Appearances

html
<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 changes

  • detail.pressed: Boolean indicating new pressed state (true/false)

  • detail.originalEvent: The original event that triggered the toggle

  • focus - Fired when button receives focus

  • detail.originalEvent: The original FocusEvent

  • blur - Fired when button loses focus

  • detail.originalEvent: The original FocusEvent

  • escape - Fired when Escape key is pressed while focused

  • detail.originalEvent: The original KeyboardEvent

  • Useful for closing associated menus or dialogs

Keyboard Support

  • Tab - Moves focus to/from the button
  • Shift + Tab - Moves focus in reverse order
  • Enter - 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 fires escape event

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

PropertyAttributeTypeDefaultDescription
namenamestring | undefinedThe name of the icon to display. Must match an icon in the configured icon set.
sizesize'small' | 'medium' | 'large''medium'The size of the icon button.
shapeshape'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
emphasisemphasis'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)
hrefhrefstring | undefinedWhen set, renders the button as an <a> tag with this href instead of a <button>. The component automatically handles link-specific behavior and attributes.
targettarget'_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
downloaddownloadstring | undefinedPrompts the browser to download the linked file with this filename. Only used when href is set.
labellabelstring''An accessible label that describes what the icon button does. Required for accessibility - if not provided, you must use aria-label or aria-labelledby.
disableddisabledbooleanfalseDisables 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
softDisabledsoft-disabledbooleanfalseSoft-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.
pressedpressedstring | nullnullIndicates the pressed state of a toggle button.
ariaDescribedbyaria-describedbystring | nullnullReferences the ID of an element that provides additional description. Useful for help text, expanded descriptions, or important context.
ariaExpandedaria-expandedstring | nullnullIndicates whether an element controlled by this button is expanded or collapsed. Used with collapsible content, dropdowns, accordions, or disclosure widgets.
ariaControlsaria-controlsstring | nullnullIndicates that this button controls another element or group of elements. Should reference the ID(s) of the controlled element(s).
ariaHaspopuparia-haspopupstring | nullnullIndicates that the button triggers a popup element.
autofocusautofocusbooleanfalseMarks 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

EventDetailDescription
press-changeFired when a toggle button's pressed state changes. Detail: { pressed: boolean, originalEvent: MouseEvent | KeyboardEvent }
focusFired when the button receives focus. Detail: { originalEvent: FocusEvent }
blurFired when the button loses focus. Detail: { originalEvent: FocusEvent }
escapeFired when Escape is pressed while focused; useful for closing associated menus or dialogs. Detail: { originalEvent: KeyboardEvent }

CSS Parts

PartDescription
baseThe icon button's base wrapper element (button or anchor tag)

CSS Custom Properties

PropertyDescription
--hy-icon-button-strokeBorder width for outlined appearance
--hy-icon-button-height-smOuter 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-mdOuter height for medium size variant. Default: var(--hy-control-height-md)
--hy-icon-button-height-lgOuter height for large size variant. Default: var(--hy-control-height-lg)
--hy-icon-button-hover-durationTransition duration for hover and focus state feedback
--hy-icon-button-hover-easingTransition easing for hover and focus state feedback
--hy-icon-button-foreground-default-restResting 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 click bubbles naturally; optionally fires press-change for toggles
  • Safe to call during SSR or before first render
javascript
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);
});
css
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 focus event when focused
  • Safe to call during SSR or before first render

Parameters:

  • options - Optional focus options
  • options.preventScroll - If true, prevents scrolling to the focused element
javascript
const iconButton = document.querySelector('hy-icon-button');

// Focus the button
iconButton.focus();

// Listen for focus event
iconButton.addEventListener('focus', () => {
  console.log('Button focused');
});
css
const iconButton = document.querySelector('hy-icon-button');

// Focus without scrolling viewport
iconButton.focus({ preventScroll: true });
javascript
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 blur event when blurred
  • Safe to call during SSR or before first render
javascript
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');
});
javascript
const menuButton = document.querySelector('hy-icon-button[aria-haspopup="menu"]');

menuButton.addEventListener('escape', () => {
  closeMenu();
  menuButton.blur(); // Remove focus after closing
});

Built with Lit. Documented with VitePress.