A rugged, minimal framework for composing JavaScript behavior in your markup.

Related tags

Frameworks alpine
Overview

Alpine.js

npm bundle size npm version Chat

Alpine.js offers you the reactive and declarative nature of big frameworks like Vue or React at a much lower cost.

You get to keep your DOM, and sprinkle in behavior as you see fit.

Think of it like Tailwind for JavaScript.

Note: This tool's syntax is almost entirely borrowed from Vue (and by extension Angular). I am forever grateful for the gift they are to the web.

Translated documentation

Language Link for documentation
Arabic التوثيق باللغة العربية
Chinese Simplified 简体中文文档
Chinese Traditional 繁體中文說明文件
German Dokumentation in Deutsch
Indonesian Dokumentasi Bahasa Indonesia
Japanese 日本語ドキュメント
Portuguese Documentação em Português
Russian Документация на русском
Spanish Documentación en Español
Turkish Türkçe Dokümantasyon
Français Documentation en Français
Korean 한국어 문서

Install

From CDN: Add the following script to the end of your <head> section.

<script src="https://cdn.jsdelivr.net/gh/alpinejs/[email protected]/dist/alpine.min.js" defer></script>

That's it. It will initialize itself.

For production environments, it's recommended to pin a specific version number in the link to avoid unexpected breakage from newer versions. For example, to use version 2.8.2 (latest):

<script src="https://cdn.jsdelivr.net/gh/alpinejs/[email protected]/dist/alpine.min.js" defer></script>

From npm: Install the package from npm.

npm i alpinejs

Include it in your script.

import 'alpinejs'

For IE11 support Use the following scripts instead.

<script type="module" src="https://cdn.jsdelivr.net/gh/alpinejs/[email protected]/dist/alpine.min.js"></script>
<script nomodule src="https://cdn.jsdelivr.net/gh/alpinejs/[email protected]/dist/alpine-ie11.min.js" defer></script>

The pattern above is the module/nomodule pattern that will result in the modern bundle automatically loaded on modern browsers, and the IE11 bundle loaded automatically on IE11 and other legacy browsers.

Use

Dropdown/Modal

<div x-data="{ open: false }">
    <button @click="open = true">Open Dropdown</button>

    <ul
        x-show="open"
        @click.away="open = false"
    >
        Dropdown Body
    </ul>
</div>

Tabs

<div x-data="{ tab: 'foo' }">
    <button :class="{ 'active': tab === 'foo' }" @click="tab = 'foo'">Foo</button>
    <button :class="{ 'active': tab === 'bar' }" @click="tab = 'bar'">Bar</button>

    <div x-show="tab === 'foo'">Tab Foo</div>
    <div x-show="tab === 'bar'">Tab Bar</div>
</div>

You can even use it for non-trivial things: Pre-fetching a dropdown's HTML content on hover.

<div x-data="{ open: false }">
    <button
        @mouseenter.once="
            fetch('/dropdown-partial.html')
                .then(response => response.text())
                .then(html => { $refs.dropdown.innerHTML = html })
        "
        @click="open = true"
    >Show Dropdown</button>

    <div x-ref="dropdown" x-show="open" @click.away="open = false">
        Loading Spinner...
    </div>
</div>

Learn

There are 14 directives available to you:

Directive Description
x-data Declares a new component scope.
x-init Runs an expression when a component is initialized.
x-show Toggles display: none; on the element depending on expression (true or false).
x-bind Sets the value of an attribute to the result of a JS expression.
x-on Attaches an event listener to the element. Executes JS expression when emitted.
x-model Adds "two-way data binding" to an element. Keeps input element in sync with component data.
x-text Works similarly to x-bind, but will update the innerText of an element.
x-html Works similarly to x-bind, but will update the innerHTML of an element.
x-ref Convenient way to retrieve raw DOM elements out of your component.
x-if Remove an element completely from the DOM. Needs to be used on a <template> tag.
x-for Create new DOM nodes for each item in an array. Needs to be used on a <template> tag.
x-transition Directives for applying classes to various stages of an element's transition.
x-spread Allows you to bind an object of Alpine directives to an element for better reusability.
x-cloak This attribute is removed when Alpine initializes. Useful for hiding pre-initialized DOM.

And 6 magic properties:

Magic Properties Description
$el Retrieve the root component DOM node.
$refs Retrieve DOM elements marked with x-ref inside the component.
$event Retrieve the native browser "Event" object within an event listener.
$dispatch Create a CustomEvent and dispatch it using .dispatchEvent() internally.
$nextTick Execute a given expression AFTER Alpine has made its reactive DOM updates.
$watch Will fire a provided callback when a component property you "watched" gets changed.

Sponsors

Tailwind CSS

Want your logo here? DM on Twitter

Community Projects

Directives


x-data

Example: <div x-data="{ foo: 'bar' }">...</div>

Structure: <div x-data="[object literal]">...</div>

x-data declares a new component scope. It tells the framework to initialize a new component with the following data object.

Think of it like the data property of a Vue component.

Extract Component Logic

You can extract data (and behavior) into reusable functions:

<div x-data="dropdown()">
    <button x-on:click="open">Open</button>

    <div x-show="isOpen()" x-on:click.away="close">
        // Dropdown
    </div>
</div>

