Skip to content

Event Modifiers & Directives ​

AkashJS provides a set of helpers for event handling, element binding, and reactive styling inspired by Svelte's event modifiers and binding directives.

withModifiers() ​

withModifiers() wraps an event handler with declarative modifiers, removing boilerplate like e.preventDefault().

ts
import { withModifiers } from '@akashjs/runtime';

html`
  <form @submit=${withModifiers(handleSubmit, ['preventDefault'])}>
    <input name="email" />
    <button type="submit">Submit</button>
  </form>
`;

Available Modifiers ​

ModifierDescription
preventDefaultCalls event.preventDefault()
stopPropagationCalls event.stopPropagation()
onceHandler fires only once, then removes itself
selfOnly fires if event.target is the element itself (not a child)
captureAdds the listener in capture phase
passiveSets { passive: true } for scroll performance

Combine multiple modifiers:

ts
html`
  <div @click=${withModifiers(handleClick, ['preventDefault', 'stopPropagation', 'once'])}>
    Click me (once)
  </div>
`;

onEvent() ​

onEvent() is an imperative helper for adding event listeners with automatic cleanup. It returns a dispose function.

ts
import { onEvent } from '@akashjs/runtime';

const dispose = onEvent(window, 'resize', (e) => {
  console.log('Window resized:', e.target.innerWidth);
});

// Later: remove the listener
dispose();

When used inside a component's setup function, onEvent() automatically disposes when the component is destroyed:

ts
const MyComponent = component('app-my', () => {
  // Automatically cleaned up on component destroy
  onEvent(document, 'keydown', (e) => {
    if (e.key === 'Escape') closeModal();
  });

  return html`...`;
});

onEvent Options ​

ts
onEvent(target, event, handler, {
  capture: false,
  passive: false,
  once: false,
});

bindDimensions() ​

bindDimensions() tracks an element's clientWidth and clientHeight as reactive signals, updating on resize.

ts
import { bindDimensions } from '@akashjs/runtime';

const MyComponent = component('app-resizable', () => {
  const dims = bindDimensions();

  return html`
    <div ${dims.ref}>
      <p>Width: ${() => dims.clientWidth()}px</p>
      <p>Height: ${() => dims.clientHeight()}px</p>
    </div>
  `;
});

Props ​

PropertyTypeDescription
dims.refdirectiveApply to the target element
dims.clientWidthSignal<number>Reactive client width
dims.clientHeightSignal<number>Reactive client height

Dimensions update automatically using ResizeObserver under the hood.

bindScroll() ​

bindScroll() creates a two-way binding for scroll position. Read the current scroll position or programmatically scroll by setting the signal.

ts
import { bindScroll } from '@akashjs/runtime';

const MyComponent = component('app-scrollable', () => {
  const scroll = bindScroll();

  return html`
    <div ${scroll.ref} class="scroll-container">
      <p>Scroll Y: ${() => Math.round(scroll.scrollY())}</p>
      <!-- long content -->
    </div>
    <button @click=${() => scroll.scrollY.set(0)}>Back to top</button>
  `;
});

Props ​

PropertyTypeDescription
scroll.refdirectiveApply to the scrollable element
scroll.scrollXSignal<number>Reactive horizontal scroll (read/write)
scroll.scrollYSignal<number>Reactive vertical scroll (read/write)

bindElement() ​

bindElement() captures a reference to a DOM element as a reactive signal. This is AkashJS's equivalent of Svelte's bind:this.

ts
import { bindElement } from '@akashjs/runtime';

const MyComponent = component('app-canvas', () => {
  const canvas = bindElement<HTMLCanvasElement>();

  effect(() => {
    const el = canvas();
    if (!el) return;
    const ctx = el.getContext('2d');
    ctx.fillStyle = 'red';
    ctx.fillRect(0, 0, 100, 100);
  });

  return html`
    <canvas ${canvas.ref} width="400" height="300"></canvas>
  `;
});

Props ​

PropertyTypeDescription
canvas()T | undefinedThe element (undefined before mount)
canvas.refdirectiveApply to the target element

bindGroup() / bindGroupItem() ​

bindGroup() and bindGroupItem() manage radio button groups with a single signal.

ts
import { bindGroup, bindGroupItem } from '@akashjs/runtime';

const MyComponent = component('app-radio', () => {
  const color = bindGroup<string>('red');

  return html`
    <label>
      <input type="radio" name="color" ${bindGroupItem(color, 'red')} />
      Red
    </label>
    <label>
      <input type="radio" name="color" ${bindGroupItem(color, 'green')} />
      Green
    </label>
    <label>
      <input type="radio" name="color" ${bindGroupItem(color, 'blue')} />
      Blue
    </label>
    <p>Selected: ${color}</p>
  `;
});

Props ​

FunctionParametersDescription
bindGroup(initial)initial: TCreates a signal for the group's selected value
bindGroupItem(group, value)group: Signal<T>, value: TDirective that binds a radio input to the group

bindClass() / bindClasses() ​

Conditionally apply CSS classes to elements.

bindClass() ​

Apply a single class conditionally:

ts
import { bindClass } from '@akashjs/runtime';

html`
  <div ${bindClass('active', () => isActive())}>
    Conditionally active
  </div>
`;

bindClasses() ​

Apply multiple classes from an object:

ts
import { bindClasses } from '@akashjs/runtime';

html`
  <div ${bindClasses({
    active: () => isActive(),
    disabled: () => isDisabled(),
    'text-bold': () => isBold(),
  })}>
    Multiple conditional classes
  </div>
`;

Props ​

FunctionParametersDescription
bindClass(name, condition)name: string, condition: () => booleanToggle a single class
bindClasses(map)map: Record<string, () => boolean>Toggle multiple classes

bindStyle() / bindStyles() ​

Reactively set inline styles on elements.

bindStyle() ​

Set a single style property:

ts
import { bindStyle } from '@akashjs/runtime';

html`
  <div ${bindStyle('color', () => isDanger() ? 'red' : 'inherit')}>
    Reactive color
  </div>
`;

bindStyles() ​

Set multiple style properties from an object:

ts
import { bindStyles } from '@akashjs/runtime';

html`
  <div ${bindStyles({
    color: () => textColor(),
    'font-size': () => `${fontSize()}px`,
    opacity: () => isVisible() ? '1' : '0',
    transform: () => `translateX(${offset()}px)`,
  })}>
    Multiple reactive styles
  </div>
`;

Props ​

FunctionParametersDescription
bindStyle(prop, value)prop: string, value: () => stringSet one CSS property reactively
bindStyles(map)map: Record<string, () => string>Set multiple CSS properties reactively

Combining Helpers ​

These utilities compose naturally:

ts
const Card = component('app-card', () => {
  const el = bindElement<HTMLDivElement>();
  const dims = bindDimensions();
  const expanded = signal(false);

  return html`
    <div
      ${el.ref}
      ${dims.ref}
      ${bindClasses({
        card: () => true,
        expanded: () => expanded(),
        compact: () => dims.clientWidth() < 400,
      })}
      ${bindStyles({
        'max-height': () => expanded() ? 'none' : '200px',
      })}
      @click=${withModifiers(() => expanded.set(!expanded()), ['self'])}
    >
      <p>Card is ${() => dims.clientWidth()}px wide</p>
      <slot></slot>
    </div>
  `;
});

Released under the MIT License.