Local state and directives
Bind text, attributes, and visibility
Use x-text, x-bind, x-show, and x-cloak according to whether content, attributes, or display should change.
Alpine has a different directive for each part of an element you might want to change. Text content, attributes, and visibility each get their own. Pick the one that matches what changes and the HTML stays readable.
Let’s build the issue row disclosure with all three.
x-text for text
x-text sets the text content of an element from an expression. We use it for the result count on the board:
<p>Showing <span x-text="count"></span> issues</p>
Alpine escapes the value, so a title like <script> shows up as literal text. There is also x-html, which inserts raw HTML. I don’t use it for anything the user typed. If you only need text, use x-text.
x-bind for attributes
x-bind:attribute sets an attribute from state. The short form is a colon. The disclosure button needs aria-expanded to follow the expanded value:
<li x-data="{ expanded: false }">
<button :aria-expanded="expanded" @click="expanded = !expanded">
Login button unresponsive on Safari
</button>
</li>
Click the button and the attribute flips between "false" and "true". Screen readers announce it. Open the Elements panel in your browser and watch it change.
You can bind any attribute this way. :class and :disabled are the ones I reach for most.
x-show for visibility
x-show toggles display: none on the element. The element stays in the DOM, it just hides:
<div x-show="expanded">
Reported by Marta, 3 days ago.
</div>
Because the element stays, its content and any form values inside it survive the toggle.
x-cloak for the first paint
There is a small window between the HTML arriving and Alpine starting. In that window, x-show="expanded" has not run yet, so the hidden panel flashes on screen.
x-cloak fixes this. Alpine removes the attribute as soon as it initializes the element. You pair it with one CSS rule:
[x-cloak] { display: none !important; }
<div x-show="expanded" x-cloak>
Reported by Marta, 3 days ago.
</div>
Now the panel is hidden before Alpine loads, and Alpine takes over from there.
Put them together
The full disclosure row:
<li x-data="{ expanded: false }">
<button :aria-expanded="expanded" @click="expanded = !expanded">
Login button unresponsive on Safari
</button>
<div x-show="expanded" x-cloak>
Reported by Marta, 3 days ago.
</div>
</li>
Text, attribute, visibility. Each one changes exactly one thing. Build this row yourself, reload the page with the network throttled, and check that the panel never flashes.
Lesson completed