Asok Logo Asok
esc

Type to search across all documentation

5 min read
Edit on GitHub

Asok Directives#

Asok includes Alpine.js-style reactive directives for building interactive UIs without JavaScript. These directives are automatically processed and inject a lightweight runtime (~5KB).

State Management#

asok-state — Component state#

Define reactive local state for a component:

<div asok-state="{ count: 0, name: 'Alice' }">
  <p>Count: {{ count }}</p>
  <p>Name: {{ name }}</p>
  <button asok-on:click="count = count + 1">Increment</button>
</div>

State is scoped to the component and its children. Changes trigger automatic re-renders.

$store — Global state#

Access shared state across all components:

<!-- Component 1 -->
<div asok-state="{}">
  <button asok-on:click="$store.theme = 'dark'">Dark Mode</button>
</div>

<!-- Component 2 (updates automatically) -->
<div asok-state="{}" asok-class="$store.theme === 'dark' ? 'bg-black text-white' : ''">
  Theme: {{ $store.theme }}
</div>

The store uses dependency tracking — only components that use a property are updated when it changes (10-20x faster than updating everything).

Access from JavaScript:

window.Asok.store.theme = 'dark';
window.Asok.store.user = { name: 'Alice', role: 'admin' };

Display & Visibility#

asok-show / asok-hide#

Toggle element visibility with display: none:

<div asok-state="{ visible: true }">
  <div asok-show="visible">I'm visible</div>
  <div asok-hide="visible">I'm hidden</div>
  <button asok-on:click="visible = !visible">Toggle</button>
</div>

asok-text#

Set text content reactively:

<div asok-state="{ count: 0 }">
  <p asok-text="'Count: ' + count"></p>
  <button asok-on:click="count++">+</button>
</div>

Class & Attribute Binding#

asok-class — Dynamic classes#

Three syntaxes for maximum flexibility:

<div asok-state="{ isOpen: false, status: 'success' }">
  <!-- 1. Toggle a single class -->
  <div asok-class:active="isOpen">Toggle</div>

  <!-- 2. Ternary (like Alpine.js) -->
  <div asok-class="isOpen ? 'text-blue-500 font-bold' : 'text-red-500'">
    Conditional classes
  </div>

  <!-- 3. Object (multiple toggles) -->
  <div asok-class="{ 'active': isOpen, 'disabled': !enabled, 'success': status === 'success' }">
    Multiple classes
  </div>
</div>

Perfect for Tailwind CSS with long class lists:

<div asok-class="isOpen ? 'bg-white px-4 py-2 border border-gray-300 rounded-md shadow-sm' : 'bg-gray-100'">
  ...
</div>

asok-bind:attr#

Bind any HTML attribute:

<div asok-state="{ url: '/page', disabled: false }">
  <a asok-bind:href="url">Link</a>
  <button asok-bind:disabled="disabled">Button</button>
  <input asok-bind:placeholder="'Enter ' + fieldName">
</div>

Forms & Input#

asok-model#

Two-way data binding for form inputs:

<div asok-state="{ name: '', email: '' }">
  <input asok-model="name" placeholder="Name">
  <input type="email" asok-model="email" placeholder="Email">

  <p>Hello {{ name }}! Your email is {{ email }}</p>
</div>

Works with: - Text inputs (<input type="text">) - Checkboxes (<input type="checkbox">) - Radio buttons (<input type="radio">) - Select dropdowns (<select>) - Textareas (<textarea>)

Event Handling#

asok-on:event#

Listen to any DOM event:

<div asok-state="{ count: 0 }">
  <button asok-on:click="count++">Clicked {{ count }} times</button>
  <input asok-on:input="count = $event.target.value.length">
  <div asok-on:mouseenter="hovered = true">Hover me</div>
</div>

Event modifiers:

<!-- Prevent default -->
<form asok-on:submit.prevent="handleSubmit()">...</form>

<!-- Stop propagation -->
<button asok-on:click.stop="doSomething()">Click</button>

<!-- Debounce (300ms default) -->
<input asok-on:input.debounce-500="search()">

<!-- Key filters -->
<input asok-on:keydown.enter="submit()">
<input asok-on:keydown.escape="close()">

<!-- Outside clicks -->
<div asok-on:click.outside="open = false">...</div>

Conditional Rendering#

asok-if / asok-elif / asok-else#

Conditional rendering (elements are removed from DOM):

