Skip to content

HyIconButton

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


### Styling with CSS Parts

```css
hy-icon-button::part(base) {
border-radius: 8px;
}

hy-icon-button::part(base):hover {
transform: scale(1.05);
}

Methods

connectedCallback()

Called when the element is added to the DOM. Validates accessibility requirements (client-side only for SSR safety).

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
});

render()

Renders the icon button as either a button or link element. Handles all attributes, ARIA properties, and event listeners.

Built with Lit. Documented with VitePress.