<script>
    function dropdown() {
        return {
            show: false,
            open() { this.show = true },
            close() { this.show = false },
            isOpen() { return this.show === true },
        }
    }
</script>

For bundler users, note that Alpine.js accesses functions that are in the global scope (window), you'll need to explicitly assign your functions to window in order to use them with x-data for example window.dropdown = function () {} (this is because with Webpack, Rollup, Parcel etc. function's you define will default to the module's scope not window).

You can also mix-in multiple data objects using object destructuring:

<div x-data="{...dropdown(), ...tabs()}">

x-init

Example: <div x-data="{ foo: 'bar' }" x-init="foo = 'baz'"></div>

Structure: <div x-data="..." x-init="[expression]"></div>

x-init runs an expression when a component is initialized.

If you wish to run code AFTER Alpine has made its initial updates to the DOM (something like a mounted() hook in VueJS), you can return a callback from x-init, and it will be run after:

x-init="() => { // we have access to the post-dom-initialization state here // }"


x-show

Example: <div x-show="open"></div>

Structure: <div x-show="[expression]"></div>

x-show toggles the display: none; style on the element depending if the expression resolves to true or false.

x-show.transition

x-show.transition is a convenience API for making your x-shows more pleasant using CSS transitions.

<div x-show.transition="open">
    These contents will be transitioned in and out.
</div>
Directive Description
x-show.transition A simultaneous fade and scale. (opacity, scale: 0.95, timing-function: cubic-bezier(0.4, 0.0, 0.2, 1), duration-in: 150ms, duration-out: 75ms)
x-show.transition.in Only transition in.
x-show.transition.out Only transition out.
x-show.transition.opacity Only use the fade.
x-show.transition.scale Only use the scale.
x-show.transition.scale.75 Customize the CSS scale transform transform: scale(.75).
x-show.transition.duration.200ms Sets the "in" transition to 200ms. The out will be set to half that (100ms).
x-show.transition.origin.top.right Customize the CSS transform origin transform-origin: top right.
x-show.transition.in.duration.200ms.out.duration.50ms Different durations for "in" and "out".

Note: All of these transition modifiers can be used in conjunction with each other. This is possible (although ridiculous lol): x-show.transition.in.duration.100ms.origin.top.right.opacity.scale.85.out.duration.200ms.origin.bottom.left.opacity.scale.95

Note: x-show will wait for any children to finish transitioning out. If you want to bypass this behavior, add the .immediate modifier:

<div x-show.immediate="open">
    <div x-show.transition="open">
</div>

x-bind

Note: You are free to use the shorter ":" syntax: :type="...".

Example: <input x-bind:type="inputType">

Structure: <input x-bind:[attribute]="[expression]">

x-bind sets the value of an attribute to the result of a JavaScript expression. The expression has access to all the keys of the component's data object, and will update every-time its data is updated.

Note: attribute bindings ONLY update when their dependencies update. The framework is smart enough to observe data changes and detect which bindings care about them.

x-bind for class attributes

x-bind behaves a little differently when binding to the class attribute.

For classes, you pass in an object whose keys are class names, and values are boolean expressions to determine if those class names are applied or not.

For example: <div x-bind:class="{ 'hidden': foo }"></div>

In this example, the "hidden" class will only be applied when the value of the foo data attribute is true.

x-bind for boolean attributes

x-bind supports boolean attributes in the same way as value attributes, using a variable as the condition or any JavaScript expression that resolves to true or false.

For example:

<!-- Given: -->
<button x-bind:disabled="myVar">Click me</button>

<!-- When myVar == true: -->
<button disabled="disabled">Click me</button>

<!-- When myVar == false: -->
<button>Click me</button>

This will add or remove the disabled attribute when myVar is true or false respectively.

Boolean attributes are supported as per the HTML specification, for example disabled, readonly, required, checked, hidden, selected, open, etc.

Note: If you need a false state to show for your attribute, such as aria-*, chain .toString() to the value while binding to the attribute. For example: :aria-expanded="isOpen.toString()" would persist whether isOpen was true or false.

.camel modifier Example: <svg x-bind:view-box.camel="viewBox">

The camel modifier will bind to the camel case equivalent of the attribute name. In the example above, the value of viewBox will be bound the viewBox attribute as opposed to the view-box attribute.


x-on

Note: You are free to use the shorter "@" syntax: @click="...".

Example: <button x-on:click="foo = 'bar'"></button>

Structure: <button x-on:[event]="[expression]"></button>

x-on attaches an event listener to the element it's declared on. When that event is emitted, the JavaScript expression set as its value is executed. You can use x-on with any event available for the element you're adding the directive on, for a full list of events, see the Event reference on MDN for a list of possible values.

If any data is modified in the expression, other element attributes "bound" to this data, will be updated.

Note: You can also specify a JavaScript function name.

Example: <button x-on:click="myFunction"></button>

This is equivalent to: <button x-on:click="myFunction($event)"></button>

keydown modifiers

Example: <input type="text" x-on:keydown.escape="open = false">

You can specify specific keys to listen for using keydown modifiers appended to the x-on:keydown directive. Note that the modifiers are kebab-cased versions of Event.key values.

Examples: enter, escape, arrow-up, arrow-down

Note: You can also listen for system-modifier key combinations like: x-on:keydown.cmd.enter="foo"

.away modifier

Example: <div x-on:click.away="showModal = false"></div>

