For 35 years, the anchor tag has worked the same way: one link, one destination, chosen by the author. It's so fundamental we stopped questioning it.
But what if a link could be an invitation instead of a demand — offering the reader a curated set of choices?
That's Alap.
Alap extends the idea of what we would expect from a link. We get dynamic menus via a simple attribute and a lightweight expression language. No proprietary formats, no lock-in, no framework required.
The project is open source (Apache 2.0) and published across five package registries (npm, and language parsers in: crates.io, PyPI, Go Modules, and Packagist).
Instead of hardcoding destinations, Alap uses a tag-based query language. You can use regular anchors with Alap attributes or the <alap-link> web component — your choice:
<!-- Standard anchor with Alap attributes --> <a class="alap" data-alap-linkitems=".coffee">coffee shops</a> <!-- Or as a web component --> <alap-link query=".coffee">coffee shops</alap-link> <!-- All bridges --> <alap-link query=".bridge">bridges</alap-link> <!-- Intersection — NYC bridges only --> <alap-link query=".nyc + .bridge">NYC bridges</alap-link> <!-- Macro — a named set of favorites --> <alap-link query="@favorites">favorites</alap-link>
Tags, intersections, unions, subtraction — compose your menus from a shared link library. Macros let you name and reuse common sets. The grammar is small enough to learn in minutes, powerful enough to express complex selections.
Alap can also pull links from the live web and from AT Protocol (Bluesky), so your menus can mix curated and dynamic sources:
<!-- Live search — Open Library JSON API --> <alap-link query=":web:books:decentralization:">Books: "decentralization"</alap-link> <!-- AT Protocol — recent posts from a Bluesky account --> <alap-link query=":atproto:feed:eff.org:limit=3:">EFF — recent posts</alap-link> <!-- Mix all three: static tags + web API + AT Protocol --> <alap-link query="(.orgs *limit:2*), (:web:books:decentralization:), (:atproto:feed:eff.org:limit=3:)"> Decentralization: orgs + books + EFF posts </alap-link>
Alap separates parsing from presentation. The expression engine resolves a set of links — what you do with them is up to you.
The default renderer shows a dropdown menu. Swap in the lightbox renderer and the same config can drive a carousel. The parser doesn't care. Build a side panel, a card grid, a command palette — the resolved links are just data.
Alap ships adapters for most major frameworks. Each one provides idiomatic components, hooks, and state management — not a wrapper around a generic widget.
| Adapter | Components | Notable |
|---|---|---|
| DOM | <alap-link> web component | Zero dependencies, no build step |
| React | AlapProvider, AlapLink, useAlap() | Hooks and context |
| Vue | AlapProvider, AlapLink, useAlap() | Composition API |
| Svelte | AlapProvider, AlapLink, useAlap() | Svelte 5 |
| Solid | AlapProvider, AlapLink, useAlap() | Fine-grained reactivity |
| Qwik | AlapProvider, AlapLink, useAlap() | Resumable, lazy by default |
| Alpine | x-alap directive | No build step needed |
| Astro | AlapLink.astro, AlapSetup.astro | Works in .astro and MDX |
React, Vue, Svelte, Solid, and Qwik adapters support three rendering modes: DOM, Web Component, and Popover API. Every adapter includes keyboard navigation, ARIA accessibility, click-outside dismissal, and a compass-based placement engine.
Drop Alap into your static site generator or documentation platform with zero-config setup.
| Integration | Platform | What It Does |
|---|---|---|
| astro-alap | Astro | Auto-injects web component setup; optional remark plugin for .md and .mdx |
| eleventy-alap | Eleventy | Dual-mode: static build-time resolution (plain <ul>) or interactive web components; shortcode + filters |
| hugo-alap | Hugo | Hugo module with alap shortcode; full expression syntax client-side |
| next-alap | Next.js | 'use client' re-exports; AlapLayout for app/layout.tsx; optional MDX support |
| nuxt-alap | Nuxt 3 | Client plugin factory; Vue component re-exports; Nuxt Content markdown support |
| vitepress-alap | VitePress | Vite plugin for custom element registration; use <alap-link> directly in markdown |
| qwik-alap | Qwik City | Vite plugin with auto-injection; supports both web components and Qwik components |
Content-level transforms for CMS pipelines, rich text editors, and markdown toolchains.
| Plugin | Environment | What It Does |
|---|---|---|
| remark-alap | Markdown (unified) | Transforms [text](alap:query) links to <alap-link> web components |
| rehype-alap | HTML (unified) | Transforms <a href="alap:query"> for headless CMS content (Contentful, Sanity, WordPress REST, etc.) |
| mdx | MDX / React | Remark plugin + React provider; emits <AlapLink> components resolved via context |
| tiptap-alap | Tiptap / ProseMirror | Inline <alap-link> nodes with insert/update/remove commands; Mod-Shift-A shortcut |
| WordPress | WordPress (PHP) | [alap query=".tag"] shortcode; SQLite-based; file config, no database tables |
I wrote idiomatic parsers in four languages — not mechanical ports, but implementations that feel native to each ecosystem.
| Language | Package | Capabilities |
|---|---|---|
| Rust | alap (crates.io) | Expression parsing, config merging, URL sanitization |
| Python | alap-python (PyPI) | Expression parsing, config merging, ReDoS guard |
| Go | alap-go (Go modules) | Expression parsing, config merging, URL sanitization |
| PHP | danielsmith/alap (Packagist) | Expression parsing, config merging, ReDoS validation |
All four implement the same expression grammar — item IDs, tag queries, operators, macros, regex search, and grouping — with full test suites. Use them for server-side resolution, build-time rendering, or API endpoints.
977 tests across 45 files — core parser, every framework adapter, integrations, plugins, security (URL sanitization, ReDoS guards, config validation). This is a beta because the API could shift, and I feel like polishing examples, filling in some gaps, and doing the web site.
I created example config editors in many frameworks — React, React ShadCN, Vue, Svelte, Solid, Alpine, Astro, and a React Design reference — to prove that the same editing experience works consistently across stacks.
Each editor supports JSON import/export as files and can call any of our example servers (Axum, Flask, Go, Express, PHP) for server-side resolution. They share the same UI: build a link library, write test queries, and preview resolved menus — all with drag-and-drop link import and client-side metadata extraction.
The point isn't that you need all of these editors. It's that any framework can drive the same config surface with the same results.
Many live examples — DOM, Web Components, React, Vue, Svelte, Solid, Qwik, Alpine, Astro, htmx, CMS content, markdown, and a lightbox renderer. Each one is self-contained and viewable in the examples gallery.
npm install alap
I'm Daniel Smith, and I've been thinking about aspects of this for 30 years. I published an early version of this back in 2012, and rewrote it in 2022, with an eye towards Vue and React. Version 3 is a complete rewrite in TypeScript. I worked in tandem with Claude Code to get this going, and I assure you this is no vibe coding project :)