<div asok-state="{ role: 'admin', count: 5 }">
  <template asok-if="role === 'admin'">
    <p>Admin panel</p>
  </template>
  <template asok-elif="role === 'user'">
    <p>User dashboard</p>
  </template>
  <template asok-else>
    <p>Guest view</p>
  </template>
</div>

Loops#

asok-for#

Iterate over arrays:

<div asok-state="{ items: ['Apple', 'Banana', 'Cherry'] }">
  <ul>
    <template asok-for="item in items">
      <li>{{ item }} (index: {{ index }})</li>
    </template>
  </ul>
</div>

Data Fetching#

asok-fetch — Declarative HTTP requests#

Fetch JSON data automatically:

<!-- Auto-fetch on mount -->
<div asok-state="{ users: null, loading: false, error: null }"
     asok-fetch="/api/users"
     asok-fetch-as="users">

  <div asok-show="loading">Loading...</div>
  <div asok-show="error">Error: {{ error }}</div>

  <div asok-show="users">
    <p>{{ users.length }} users loaded</p>
  </div>
</div>

<!-- Fetch on click -->
<button asok-fetch="/api/products"
        asok-fetch-as="products"
        asok-fetch-on="click">
  Load Products
</button>

Attributes: - asok-fetch="/url" — URL to fetch (GET request) - asok-fetch-as="varname" — Variable name (default: "data") - asok-fetch-on="event" — Trigger event (default: "load")

Automatically sets loading and error in the component state.

asok-fetch-async — Custom async expressions#

For more control, use async JavaScript expressions:

<div asok-state="{ data: null, loading: false, error: null }">
  <!-- Single fetch -->
  <button asok-fetch-async="data = await fetch('/api/users').then(r => r.json())">
    Load
  </button>

  <!-- Parallel fetches -->
  <button asok-fetch-async="[users, products] = await Promise.all([
    fetch('/api/users').then(r => r.json()),
    fetch('/api/products').then(r => r.json())
  ])">
    Load All
  </button>
</div>

Advanced#

asok-ref#

Get a reference to an element:

<div asok-state="{}">
  <input asok-ref="emailInput">
  <button asok-on:click="$refs.emailInput.focus()">Focus Email</button>
</div>

asok-init#

Run code when component initializes:

<div asok-state="{ time: null }"
     asok-init="time = new Date().toLocaleTimeString()">
  Initialized at: {{ time }}
</div>

asok-teleport#

Render content in a different location:

<template asok-teleport="#modal-container">
  <div class="modal">Modal content</div>
</template>

<!-- Elsewhere in the page -->
<div id="modal-container"></div>

asok-cloak#

Hide element until directives are initialized (prevents flash of unstyled content):

<style>
  [asok-cloak] { display: none; }
</style>

<div asok-state="{ loaded: false }" asok-cloak>
  {{ message }}
</div>

Special Variables#

Inside directive expressions, you have access to:

Variable Description
$store Global store (shared across components)
$el Current element
$event Event object (in event handlers)
$refs Object of referenced elements
$nextTick(fn) Run function after next DOM update

Example: Complete Todo App#

<div asok-state="{
  todos: [],
  newTodo: '',
  filter: 'all'
}">
  <!-- Add todo -->
  <form asok-on:submit.prevent="todos.push({text: newTodo, done: false}); newTodo = ''">
    <input asok-model="newTodo" placeholder="What needs to be done?">
    <button type="submit">Add</button>
  </form>

  <!-- Filter -->
  <div>
    <button asok-on:click="filter = 'all'"
            asok-class:active="filter === 'all'">All</button>
    <button asok-on:click="filter = 'active'"
            asok-class:active="filter === 'active'">Active</button>
    <button asok-on:click="filter = 'done'"
            asok-class:active="filter === 'done'">Done</button>
  </div>

  <!-- List -->
  <ul>
    <template asok-for="todo in todos.filter(t =>
      filter === 'all' ||
      (filter === 'active' && !t.done) ||
      (filter === 'done' && t.done)
    )">
      <li asok-class="{ 'line-through': todo.done }">
        <input type="checkbox" asok-model="todo.done">
        <span>{{ todo.text }}</span>
        <button asok-on:click="todos.splice(index, 1)">×</button>
      </li>
    </template>
  </ul>

  <!-- Stats -->
  <p>{{ todos.filter(t => !t.done).length }} items left</p>
</div>