When the .away modifier is present, the event handler will only be executed when the event originates from a source other than itself, or its children.

This is useful for hiding dropdowns and modals when a user clicks away from them.

.prevent modifier Example: <input type="checkbox" x-on:click.prevent>

Adding .prevent to an event listener will call preventDefault on the triggered event. In the above example, this means the checkbox wouldn't actually get checked when a user clicks on it.

.stop modifier Example: <div x-on:click="foo = 'bar'"><button x-on:click.stop></button></div>

Adding .stop to an event listener will call stopPropagation on the triggered event. In the above example, this means the "click" event won't bubble from the button to the outer <div>. Or in other words, when a user clicks the button, foo won't be set to 'bar'.

.self modifier Example: <div x-on:click.self="foo = 'bar'"><button></button></div>

Adding .self to an event listener will only trigger the handler if the $event.target is the element itself. In the above example, this means the "click" event that bubbles from the button to the outer <div> will not run the handler.

.window modifier Example: <div x-on:resize.window="isOpen = window.outerWidth > 768 ? false : open"></div>

Adding .window to an event listener will install the listener on the global window object instead of the DOM node on which it is declared. This is useful for when you want to modify component state when something changes with the window, like the resize event. In this example, when the window grows larger than 768 pixels wide, we will close the modal/dropdown, otherwise maintain the same state.

Note: You can also use the .document modifier to attach listeners to document instead of window

.once modifier Example: <button x-on:mouseenter.once="fetchSomething()"></button>

Adding the .once modifier to an event listener will ensure that the listener will only be handled once. This is useful for things you only want to do once, like fetching HTML partials and such.

.passive modifier Example: <button x-on:mousedown.passive="interactive = true"></button>

Adding the .passive modifier to an event listener will make the listener a passive one, which means preventDefault() will not work on any events being processed, this can help, for example with scroll performance on touch devices.

.debounce modifier Example: <input x-on:input.debounce="fetchSomething()">

The debounce modifier allows you to "debounce" an event handler. In other words, the event handler will NOT run until a certain amount of time has elapsed since the last event that fired. When the handler is ready to be called, the last handler call will execute.

The default debounce "wait" time is 250 milliseconds.

If you wish to customize this, you can specify a custom wait time like so:

<input x-on:input.debounce.750="fetchSomething()">
<input x-on:input.debounce.750ms="fetchSomething()">

.camel modifier Example: <input x-on:event-name.camel="doSomething()">

The camel modifier will attach an event listener for the camel case equivalent event name. In the example above, the expression will be evaluated when the eventName event is fired on the element.


x-model

Example: <input type="text" x-model="foo">

Structure: <input type="text" x-model="[data item]">

x-model adds "two-way data binding" to an element. In other words, the value of the input element will be kept in sync with the value of the data item of the component.

Note: x-model is smart enough to detect changes on text inputs, checkboxes, radio buttons, textareas, selects, and multiple selects. It should behave how Vue would in those scenarios.

.number modifier Example: <input x-model.number="age">

The number modifier will convert the input's value to a number. If the value cannot be parsed as a valid number, the original value is returned.

.debounce modifier Example: <input x-model.debounce="search">

The debounce modifier allows you to add a "debounce" to a value update. In other words, the event handler will NOT run until a certain amount of time has elapsed since the last event that fired. When the handler is ready to be called, the last handler call will execute.

The default debounce "wait" time is 250 milliseconds.

If you wish to customize this, you can specifiy a custom wait time like so:

<input x-model.debounce.750="search">
<input x-model.debounce.750ms="search">

x-text

Example: <span x-text="foo"></span>

Structure: <span x-text="[expression]"

x-text works similarly to x-bind, except instead of updating the value of an attribute, it will update the innerText of an element.


x-html

Example: <span x-html="foo"></span>

Structure: <span x-html="[expression]"

x-html works similarly to x-bind, except instead of updating the value of an attribute, it will update the innerHTML of an element.

⚠️ Only use on trusted content and never on user-provided content. ⚠️

Dynamically rendering HTML from third parties can easily lead to XSS vulnerabilities.


x-ref

Example: <div x-ref="foo"></div><button x-on:click="$refs.foo.innerText = 'bar'"></button>

Structure: <div x-ref="[ref name]"></div><button x-on:click="$refs.[ref name].innerText = 'bar'"></button>

x-ref provides a convenient way to retrieve raw DOM elements out of your component. By setting an x-ref attribute on an element, you are making it available to all event handlers inside an object called $refs.

This is a helpful alternative to setting ids and using document.querySelector all over the place.

Note: you can also bind dynamic values for x-ref: <span :x-ref="item.id"></span> if you need to.


x-if

Example: <template x-if="true"><div>Some Element</div></template>

Structure: <template x-if="[expression]"><div>Some Element</div></template>

For cases where x-show isn't sufficient (x-show sets an element to display: none if it's false), x-if can be used to actually remove an element completely from the DOM.

It's important that x-if is used on a <template></template> tag because Alpine doesn't use a virtual DOM. This implementation allows Alpine to stay rugged and use the real DOM to work its magic.

Note: x-if must have a single root element inside the <template></template> tag.

Note: When using template in a svg tag, you need to add a polyfill that should be run before Alpine.js is initialized.


x-for

Example:

<template x-for="item in items" :key="item">
    <div x-text="item"></div>
</template>

Note: the :key binding is optional, but HIGHLY recommended.

x-for is available for cases when you want to create new DOM nodes for each item in an array. This should appear similar to v-for in Vue, with one exception of needing to exist on a template tag, and not a regular DOM element.

If you want to access the current index of the iteration, use the following syntax:

<template x-for="(item, index) in items" :key="index">
    <!-- You can also reference "index" inside the iteration if you need. -->
    <div x-text="index"></div>
</template>

If you want to access the array object (collection) of the iteration, use the following syntax:

<template x-for="(item, index, collection) in items" :key="index">
    <div>
        <!-- You can also reference "collection" inside the iteration if you need. -->
        <!-- Current item. -->
        <div x-text="item"></div>
        <!-- Same as above. -->
        <div x-text="collection[index]"></div>
        <!-- Previous item. -->
        <div x-text="collection[index - 1]"></div>
    </div>
</template>

Note: x-for must have a single root element inside of the <template></template> tag.

Note: When using template in a svg tag, you need to add a polyfill that should be run before Alpine.js is initialized.

Nesting x-fors

You can nest x-for loops, but you MUST wrap each loop in an element. For example:

<template x-for="item in items">
    <div>
        <template x-for="subItem in item.subItems">
            <div x-text="subItem"></div>
        </template>
    </div>
</template>

Iterating over a range

Alpine supports the i in n syntax, where n is an integer, allowing you to iterate over a fixed range of elements.

<template x-for="i in 10">
    <span x-text="i"></span>
</template>

x-transition

Example:

<div
    x-show="open"
    x-transition:enter="transition ease-out duration-300"
    x-transition:enter-start="opacity-0 transform scale-90"
    x-transition:enter-end="opacity-100 transform scale-100"
    x-transition:leave="transition ease-in duration-300"
    x-transition:leave-start="opacity-100 transform scale-100"
    x-transition:leave-end="opacity-0 transform scale-90"
>...</div>
<template x-if="open">
    <div
        x-transition:enter="transition ease-out duration-300"
        x-transition:enter-start="opacity-0 transform scale-90"
        x-transition:enter-end="opacity-100 transform scale-100"
        x-transition:leave="transition ease-in duration-300"
        x-transition:leave-start="opacity-100 transform scale-100"
        x-transition:leave-end="opacity-0 transform scale-90"
    >...</div>
</template>

The example above uses classes from Tailwind CSS.

Alpine offers 6 different transition directives for applying classes to various stages of an element's transition between "hidden" and "shown" states. These directives work both with x-show AND x-if.

These behave exactly like VueJS's transition directives, except they have different, more sensible names:

Directive Description
:enter Applied during the entire entering phase.
:enter-start Added before element is inserted, removed one frame after element is inserted.
:enter-end Added one frame after element is inserted (at the same time enter-start is removed), removed when transition/animation finishes.
:leave Applied during the entire leaving phase.
:leave-start Added immediately when a leaving transition is triggered, removed after one frame.
:leave-end Added one frame after a leaving transition is triggered (at the same time leave-start is removed), removed when the transition/animation finishes.

x-spread

Example:

<div x-data="dropdown()">
    <button x-spread="trigger">Open Dropdown</button>

    <span x-spread="dialogue">Dropdown Contents</span>
</div>

<script>
    function dropdown() {
        return {
            open: false,
            trigger: {
                ['@click']() {
                    this.open = true
                },
            },
            dialogue: {
                ['x-show']() {
                    return this.open
                },
                ['@click.away']() {
                    this.open = false
                },
            }
        }
    }
</script>

x-spread allows you to extract an element's Alpine bindings into a reusable object.

The object keys are the directives (Can be any directive including modifiers), and the values are callbacks to be evaluated by Alpine.

Note: There are a couple of caveats to x-spread:

  • When the directive being "spread" is x-for, you should return a normal expression string from the callback. For example: ['x-for']() { return 'item in items' }.
  • x-data and x-init can't be used inside a "spread" object.

x-cloak

Example: <div x-data="{}" x-cloak></div>

x-cloak attributes are removed from elements when Alpine initializes. This is useful for hiding pre-initialized DOM. It's typical to add the following global style for this to work:

<style>
    [x-cloak] {
        display: none !important;
    }
</style>

Magic Properties

With the exception of $el, magic properties are not available within x-data as the component isn't initialized yet.


$el

Example:

<div x-data>
    <button @click="$el.innerHTML = 'foo'">Replace me with "foo"</button>
</div>

$el is a magic property that can be used to retrieve the root component DOM node.

$refs

Example:

<span x-ref="foo"></span>

<button x-on:click="$refs.foo.innerText = 'bar'"></button>

$refs is a magic property that can be used to retrieve DOM elements marked with x-ref inside the component. This is useful when you need to manually manipulate DOM elements.


$event

Example:

<input x-on:input="alert($event.target.value)">

$event is a magic property that can be used within an event listener to retrieve the native browser "Event" object.

Note: The $event property is only available in DOM expressions.

If you need to access $event inside of a JavaScript function you can pass it in directly:

<button x-on:click="myFunction($event)"></button>


$dispatch

Example:

<div @custom-event="console.log($event.detail.foo)">
    <button @click="$dispatch('custom-event', { foo: 'bar' })">
    <!-- When clicked, will console.log "bar" -->
