HyLink
Usage examples on this page are written for Lit / plain HTML (
<hy-link>). The same component ships asHyLinkin@whitespaceux/harmony-react(native React 19), with typed wrappers for Angular, Solid, Svelte, and Vue. The API reference below applies to all frameworks.
A versatile link component that provides enhanced styling and functionality over standard anchor tags. Supports multiple visual variants, size options, disabled states, comprehensive accessibility features, form integration, and flexible content slots. Optimized for performance with hardware acceleration and includes full keyboard navigation, screen reader support, responsive design capabilities, high contrast mode support, reduced motion preferences, and print-friendly styling.
Click gating: native click events bubble normally. When disabled, clicks are stopped (stopPropagation + preventDefault) on the host so external listeners never see them.
Features
- Multiple variants (default, emphasis, navigation) and sizes (small, medium, large)
- Conditional gap spacing - gaps only appear between prefix/suffix content for default and emphasis variants
- Enhanced accessibility with ARIA support, high contrast mode and reduced motion
- Form integration with validation and comprehensive error handling
- Container queries for responsive behavior
- Dark mode and high contrast support with automatic color adjustments
- Performance optimized with hardware acceleration and CSS transforms
- Touch-friendly targets on mobile devices
- Print-friendly styles with URL display for external links
When to use
- Going to another page, view or resource, inside a sentence or on its own.
- A reference people may open in a new tab: a help page, a document, a record.
- Navigation in a header, a footer or a list of pages.
Do
- Keep links inside running text as part of the sentence.
- Show when a link leaves the product.
When not to use
- Activation changes something on the page or in the data:
hy-button. - The main call to action of a page, even one that navigates:
hy-buttonwithhref. - A glyph with no text that acts or navigates:
hy-icon-button. - Submitting a form:
hy-button type="submit". Keep the link's form-submit mode for layouts where the submit must read as text. - Section navigation inside an app, where each item is a page:
hy-tab-nav. - The trail of pages above the current one:
hy-breadcrumb.
Accessibility: your part
- Make the link text name where it goes, so it makes sense read out of context. Never "click here" or "more".
- Say in the link text, or with a named
suffixicon, that adownloadlink saves a file.
Examples
Basic usage
<hy-link href="/about">About us</hy-link>Different sizes and variants
<hy-link href="/profile" variant="emphasis" size="large"> Large Emphasis Link </hy-link>
<hy-link href="/settings" size="small">Small Link</hy-link>Navigation variant with bottom border indicator
<hy-link href="/home" variant="navigation">Home</hy-link>
<hy-link href="/about" variant="navigation" selected>About</hy-link>With slots for icons (automatic gap spacing)
<hy-link href="/profile" variant="emphasis">
<hy-icon name="user" slot="prefix"></hy-icon>
User Profile
<hy-icon name="arrow-right" slot="suffix"></hy-icon>
</hy-link>Form submission with validation
<hy-link href="#" form="my-form" formaction="/api/submit" formmethod="post"> Submit Form </hy-link>
<script>
const link = document.querySelector('hy-link[form="my-form"]');
link.addEventListener('form-submit', handleSubmit); // cancelable
link.addEventListener('validation-error', handleValidationError);
</script>External link with accessibility
<hy-link
href="https://example.com"
target="_blank"
variant="emphasis"
aria-label="Visit Example.com (opens in new window)"
>
External Link
<hy-icon name="external-link" slot="suffix"></hy-icon>
</hy-link>Disabled and selected states
<hy-link href="/current" selected>Current Page</hy-link>
<hy-link href="/disabled" disabled>Disabled Link</hy-link>API
Properties
| Property | Attribute | Type | Default | Description |
|---|---|---|---|---|
href | href | string | '#' | The URL to navigate to when clicked. Use '#' for form submission or button behavior. |
target | target | '_blank' | '_parent' | '_self' | '_top' | — | Tells the browser where to open the link. |
download | download | string | undefined | — | Tells the browser to download the linked file as this filename. |
rel | rel | string | undefined | — | The link's relationship to the current page, written to the anchor's rel. When omitted, target="_blank" gets noreferrer noopener; a value you set replaces that default, so include noopener yourself when you open a new window. |
variant | variant | 'default' | 'emphasis' | 'navigation' | 'default' | The variant type of the link. - default: Standard link with underline decoration - emphasis: Default ink with a brand underline - navigation: Neutral link with padding and a brand indicator under the current page (selected); non-current items rest in subtle ink and hover with a neutral hairline — the brand colors the indicator, never the ink |
size | size | 'small' | 'medium' | 'large' | 'medium' | The size of the link. Affects font size, spacing, and touch targets. |
disabled | disabled | boolean | false | Disables the link and prevents all interactions. The anchor drops its href (and target / download) while disabled, so no gesture can follow it; it stays announced as a disabled link when the user reaches it — the change itself is not announced (no live region), as in every major system. It is drawn as disabled text: the disabled ink and no underline, in every variant. |
selected | selected | boolean | false | Marks the link as selected/active. Useful for navigation menus. Shows bottom border for navigation variant. |
form | form | string | undefined | — | The form to submit when href="#". References a form element by ID. Triggers form-submit event. |
formaction | formaction | string | undefined | — | The URL to submit the form data to when href="#". Overrides form's action attribute. |
formmethod | formmethod | 'get' | 'post' | — | The HTTP method to use when submitting the form when href="#". Overrides form's method attribute. |
formenctype | formenctype | 'application/x-www-form-urlencoded' | 'multipart/form-data' | 'text/plain' | — | The encoding type for form submission when href="#". Overrides form's enctype attribute. |
formnovalidate | formnovalidate | boolean | false | Indicates that the form should not be validated when submitted via this link. |
formtarget | formtarget | '_blank' | '_parent' | '_self' | '_top' | — | Specifies where to display the response after submitting the form. Overrides form's target attribute. |
ariaLabel | aria-label | string | null | null | Custom ARIA label for enhanced accessibility. Automatically enhanced with contextual information. |
ariaDescribedby | aria-describedby | string | null | null | ID of element that describes this link for screen readers. |
ariaExpanded | aria-expanded | 'true' | 'false' | null | Indicates if link controls an expandable element (true/false/null for none). |
disabledLabel | disabled-label | string | '(disabled)' | The note a screen reader hears after the link text while disabled. Use for internationalization. |
newWindowLabel | new-window-label | string | '(opens in new window)' | The note a screen reader hears after the link text when target="_blank". Use for internationalization. |
Events
| Event | Detail | Description |
|---|---|---|
focus | — | Fired when the link receives focus |
blur | — | Fired when the link loses focus |
form-submit | — | Fired when form submission is triggered (cancelable) |
validation-error | — | Fired when form validation fails |
form-error | — | Fired when form submission encounters an error |
Slots
| Slot | Description |
|---|---|
default | The link text. |
prefix | Content to show before the link text (icons, badges, etc.). Creates automatic gap spacing for default and emphasis variants. |
suffix | Content to show after the link text (icons, arrows, etc.). Creates automatic gap spacing for default and emphasis variants. |
CSS Parts
| Part | Description |
|---|---|
base | The link's base anchor element |
label | The link's text content wrapper |
prefix | The prefix slot wrapper |
suffix | The suffix slot wrapper |
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-link-text-transform | none | Text transform for link text |
--hy-link-underline-offset | — | Text underline offset |
--hy-link-text-decoration | underline | The default variant's underline at rest: underline or none. |
--hy-link-text-decoration-hover | underline | The default variant's underline on hover. |
--hy-link-stroke-md | var(--hy-stroke-md) | Thickness of the navigation variant's current-page indicator. (the rung hy-tab-nav-item reads) |
--hy-link-foreground-subtle-rest | var(--hy-foreground-subtle-rest) | Ink of a navigation item that is not the current page. |
--hy-link-foreground-subtle-hover | var(--hy-foreground-subtle-hover) | That ink on hover. |
--hy-link-foreground-subtle-pressed | var(--hy-foreground-subtle-pressed) | That ink when pressed. |
--hy-link-foreground-default-hover | var(--hy-foreground-default-hover) | Ink of the current navigation item and of an emphasis link on hover. |
--hy-link-foreground-default-pressed | var(--hy-foreground-default-pressed) | Ink of the current navigation item and of a default or emphasis link when pressed. |
--hy-link-border-default-hover | var(--hy-border-default-hover) | The neutral hairline under a hovered non-current navigation item. |
--hy-link-border-default-pressed | var(--hy-border-default-pressed) | That hairline when the item is pressed. |
--hy-link-border-brand-hover | var(--hy-border-brand-hover) | The current-page indicator on hover. |
--hy-link-border-brand-pressed | var(--hy-border-brand-pressed) | The current-page indicator and the emphasis underline when pressed. |
--hy-link-padding-row-nav-sm-block | var(--hy-padding-row-nav-sm-block) | Navigation variant: block padding — the gap between the text and the indicator. |
--hy-link-padding-row-nav-sm-inline | var(--hy-padding-row-nav-sm-inline) | Navigation variant, small: inline padding — how far the indicator runs past the text. |
--hy-link-padding-row-nav-md-inline | var(--hy-padding-row-nav-md-inline) | Navigation variant, medium: inline padding — how far the indicator runs past the text. |
--hy-link-padding-row-nav-lg-inline | var(--hy-padding-row-nav-lg-inline) | Navigation variant, large: inline padding — how far the indicator runs past the text. |
--hy-link-motion-transition-hover-duration | var(--hy-motion-transition-hover-duration) | Transition duration for hover and focus state feedback |
--hy-link-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 link. Respects disabled state.
const link = document.querySelector('hy-link');
if (!link.disabled) {
link.click(); // Programmatically trigger the link
}focus()
Sets focus on the link. Automatically skipped if disabled.
Parameters:
options- Optional focus options (preventScroll, etc.)
const link = document.querySelector('hy-link');
link.focus({ preventScroll: true });blur()
Removes focus from the link.
const link = document.querySelector('hy-link');
link.blur();