Reusable Alpine

Evaluate plugins and CSP

Add official plugins or a CSP-compatible build only when the deployment and feature justify them.

Alpine ships a set of official plugins. Focus, Persist, Intersect, Collapse, Mask, Anchor, Morph, Sort. Each one adds a directive or a magic for one job.

They’re good. My advice is still to add one only after the native version has become a real problem. Every plugin is another script, another API to learn, and another thing to check when Alpine upgrades.

The focus example

The editor needs focus to move into the title field and back to the Edit button. We did that with $refs and $nextTick in a few lines.

The Focus plugin adds x-trap, which keeps Tab and Shift+Tab inside an element while a condition is true:

<form x-show="editing" x-trap="editing">

Do we need it? For an inline editor, no. Tabbing out into the issue list is fine. For a modal dialog, yes: focus must not escape behind the overlay, and a correct focus trap is fiddly to write by hand.

So the board adopts Focus the day the editor becomes a modal, and not before. That’s the pattern for every plugin. Name the requirement first, then check whether the plugin beats ten lines of your own code.

Adding a plugin

Plugins load as separate scripts, before Alpine’s core:

<script defer src="https://cdn.jsdelivr.net/npm/@alpinejs/focus@3/dist/cdn.min.js"></script>
<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3/dist/cdn.min.js"></script>

Order matters. Load the plugin after core and x-trap silently does nothing.

Content Security Policy

A Content Security Policy is an HTTP header that tells the browser which scripts may run. A strict one forbids 'unsafe-eval'. Regular Alpine needs it, because it turns your inline expressions into functions with new Function().

If your deployment sets a strict CSP, the console fills with errors and nothing works. Either loosen the policy, or switch to Alpine’s CSP build:

<script defer nonce="a23gbfz9e" src="https://cdn.jsdelivr.net/npm/@alpinejs/csp@3/dist/cdn.min.js"></script>

The CSP build evaluates expressions with its own small parser. Simple ones work as before: count++, open = !open, issue.title, saving ? 'Saving…' : 'Save'. Some things don’t: arrow functions, template literals, spread, calls to globals like console.log or Math.max, and x-html.

That pushes logic out of the markup and into Alpine.data components, where it belonged anyway. x-show="issues.filter(i => i.status === 'open').length > 0" becomes x-show="hasOpenIssues" with a getter.

Don’t weaken the policy for convenience

The shortcut is adding 'unsafe-eval' back so inline expressions keep working. That reopens a class of injection attacks to save a refactor. If the policy is strict, someone chose that. Use the CSP build.

Test with the production headers, not your local dev server. Open the deployed page, check the console for CSP violations, and click through the board once.

Try this on your board: pick one plugin you’re tempted by, write down the lines it replaces, and decide. Then run curl -I against your deployment and check whether 'unsafe-eval' is in the CSP header.

Lesson completed