</div>

Note on Event Propagation

Notice that, because of event bubbling, when you need to capture events dispatched from nodes that are under the same nesting hierarchy, you'll need to use the .window modifier:

Example:

<div x-data>
    <span @custom-event="console.log($event.detail.foo)"></span>
    <button @click="$dispatch('custom-event', { foo: 'bar' })">
<div>

This won't work because when custom-event is dispatched, it'll propagate to its common ancestor, the div.

Dispatching to Components

You can also take advantage of the previous technique to make your components talk to each other:

Example:

<div x-data @custom-event.window="console.log($event.detail)"></div>

<button x-data @click="$dispatch('custom-event', 'Hello World!')">
<!-- When clicked, will console.log "Hello World!". -->

$dispatch is a shortcut for creating a CustomEvent and dispatching it using .dispatchEvent() internally. There are lots of good use cases for passing data around and between components using custom events. Read here for more information on the underlying CustomEvent system in browsers.

You will notice that any data passed as the second parameter to $dispatch('some-event', { some: 'data' }), becomes available through the new events "detail" property: $event.detail.some. Attaching custom event data to the .detail property is standard practice for CustomEvents in browsers. Read here for more info.

You can also use $dispatch() to trigger data updates for x-model bindings. For example:

<div x-data="{ foo: 'bar' }">
    <span x-model="foo">
        <button @click="$dispatch('input', 'baz')">
        <!-- After the button is clicked, `x-model` will catch the bubbling "input" event, and update foo to "baz". -->
    </span>
</div>

Note: The $dispatch property is only available in DOM expressions.

If you need to access $dispatch inside of a JavaScript function you can pass it in directly:

<button x-on:click="myFunction($dispatch)"></button>


$nextTick

Example:

<div x-data="{ fruit: 'apple' }">
    <button
        x-on:click="
            fruit = 'pear';
            $nextTick(() => { console.log($event.target.innerText) });
        "
        x-text="fruit"
    ></button>
</div>

$nextTick is a magic property that allows you to only execute a given expression AFTER Alpine has made its reactive DOM updates. This is useful for times you want to interact with the DOM state AFTER it's reflected any data updates you've made.


$watch

Example:

<div x-data="{ open: false }" x-init="$watch('open', value => console.log(value))">
    <button @click="open = ! open">Toggle Open</button>
</div>

You can "watch" a component property with the $watch magic method. In the above example, when the button is clicked and open is changed, the provided callback will fire and console.log the new value.

Security

If you find a security vulnerability, please send an email to [email protected].

Alpine relies on a custom implementation using the Function object to evaluate its directives. Despite being more secure then eval(), its use is prohibited in some environments, such as Google Chrome App, using restrictive Content Security Policy (CSP).

If you use Alpine in a website dealing with sensitive data and requiring CSP, you need to include unsafe-eval in your policy. A robust policy correctly configured will help protecting your users when using personal or financial data.

Since a policy applies to all scripts in your page, it's important that other external libraries included in the website are carefully reviewed to ensure that they are trustworthy and they won't introduce any Cross Site Scripting vulnerability either using the eval() function or manipulating the DOM to inject malicious code in your page.

