Skip to content

Web Component

The <alap-link> custom element. Works in any HTML — no framework required.

Setup

typescript
import { defineAlapLink, registerConfig } from 'alap';

defineAlapLink();           // registers <alap-link> custom element
registerConfig(config);     // feed config to the registry

Functions

FunctionSignatureDescription
defineAlapLink()(tagName?: string) => voidRegister the custom element. Default tag: 'alap-link'. Safe to call multiple times.
registerConfig()(config: AlapConfig, name?: string) => voidRegister a config. Default name: '_default'
updateRegisteredConfig()(config: AlapConfig, name?: string) => voidUpdate a registered config

HTML attributes

AttributeTypeRequiredDescription
querystringYesExpression to evaluate
configstringNoNamed config to use. Default: '_default'
hrefstringNoExisting URL — included in menu per existingUrl setting
placementstringNoMenu placement: N, NE, E, SE, S, SW, W, NW, C. Overrides config
html
<alap-link query=".coffee">cafes</alap-link>
<alap-link query=".nyc + .bridge">NYC bridges</alap-link>
<alap-link query="@favorites" placement="N">my picks (menu above)</alap-link>

Multi-config

typescript
registerConfig(newsConfig, 'news');
registerConfig(docsConfig, 'docs');
defineAlapLink();
html
<alap-link query=".breaking" config="news">latest stories</alap-link>
<alap-link query=".api" config="docs">API reference</alap-link>

ARIA (set automatically)

AttributeValue
role"button" (on host, if not already set)
aria-haspopup"true"
aria-expanded"true" when open, "false" when closed
tabindex"0" (if not already focusable)

Shadow DOM structure

<alap-link>
  (light DOM — your content)
  #shadow-root (open)
    <style>...</style>
    <slot></slot>
    <div class="menu" role="menu" aria-hidden="true" part="menu">
      <ul part="list">
        <li role="none" part="item">
          <a role="menuitem" part="link" tabindex="-1">
            Link Label
          </a>
        </li>
      </ul>
    </div>

Custom events

All events bubble and are composed (pierce the shadow boundary). Listen at any ancestor or on document.

EventDetail TypeWhen
alap:trigger-hoverTriggerHoverDetailMouse enters trigger
alap:trigger-contextTriggerContextDetailRight-click on trigger
alap:item-hoverItemHoverDetailMouse enters menu item
alap:item-contextItemContextDetailRight-click or ArrowRight on menu item
alap:item-context-dismissItemContextDismissDetailArrowLeft on menu item
javascript
document.addEventListener('alap:item-hover', (e) => {
  const { id, link } = e.detail;
  showPreview(link.thumbnail, link.description);
});

See Events for full detail type definitions.

Styling

The web component uses an open Shadow DOM. External CSS cannot reach inside — use ::part() selectors and --alap-* custom properties instead.

::part() selectors

SelectorElementDescription
alap-link::part(menu)<div>Menu container
alap-link::part(list)<ul> / <ol>The list element
alap-link::part(item)<li>Each list item
alap-link::part(link)<a>Each link anchor
alap-link::part(image)<img>Image inside a link (when image field is set)

CSS custom properties

Set these on alap-link or any ancestor. They cross the shadow boundary.

PropertyDefaultDescription
--alap-bg#ffffffMenu background
--alap-border#e5e7ebMenu border color
--alap-border-width1pxMenu border width
--alap-radius6pxMenu border radius
--alap-corner-shaperoundCorner geometry: round, squircle, scoop, notch, bevel, straight
--alap-shadow0 4px 12px rgba(0,0,0,0.1)Menu box shadow
--alap-drop-shadow(unset)Menu filter: drop-shadow()
--alap-opacity1Menu container opacity
--alap-backdrop(unset)Menu backdrop-filter (e.g. blur(10px))
--alap-min-width200pxMinimum menu width
--alap-max-widthnoneMaximum menu width
--alap-z-index10Menu stacking order
--alap-gap0.5remSpace between trigger and menu
--alap-menu-padding0.25rem 0Padding on the list
--alap-menu-transition(unset)Transition for menu properties

Items

