Skip to content

Alap for WordPress

WordPress plugin that adds the [alap] shortcode for multi-target link menus. No npm, no build step — just a PHP file and the IIFE build.

Both options below use SQLite — single container, no MySQL or MariaDB.

Quick Start

Pre-seeded WordPress with Alap active and demo content ready. No wizard, no activation step.

bash
cd plugins/wordpress/demo
./run.sh

# → "Alap WordPress demo running at http://localhost:8090"
# → Open browser, see a working page with Alap menus
# → Admin at http://localhost:8090/wp-admin (demo / demo)

Stop with:

bash
podman compose down

Option B — Fresh install (your own site)

Empty WordPress with SQLite. You run the install wizard and set it up yourself.

bash
cd plugins/wordpress
./run.sh

# → Asks: "Persist database across restarts? [y/N]"
# → "Starting WordPress (SQLite) with Alap plugin..."
# → Open http://localhost:8080, complete the install wizard
# → Activate "Alap" in WP Admin → Plugins

Stop with:

bash
podman compose down

Both options use Podman (or Docker — just replace podman with docker in the commands).

Usage

In any post or page:

Check out some [alap query=".coffee"]coffee spots[/alap] in the city.

Famous [alap query=".bridge"]bridges[/alap] you should visit.

A macro: [alap query="@cars"]favorite cars[/alap].

Each shortcode becomes an <alap-link> web component. Readers click to see a menu of destinations.

Install (manual, no containers)

  1. Copy the wordpress/ directory to wp-content/plugins/alap/
  2. Copy dist/alap.iife.js from the Alap build into the plugin directory
  3. Edit alap-config.js with your link library (see Configuration below)
  4. Activate "Alap" in WP Admin → Plugins

Configuration

How it works

The link library is stored as a JavaScript file (alap-config.js) in the plugin directory. This is a deliberate choice for the beta:

  • No database tables — nothing to migrate, back up separately, or lose during DB restores
  • No admin UI — no settings page to build, no PHP serialization edge cases
  • Portable — the same config file works in every Alap example, server, and integration
  • Version-controllable — commit it alongside your theme, track changes in git
  • No WordPress lock-in — if you move to a different CMS, the config file goes with you unchanged

The file is a plain JavaScript object assigned to var alapConfig. It's loaded as a <script> tag before the Alap runtime, so it's available when the web component initializes.

The config file

Edit alap-config.js in the plugin directory (wp-content/plugins/alap/alap-config.js):

js
var alapConfig = {
  settings: {
    listType: "ul",        // "ul" or "ol"
    menuTimeout: 5000,     // auto-dismiss after 5 seconds
    placement: "S"         // menu appears below the link (compass: N, NE, E, SE, S, SW, W, NW, C)
  },

  macros: {
    // Named shortcuts for complex expressions.
    // Use these in shortcodes: [alap query="@bridges"]...[/alap]
    bridges:    { linkItems: ".nyc + .bridge" },
    favorites:  { linkItems: "golden, bluebottle, highline" },
    cars:       { linkItems: ".car" }
  },

  allLinks: {
    // Each entry is a menu item. The key is the item ID.
    brooklyn: {
      label: "Brooklyn Bridge",
      url: "https://en.wikipedia.org/wiki/Brooklyn_Bridge",
      tags: ["nyc", "bridge", "landmark"]
    },
    golden: {
      label: "Golden Gate Bridge",
      url: "https://en.wikipedia.org/wiki/Golden_Gate_Bridge",
      tags: ["sf", "bridge", "landmark"]
    },
    bluebottle: {
      label: "Blue Bottle Coffee",
      url: "https://bluebottlecoffee.com",
      tags: ["coffee", "sf"]
    }
    // ... add your own links here
  }
};

Config structure

SectionPurposeExample
settingsGlobal defaults — list type, timeout, menu placement{ menuTimeout: 5000, placement: "S" }
macrosNamed expressions for reuse in shortcodes{ bridges: { linkItems: ".nyc + .bridge" } }
allLinksThe link library — every item that can appear in a menu{ brooklyn: { label: "...", url: "...", tags: [...] } }

Link entry fields

FieldRequiredDescription
labelYesDisplay text in the menu
urlYesDestination URL
tagsNoArray of tags for expression queries
cssClassNoCSS class added to this menu item
imageNoImage URL (renders <img> instead of text label)
altTextNoAlt text for image items
targetWindowNoLink target — "_self", "_blank", "fromAlap" (default: "fromAlap", opens in a named window)