V3 Roadmap

  • Move from x-ref to ref for Vue parity?
  • Add Alpine.directive()
  • Add Alpine.component('foo', {...}) (With magic __init() method)
  • Dispatch Alpine events for "loaded", "transition-start", etc... (#299) ?
  • Remove "object" (and array) syntax from x-bind:class="{ 'foo': true }" (#236 to add support for object syntax for the style attribute)
  • Improve x-for mutation reactivity (#165)
  • Add "deep watching" support in V3 (#294)
  • Add $el shortcut
  • Change @click.away to @click.outside?

License

Copyright © 2019-2021 Caleb Porzio and contributors

Licensed under the MIT license, see LICENSE.md for details.

Comments
  • Nested components cannot access external data

    Nested components cannot access external data

    Hi @calebporzio, thanks for the amazing work so far. I was having a go with alpinejs and I noticed that, when there are nested components, the internal component cannot access the scope of the external one.

    For example,

    <html>
      <head>
        <script src="https://cdn.jsdelivr.net/gh/alpinejs/[email protected]/dist/alpine.js" defer></script>
      </head>
      <body>
        <div x-data="{ foo: 'bar', foo2: 'BAR' }">
          <span x-text="foo"></span>
          <div x-data="{ foo: 'bob' }">
            <span id="s1" x-text="foo"></span>
            <span id="s2" x-text="foo2"></span>
          </div>
        </div>
      </body>
    </html>
    

    I would expect span#s2 to display 'BAR' or, in alternative, i would expect to be able to reference foo2 in the internal data structure.

    I'm happy to work on a PR for this but I just wanted to check with you first in case this behaviour is expected and you do not want components to access external scopes.

    Thanks, Simone

    opened by SimoTod 63
  • Improve Security Awareness

    Improve Security Awareness

    for (const el of document.querySelectorAll('*')) {
      const attributes = [...el.attributes].filter(attr => '@click' === attr.name);
      if (attributes.length)
        attributes[0].value = 'alert("seriously? in 2020?")';
    }
    

    You can test it in codepen, and the attack regards any script that might be injected in a page that enabled evaluation for alpine.

    I think the architecture behind alpine relies entirely on evaluation and deprecated ECMAScript features, and I am a libraries author myself, but I think it's about the time security becomes a major Web concern.

    I am not sure how to fix this, as with(...) statement and evaluation of only the right thing would blow the size of this library, as it would require a proper AST crawling per each attribute content, but I hope this issue would be at least documented properly on top of the README page, as it's way too easy to inject arbitrary code when this library is trusted, and around.

    Thanks for making the Web a better place.

    opened by WebReflection 57
  • Stopped working on IE11

    Stopped working on IE11

    Hello,

    It seems that after the transition to rollup and newer features, alpinejs is not working in IE11 with the current polyfills.

    I am getting the error:

    Expected ')'
    

    at this line in utils.js

    export function walkSkippingNestedComponents(el, callback, isRoot = true) {
    

    I then tested on a branch after the transition to rollup (1.1.3) and got it to work by changing babel.config.js to the following

    module.exports = {
      presets: [
        [
          "@babel/preset-env",
          {
            targets: {
              browsers: "> 0.5%, ie >= 11"
            },
            modules: false,
            spec: true,
            useBuiltIns: "usage",
            forceAllTransforms: true,
            corejs: {
              version: 3,
              proposals: false
            }
          }
        ]
      ]
    };
    

    this increases the build size, but gets it to work for testing purposes.

    I then tested the same configuration on the current build (1.7) and I now get the following error

    Uncaught (in promise) TypeError: Cannot create property for a non-extensible object
    

    Seems like an error with the proxy polyfill when researching that error.

    <script src="https://cdn.jsdelivr.net/npm/[email protected]/proxy.min.js"></script>
    

    I also tested by adding Laravel Mix back and the same object error still occurs.

    Wondering if anyone else has gotten a recent build to work with IE11?

    Thanks.

    opened by keyurshah 37
  • x-bind:class is removing classes

    x-bind:class is removing classes

    See https://codepen.io/bepsays/pen/xxwwzVP

    Ref. the documentation

    For classes, you pass in an object who's keys are class names, and values are boolean expressions to determine if those class names are applied or not.

    So, given this:

    <div x-data="{ open: true, level: 32 }">
      <style>
        .red {
          background-color: red;
        }
        .blue {
          background-color: blue;
        }
        
        .text-yellow {
          color: yellow;
        }
        
        .text-blue {
          color: green;
        }
        
        
      </style>
      <button x-bind:class="{ 
                              'red text-yellow' : open,
                              'red text-blue' : !open && level === 12,
                            }">Button</button>
     
    </div>
    

    I would expect a red button with yellow text. But the red class is removed by the second statement.

    I now notice that if I switch the conditions above, I get the colors I want, but that is for this very simple case.

    Is there a reason why it's implemented like this? Is this how Vue does it?

    bug 
    opened by bep 36
  • Regression in handleAttributeBindingDirective: el.setSelectionRange is applied naively

    Regression in handleAttributeBindingDirective: el.setSelectionRange is applied naively

    Please bear with me as this is the first issue I've ever opened...

    Unfortunately the fix from PR #367 has introduced a bug when binding a value to a subset of input types.

    According to the MDN page on setSelectionRange():

    Note that accordingly to the WHATWG forms spec selectionStart, selectionEnd properties and setSelectionRange method apply only to inputs of types text, search, URL, tel and password. Chrome, starting from version 33, throws an exception while accessing those properties and method on the rest of input types. For example, on input of type number: "Failed to read the 'selectionStart' property from 'HTMLInputElement': The input element's type ('number') does not support selection".

    I was playing with some code on Firefox, updated to v2.3.0, and then started getting their version of the above Chrome error:

    InvalidStateError: An attempt was made to use an object that is not, or is no longer, usable

    which is just ridiculously cryptic and took me a while to realize the problem!

    https://github.com/alpinejs/alpine/blob/49de05c3278556cac5477a4ca668e9daf280d703/src/directives/bind.js#L45-L51

    At the very least L49 should check that the type is not any of those types before attempting to set the selection.

    Something like

    (el === document.activeElement && ['text', 'search', 'url', 'password'].includes(el.type)) 
    

    should fix it, but I don't know if the developers would prefer to hoist this type check to the parent if/else block and only declare const cursorPosition for those types, and not also for everything else. Or at least move that const declaration inside the L49 if to optimize memory a bit.

    Thoughts?

    bug 
    opened by 03juan 35
  • Usage of x-transition with responsive elements

    Usage of x-transition with responsive elements

    x-transition only works with x-show and x-if.

    However, in many cases, these tools can't be used. For example, with an element that you only want to show on small screens.

    In these cases, you wil probably use:

    <div :class="{'block': open, 'hidden': !open}" class="hidden sm:hidden">
    

    How could one still get transition support to these cases?

    opened by georgeboot 35
  • Using ES6 classes to initialize a component scope (x-data)

    Using ES6 classes to initialize a component scope (x-data)

    Hello, everyone! I couldn't find any documentation on this specific use case and was wondering if there are any caveats to this approach. Testing a simple case on Codepen seems to work fine.

    <div x-data="App.Greeter">
      <span x-text="`Hello ${name}`"></span>
      <br />
      <input type="text" x-model="name">  
    </div>
    
    class Greeter { 
      constructor() {
        this.name = "James"
      }
    }
    
    window.App = { 
        Greeter: new Greeter() 
    }
    
    opened by thiagomajesk 31
  • Sharing state

    Sharing state

    If I have some javascript like so (note the shared state object):

    const mySharedState = {
        show: false
    };
    
    const mainMenu = function(){
        return {
            state: mySharedState,
            isOpen() { return this.state.show === true },
            toggle() {
                this.state.show = !this.state.show;
                console.log(this.state.show);
            }
        }
    };
    
    

    Can someone help explain why something like this works:

    <div x-data="mainMenu()">
      <button @click="toggle()" type="button">click me</button>
    
      <div x-show.transition="isOpen()">
        x test
      </div>
    </div>
    

    but this doesn't

    <button x-data="mainMenu()" @click="toggle()" type="button">click me</button>
    
    <!-- Somewhere else on the page -->
    <div x-data="mainMenu()" x-show.transition="isOpen()">
      x test
    </div>
    

    Here is a working example: https://codepen.io/7ammer/pen/MWwOZVp

    I've noticed that 'mySharedState' is shared but the second example is not reactive when the value changes.

    opened by 7ammer 29
  • feature: improved logging

    feature: improved logging

    ~~Debug mode improves console errors and catches syntax errors that alpine does not report in a meaningful way. When debug mode is enabled console errors will include the element where the error was thrown.~~

    ~~This is an expensive operation because Syntax Errors can only be detected through events on the window, additionally logging elements to the console is expensive. Because of this, by default debug mode is disabled similar to React __DEV__ not being enabled for prod builds.~~

    ~~With this feature a variety of common development issues can be resolved faster using browsers development tools to locate broken elements and diagnose issues previously hard decipher from Alpine's original error logs.~~

    ~~Can be enabled by calling the Alpine.enableDebugMode() before Alpine.start() is called.~~

    Primarily this will help with debugging the following types of issues:

    • Invalid identifiers and expressions in directives
    • Syntax Errors caused by malformed expressions in x-data

    ~~Here is a codepen example of the feature: https://codepen.io/jacobpozaic/pen/XWMLEmp~~ Here is a codepen example of the feature ( new iteration ) : https://codepen.io/jacobpozaic/pen/eYvqwbg More examples in tests

    Update:

    • Improved error messages in console
    • Fix issue logging errors present in previous implementation when x-data is initialized with reference/syntax error, evaluated data will use empty object instead of undefined so nested errors can be found and logged, and to prevent bogus errors
    • Trying to maintain compatible with devtools
    opened by blackmagiccoffee 28
  • Alpine.clone results in click handler being called twice

    Alpine.clone results in click handler being called twice

    Reported in https://github.com/phoenixframework/phoenix_live_view/issues/1140 with an example repo.

    Short summary: I have a list view that gets three items added after 1 second. Each list item has a @click handler. The weird thing is: it seems that Alpine.clone results in the click handler of the list item being called twice. If I remove that line in app.js, the click handlers still work and they are just called once. Also putting x-data on the list item affects this bug, with it, it never gets called twice.

    I tried to isolate the issue with a JavaScript only example, but I was not able to (because in that example I'm not calling / don't need to call Alpine.clone). I'm not sure what a "proper" javascript-only example would be that tests whether Alpine.clone is doing the right thing. Any suggestions?

    update: the problem is not specific to Phoenix, the problem also exists with Livewire: https://github.com/dvic/hello-livewire (click on the button on the home page, then on the list item, console.log will be called twice)

    opened by dvic 28
  • Remove attribute when set to null/undefined/false

    Remove attribute when set to null/undefined/false

    Closes #485 #280

    Todo:

    • [x] test for #485
    • [x] test for non-standard attribute case? https://github.com/alpinejs/alpine/issues/280#issue-582472337
    • [x] docs update

    Feedback requests:

    1. consensus to undo this, change should only apply to non bool attrs I've slightly altered the behaviour of boolean attributes set to non-falsy values, they used to be set to '' now they'll be set to whatever value is, which shouldn't make too much of a difference (eg. none of the tests are failing) cc @SimoTod @ryangjchandler
    2. consensus to remove on null/undefined/false Should we only remove on "null" or also "undefined" and "false" (per the way Vue.js works)

    value of nullundefined, or false, the disabled attribute will not even be included in the rendered <button> element

    If we go with Vue's approach we could ditch the whole "Boolean HTML attribute" detection

    opened by HugoDF 28
  • docs: Fix a few typos

    docs: Fix a few typos

    There are small typos in:

    • packages/docs/src/en/advanced/extending.md
    • tests/cypress/integration/plugins/navigate.spec.js

    Fixes:

    • Should read specifying rather than specifing.
    • Should read piece rather than peice.
    • Should read persistent rather than persistant.

    Semi-automated pull request generated by https://github.com/timgates42/meticulous/blob/master/docs/NOTE.md

    opened by timgates42 0
  • Fix x-model checkbox weirdness

    Fix x-model checkbox weirdness

    Seems like x-model should also use the skipForClone function. Normally extra listeners wouldn't cause a problem, but if you're binding to an array (for example, with multiple checkboxes) it causes incorrect values to be bound. Each value is doubled after a clone happens.

    This was discovered while investigating https://github.com/livewire/livewire/pull/5454

    I've included a test for this that fails prior to skipForClone being added.

    opened by austenc 0
  • Accept x-data=

    Accept x-data="true" as a synonym for x-data="{}"

    As per #3337, some frameworks (like htm) convert <div x-data … /> to <div x-data="true" … /> whereas Alpine.js currently only accepts the equivalent of <div x-data="" … />. This patch (based on https://github.com/alpinejs/alpine/discussions/3337#discussioncomment-4397836), also makes it accept x-data="true" to mean data is {}.

    opened by aral 5
  • Bug - Custom prefix not supported in same use cases

    Bug - Custom prefix not supported in same use cases

    Alpine provides a way to define a custom prefix rather than using x- but the internal code itself hardcodes the x- version in a lot of places so people using custom prefixes are going to get unexpected errors. Also, the prefix for x-on and x-bind is called too early so the short versions are never working with a custom prefix. Sorry for the huge PR but there were a lot of files to change.

    opened by SimoTod 0
Releases(v3.10.5)
Owner
Alpine.js
A rugged, minimal framework for composing JavaScript behavior in your markup.
Alpine.js
A JavaScript implementation of SOM, a minimal Smalltalk for teaching and research.

ohm-som A JavaScript implementation of SOM, a minimal Smalltalk for teaching and research. Just a hobby, won't be big and professional like TruffleSOM

Patrick Dubroy 16 Jun 25, 2021
Super minimal MVC library

Espresso.js Espresso.js is a tiny MVC library inspired by Backbone and React with a focus on simplicity and speed. We've aimed to bring the ideas of u

TechLayer 534 Dec 11, 2022
HTML Framework that allows you not to write JavaScript code.

EHTML (or Extended HTML) can be described as a set of custom elements that you can put on HTML page for different purposes and use cases. The main ide

Guseyn Ismayylov 171 Dec 29, 2022
Ember.js - A JavaScript framework for creating ambitious web applications

Ember.js is a JavaScript framework that greatly reduces the time, effort and resources needed to build any web application. It is focused on making yo

Ember.js 22.4k Jan 8, 2023
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

Supporting Vue.js Vue.js is an MIT-licensed open source project with its ongoing development made possible entirely by the support of these awesome ba

vuejs 201.7k Jan 8, 2023
A JavaScript Framework for Building Brilliant Applications

mithril.js What is Mithril? Installation Documentation Getting Help Contributing What is Mithril? A modern client-side JavaScript framework for buildi

null 13.5k Dec 26, 2022
A framework for real-time applications and REST APIs with JavaScript and TypeScript

A framework for real-time applications and REST APIs with JavaScript and TypeScript Feathers is a lightweight web-framework for creating real-time app

Feathers 14.2k Dec 28, 2022
The most popular HTML, CSS, and JavaScript framework for developing responsive, mobile first projects on the web.

Bootstrap Sleek, intuitive, and powerful front-end framework for faster and easier web development. Explore Bootstrap docs » Report bug · Request feat

Bootstrap 161.1k Jan 4, 2023
A no-dependency, intuitive web framework from scratch in Javascript

Poseidon ?? Intro Poseidon is, to use a nice description by Reef, an anti-framework. It's a a no-dependency, component-based Javascript framework for

Amir Bolous 45 Nov 14, 2022
hell.js 🚀 🚀 A JavaScript framework for the 🔥 next 🔥 generation. 🚀

hell.js ?? ?? A JavaScript framework for the ?? next ?? generation. ??

null 31 Oct 3, 2022
The worlds smallest fully-responsive css framework

FLUIDITY A fully responsive css framework that is impossibly small HTML is almost 100% responsive out of the box. This stylesheet patches the remainin

murmurs 1.1k Sep 24, 2022
An HTML5/CSS3 framework used at SAPO for fast and efficient website design and prototyping

Welcome to Ink Ink is an interface kit for quick development of web interfaces, simple to use and expand on. It uses a combination of HTML, CSS and Ja

SAPO 1.9k Dec 15, 2022
One framework. Mobile & desktop.

Angular - One framework. Mobile & desktop. Angular is a development platform for building mobile and desktop web applications using Typescript/JavaScr

Angular 85.7k Jan 4, 2023
The tiny framework for building hypertext applications.

Hyperapp The tiny framework for building hypertext applications. Do more with less—We have minimized the concepts you need to learn to get stuff done.

Jorge Bucaran 18.9k Jan 4, 2023
A framework for building native apps with React.

React Native Learn once, write anywhere: Build mobile apps with React. Getting Started · Learn the Basics · Showcase · Contribute · Community · Suppor

Facebook 106.8k Jan 3, 2023
The Backbone Framework

Marionette.js The Backbone Framework Marionette v5 Marionette is dropping its dependency on Backbone. That library is available here: https://github.c

Marionette.js 7.1k Jan 5, 2023
MVC framework making it easy to write realtime, collaborative applications that run in both Node.js and browsers

Derby The Derby MVC framework makes it easy to write realtime, collaborative applications that run in both Node.js and browsers. Derby includes a powe

DerbyJS 4.7k Dec 31, 2022
Better MV-ish Framework

❗ I started working on this project in 2012. React didn't exist, Angular didn't have a stable 1.0 release, Internet Explorer 7, 8, 9 was used by 35% o

Antonio Stoilkov 2.8k Jan 1, 2023