PropertyDefaultDescription
--alap-item-border(unset)Border on each li
--alap-item-border-radius(unset)Border radius on each li
--alap-item-gap0Vertical space between items
PropertyDefaultDescription
--alap-fontinheritFont family
--alap-text#1a1a1aLink text color
--alap-font-size0.9remLink font size
--alap-font-weight(inherit)Link font weight
--alap-letter-spacing(unset)Letter spacing
--alap-text-decorationnoneText decoration
--alap-text-transform(unset)Text transform
--alap-padding0.5rem 1remLink padding
--alap-cursorpointerCursor style
--alap-transition(unset)Transition for hover/focus effects

Hover

PropertyDefaultDescription
--alap-hover-bg#eff6ffHover background
--alap-hover-text#2563ebHover text color
--alap-hover-shadow(unset)Box shadow on hover
--alap-hover-text-shadow(unset)Text shadow on hover
--alap-hover-transform(unset)Transform on hover (e.g. translateX(4px))
--alap-hover-border(unset)Border on hover
--alap-dim-unhovered(unset)Opacity for non-hovered items (e.g. 0.5)

Focus

PropertyDefaultDescription
--alap-focus-ring#2563ebKeyboard focus outline color
--alap-focus-bg(falls back to hover-bg)Background on focus
--alap-focus-text(falls back to hover-text)Text color on focus

Images

PropertyDefaultDescription
--alap-img-max-height4remMax height for image items
--alap-img-radius3pxBorder radius for image items

Scrollbar

PropertyDefaultDescription
--alap-scrollbar-widththinScrollbar width
--alap-scrollbar-thumb#cbd5e1Scrollbar thumb color
--alap-scrollbar-tracktransparentScrollbar track color

Theming examples

Dark theme:

css
alap-link {
  --alap-bg: #1e1e2e;
  --alap-border: #313244;
  --alap-text: #cdd6f4;
  --alap-hover-bg: #313244;
  --alap-hover-text: #89b4fa;
  --alap-focus-ring: #89b4fa;
  --alap-radius: 12px;
}

Glassmorphism:

css
alap-link {
  --alap-bg: rgba(255, 255, 255, 0.15);
  --alap-backdrop: blur(12px) saturate(180%);
  --alap-border: rgba(255, 255, 255, 0.2);
  --alap-radius: 16px;
  --alap-corner-shape: squircle;
}

Animated open:

css
.animated::part(menu) {
  display: block !important;
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
  transform: translateY(6px);
  transition: opacity 0.2s ease, visibility 0.2s, transform 0.2s ease;
}
.animated[aria-expanded="true"]::part(menu) {
  opacity: 1;
  visibility: visible;
  pointer-events: auto;
  transform: translateY(0);
}

Positioning

The web component uses the same compass-based placement engine as the DOM adapter. Set placement globally via settings.placement or per-element via the placement attribute:

html
<alap-link query=".coffee" placement="S">centered below</alap-link>
<alap-link query=".coffee" placement="N">above me</alap-link>
<alap-link query=".coffee" placement="E">beside me</alap-link>
<alap-link query=".coffee" placement="C">centered over me</alap-link>

The --alap-gap CSS custom property controls the gap between trigger and menu. The placement engine reads this value automatically. You can also set placementGap in config settings — the CSS variable takes priority when set.

See Configuration for the full placement reference.

CDN / IIFE build

html
<script src="https://cdn.jsdelivr.net/npm/alap@3/dist/alap.iife.js"></script>
<script>
  Alap.defineAlapLink();
  Alap.registerConfig({
    allLinks: {
      reuters: { url: 'https://reuters.com', label: 'Reuters', tags: ['news'] },
      ap:      { url: 'https://apnews.com',  label: 'AP News', tags: ['news'] },
    },
  });
</script>

<alap-link query=".news">sources</alap-link>

Size: ~27 KB (8.2 KB gzipped)

Browser support

All --alap-* properties work in every modern browser (Chrome 49+, Firefox 31+, Safari 9.1+, Edge 15+).

The corner-shape property requires Chrome/Edge 134+. Older browsers fall back to standard border-radius with no errors or layout shift.