Vanilla DOM Adapter
The AlapUI class binds Alap menus to vanilla HTML. No framework required.
Setup
import { AlapUI } from 'alap';
const ui = new AlapUI(config, {
selector: '.alap',
menuTimeout: 5000,
onItemHover: (detail) => { ... },
onItemContext: (detail) => { ... },
});Constructor options
| Option | Type | Default | Description |
|---|---|---|---|
selector | string | '.alap' | CSS selector for trigger elements |
menuTimeout | number | from config | Auto-dismiss timeout in ms |
onTriggerHover | function | — | Callback when mouse enters trigger |
onTriggerContext | function | — | Callback on right-click of trigger |
onItemHover | function | — | Callback when mouse enters menu item |
onItemContext | function | — | Callback on right-click/ArrowRight of menu item |
See Events for callback detail types.
Methods
| Method | Description |
|---|---|
refresh() | Re-scan DOM for new trigger elements |
updateConfig(config) | Replace config and re-scan |
destroy() | Remove all event listeners and menu container |
HTML attributes
| Attribute | On | Description |
|---|---|---|
class="alap" | Trigger | Default selector (configurable via selector) |
data-alap-linkitems | Trigger | Expression to evaluate |
data-alap-existing | Trigger | Per-anchor existingUrl override: "prepend", "append", "ignore" |
data-alap-placement | Trigger | Per-anchor placement override: "N", "NE", "E", "SE", "S", "SW", "W", "NW", "C" |
<a class="alap" data-alap-linkitems=".coffee">cafes</a>
<a class="alap" data-alap-linkitems=".nyc + .bridge">NYC bridges</a>
<a class="alap" data-alap-linkitems="@favorites" id="favorites">my picks</a>ARIA (set automatically)
| Attribute | On | Value |
|---|---|---|
role | Trigger | "button" |
aria-haspopup | Trigger | "true" |
aria-expanded | Trigger | "true" when open, "false" when closed |
tabindex | Trigger | "0" (if not already focusable) |
role | Menu | "menu" |
aria-labelledby | Menu | Trigger's id (if present) |
role | <li> | "none" |
role | <a> | "menuitem" |
Generated markup
<div id="alapelem" class="alapelem alap_mylink" role="menu" aria-labelledby="mylink">
<ul>
<li class="alapListElem" role="none">
<a href="..." role="menuitem" tabindex="-1"
data-alap-hooks="item-hover item-context"
data-alap-guid="a1b2c3d4..."
data-alap-thumbnail="https://...">
Link Label
</a>
</li>
</ul>
</div>Styling
The DOM adapter creates a single shared menu container. No shadow boundary — the full CSS cascade applies.
Selectors
| Selector | Element | Description |
|---|---|---|
#alapelem | <div> | Menu container (shared, repositioned per trigger) |
.alap_${anchorId} | <div> | Per-anchor class (when trigger has an id) |
.alapListElem | <li> | Every list item |
Per-item cssClass
The cssClass field on an AlapLink is applied to the <li>:
{ "label": "Special", "url": "...", "cssClass": "featured" }<li class="alapListElem featured" role="none">...</li>Basic styling
#alapelem {
background: white;
border: 1px solid #ddd;
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
padding: 4px 0;
}
#alapelem a {
display: block;
padding: 8px 16px;
color: #333;
text-decoration: none;
}
#alapelem a:hover, #alapelem a:focus {
background: #f0f7ff;
color: #2563eb;
}Positioning
Alap uses a compass-based placement engine. The menu is positioned relative to the trigger, with automatic fallback when the preferred position doesn't fit in the viewport.
Placement directions
NW N NE
┌────┬────┐
W │ trigger │ E
└────┴────┘
SW S SE
(C = centered over trigger)Default: SE (below, left-aligned). Set globally via settings.placement or per-trigger via data-alap-placement:
<a class="alap" data-alap-linkitems=".coffee" data-alap-placement="N">above me</a>
<a class="alap" data-alap-linkitems=".coffee" data-alap-placement="E">beside me</a>
<a class="alap" data-alap-linkitems=".coffee" data-alap-placement="C">over me</a>Viewport containment
When viewportAdjust is true (default):
- If the preferred placement overflows the viewport, Alap tries the opposite side, then adjacent positions
- If no placement fits fully, the menu is clamped to the available space with vertical scrolling
- The menu never causes the page to scroll — uses
overflow: clipto prevent layout shift placementGapcontrols the pixel gap between trigger and menu (default: 4)viewportPaddingcontrols the minimum distance from viewport edges (default: 8)
Image triggers
Image triggers use a point rect at the click coordinates. The placement engine positions the menu relative to the click point using the same compass logic and fallback behavior.
Static positioning
Set viewportAdjust: false to disable the placement engine. The menu is positioned below the trigger with no viewport awareness — the legacy behavior.
Examples
Hover preview panel:
const preview = document.getElementById('preview');
const ui = new AlapUI(config, {
onItemHover: ({ link }) => {
preview.innerHTML = `
<img src="${link.thumbnail}" />
<p>${link.description}</p>
`;
preview.hidden = false;
},
});Dynamically added content:
ui.refresh(); // after AJAX loads new .alap links
ui.updateConfig(updatedConfig); // after config changes
ui.destroy(); // cleanup on SPA route changeExisting URL preservation:
<!-- Original href becomes the first menu item (default: prepend) -->
<a class="alap" href="https://example.com" data-alap-linkitems=".coffee">
example.com and coffee
</a>
<!-- Override per-link -->
<a class="alap" href="https://example.com" data-alap-linkitems=".coffee" data-alap-existing="append">
coffee then example.com
</a>