Updating the config

  1. Edit alap-config.js directly (via FTP, file manager, or SSH)
  2. Changes take effect on the next page load — no cache to clear, no settings to save
  3. WordPress plugin updates will not overwrite your config file (it's in .gitignore and not part of the plugin distribution)

Multiple configs

For sites with separate link libraries (e.g., blog vs. docs):

js
// In alap-config.js
var alapConfig = { /* ... main config ... */ };
var docsConfig = { /* ... docs config ... */ };

Update alap.php to register both:

php
wp_add_inline_script('alap-config',
    'Alap.registerConfig(alapConfig);' .
    'Alap.registerConfig(docsConfig, "docs");' .
    'Alap.defineAlapLink();'
);

Then use the config attribute in shortcodes:

[alap query=".api" config="docs"]API reference[/alap]

Future: Admin UI

A future version may add a WordPress settings page for editing the config in the admin dashboard, stored in wp_options. The file-based approach will continue to work — the settings page would simply generate the same JavaScript output.

Shortcode Reference

Basic usage

[alap query=".coffee"]coffee spots[/alap]

All attributes

AttributeDescriptionExample
queryAlap expression.coffee, @bridges, brooklyn, .nyc + .bridge
configNamed config (optional)"docs"

Expression syntax

SyntaxMeaningExample
.tagAll items with this tag.coffee → all coffee items
itemIdSpecific item by IDbrooklyn → Brooklyn Bridge
@macroExpand a named macro@bridges → whatever the macro defines
.a + .bIntersection (both tags).nyc + .bridge → NYC bridges
.a | .bUnion (either tag).nyc | .sf → NYC or SF items
.a - .bSubtraction.nyc - .tourist → NYC without tourist tag

Examples in posts

<!-- Simple tag query -->
Here are some [alap query=".coffee"]great cafes[/alap] to try.

<!-- Specific item -->
Don't miss the [alap query="brooklyn"]Brooklyn Bridge[/alap].

<!-- Macro (best for complex expressions) -->
Check out these [alap query="@favorites"]personal picks[/alap].

<!-- Inline in a sentence -->
The city has amazing [alap query=".park"]parks[/alap],
iconic [alap query=".bridge"]bridges[/alap],
and world-class [alap query=".coffee"]coffee[/alap].

Styling

The plugin adds minimal default styles (dotted underline, pointer cursor) that inherit the theme's link color. Override in your theme's style.css or the WordPress Customizer:

Trigger (the clickable text)

css
alap-link {
  color: #0073aa;
  text-decoration: underline;
  text-decoration-style: dotted;
  text-underline-offset: 3px;
  cursor: pointer;
}

alap-link:hover {
  text-decoration-style: solid;
}

The menu renders inside a Shadow DOM, so you style it with ::part() selectors:

css
/* Menu container */
alap-link::part(menu) {
  background: #fff;
  border: 1px solid #ddd;
  border-radius: 4px;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
}

/* Individual menu items */
alap-link::part(link) {
  color: #333;
  padding: 0.5rem 1rem;
  font-size: 0.9rem;
}

alap-link::part(link):hover {
  background: #f0f0f0;
  color: #0073aa;
}

Available ::part() names

PartElement
menuThe menu container
listThe <ul> or <ol> inside the menu
itemEach <li>
linkEach <a> inside a list item
imageImage elements inside items (when using image items)

Dark theme example

css
alap-link::part(menu) {
  background: #1e1e2e;
  border-color: #444;
}

alap-link::part(link) {
  color: #cdd6f4;
}

alap-link::part(link):hover {
  background: #313244;
  color: #89b4fa;
}

Accessibility

The web component handles accessibility automatically:

  • role="button" and aria-haspopup="true" on the trigger
  • role="menu" and aria-label on the menu
  • role="menuitem" on each link
  • Keyboard navigation: Enter/Space to open, arrow keys to navigate, Escape to close
  • Focus management: first item receives focus on open, focus returns to trigger on close

No additional WordPress configuration needed.

How SQLite Works

Both container options use the SQLite Database Integration plugin from the WordPress Performance Team. This is an official WordPress project on the path to core inclusion — the same technology behind WordPress Playground.

The Dockerfile downloads the plugin at build time and places its db.php drop-in into wp-content/. WordPress then uses SQLite instead of MySQL for all database operations. Same tables (wp_options, wp_posts, wp_users), same queries, different backend.

Inspecting the database

The .ht.sqlite file is a standard SQLite database:

bash
sqlite3 wp-content/database/.ht.sqlite
sqlite> .tables
sqlite> SELECT option_name, option_value FROM wp_options
        WHERE option_name IN ('siteurl', 'blogname', 'active_plugins');
sqlite> SELECT post_title, post_status FROM wp_posts WHERE post_status = 'publish';
sqlite> .quit

Regenerating the demo seed database

The demo's .ht.sqlite is committed to the repo. To regenerate it:

  1. Boot Option B: ./run.sh
  2. Complete the install wizard (site: "Alap Demo", user: demo, password: demo)
  3. Activate the Alap plugin
  4. Set permalinks to /%postname%/ (Settings → Permalinks)
  5. Create a page with the demo shortcode content (see demo/seed/ for reference)
  6. Set it as the static front page (Settings → Reading)
  7. Delete "Hello World" post, sample page, sample comment
  8. Disable comments (Settings → Discussion)
  9. Extract the database:
bash
podman cp <container>:/var/www/html/wp-content/database/.ht.sqlite demo/seed/.ht.sqlite

Files

plugins/wordpress/
  alap.php                       Plugin — shortcode, script enqueue, styles
  alap-config.js                 Link library (edit this)
  alap.iife.js                   Alap runtime (copy from dist/)

  Dockerfile                     Option B — fresh WordPress + SQLite
  docker-compose.yml             Option B — single service, port 8080
  docker-compose.persist.yml     Option B — volume override for persistence
  run.sh                         Option B — build and start

  seed/
    wp-config.php                WordPress config for SQLite

  demo/
    Dockerfile                   Option A — pre-seeded instant demo
    docker-compose.yml           Option A — single service, port 8090
    run.sh                       Option A — build and start
    seed/
      wp-config.php              Demo-specific WordPress config
      .ht.sqlite                 Pre-seeded database (committed)

Requirements

  • WordPress 6.4+
  • PHP 7.4+
  • Podman or Docker
  • Modern browser (Chrome, Firefox, Safari, Edge — all support web components)

License

Apache-2.0