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
  • Seven role variants: neutral, brand, success, warning, danger, info, and highlight for the one action that stands out — the button's set; each (variant, emphasis) cell paints as hy-button does
  • 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 label as the action ("Delete row"), not the glyph ("Trash").
  • Pair the button with an hy-tooltip carrying 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-button with 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. Keep href on 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 ​

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

Solid brand — the primary action of a compact row (a builder's commit) ​

html
<hy-icon-button name="chevron-right" label="Add this override" variant="brand" emphasis="solid">
</hy-icon-button>

Danger role on a plain toolbar glyph ​

html
<hy-icon-button name="trash" label="Delete row" variant="danger"></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>
html
<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="trash" disabled label="Delete item (disabled)"> </hy-icon-button>

Soft-disabled (tooltip explaining why stays reachable) ​

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

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

javascript
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 ​

javascript
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.)

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

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

  • base - The button's base wrapper element (either <button> or <a> tag)

Theming via CSS custom properties ​

css
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 ​

PropertyAttributeTypeDefaultDescription
namenamestring | undefined—The 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 - 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
variantvariant'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)
emphasisemphasis'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)
hrefhrefstring | 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.
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 | undefined—Prompts the browser to download the linked file with this filename. Only used when href is set.
relrelstring | undefined—The link's rel. Only used when href is set. Unset, a target="_blank" link gets noreferrer noopener.
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.
pressedpressed'true' | 'false'nullIndicates 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-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 ​

PartDescription
baseThe 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.

PropertyDefaultDescription
--hy-icon-button-stroke-smvar(--hy-stroke-sm)Border width for outlined emphasis
--hy-icon-button-background-brand-solid-restvar(--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-smvar(--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-mdvar(--hy-control-height-md)Outer height for medium size variant.
--hy-icon-button-control-height-lgvar(--hy-control-height-lg)Outer height for large size variant.
--hy-icon-button-motion-transition-hover-durationvar(--hy-motion-transition-hover-duration)Transition duration for hover and focus state feedback
--hy-icon-button-motion-transition-hover-easingvar(--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 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);
});
javascript
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');
});
javascript
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.