Query Extensions Extend Query with custom functionality using plugins and behaviors plug Guide
Categories

Query Extensions

Query can be extended with custom functionality using two approaches:

Query Plugins

Query plugins extend Query instances by adding methods to Query.prototype via $.plugin:

The $.plugin object is an alias for Query.prototype. Methods added here become available on all Query instances with full access to chaining and the Query API. Use plugins for utility methods that don’t require state management or complex configuration.

Query Behaviors

Query behaviors use registerBehavior() for interactive patterns with settings, events, and state management. The registerBehavior() function accepts multiple configuration options for creating feature-rich behaviors:

Use behaviors when building interactive components that need state management, configuration options, or complex event handling.

Registration Behaviors should self-register through side-effect imports, enabling modular architecture where import '@semantic-ui/query/behaviors/transition' makes the behavior available across your application.

Behavior Definition Parts

The following is an example of initialization a new behavior with all available options:

import { registerBehavior } from '@semantic-ui/query';
registerBehavior({
// Core configuration
name: 'tooltip',
defaultSettings: { showDelay: 0 },
// Configurable selectors and classes
selectors: {
trigger: '.tooltip-trigger',
content: '.tooltip-content'
},
classNames: {
visible: 'visible',
hidden: 'hidden'
},
// Plugin methods
createBehavior: ({ settings, self }) => ({
show() { /* implementation */ },
hide() { /* implementation */ },
toggle() { /* implementation */ }
}),
// Event handlers with templating support
events: {
'mouseenter {trigger}': ({ self }) => self.show(),
'mouseleave': ({ self }) => self.hide()
}
});
// Usage
$('ui-icon.help').tooltip({ showDelay: 200 });
$('ui-icon.help').tooltip('show');

Name

The name defines the plugin identifier and method name created on Query instances:

registerBehavior({
name: 'tooltip'
});
// Creates method available on all Query instances
$('ui-icon.help').tooltip();

The namespace controls where the plugin instance is stored on DOM elements (defaults to name):

registerBehavior({
name: 'tooltip',
namespace: 'myTooltip' // Stored as element.myTooltip instead of element.tooltip
});

Default Settings

Default settings establish the base configuration that gets inherited and merged with user-provided settings and data attributes:

plugin.js
const defaultSettings = {
showDelay: 300,
trigger: 'hover'
};
page.js
$('ui-icon.help').tooltip({ showDelay: 100 });
// Result: { showDelay: 100, trigger: 'hover' }
page.html
<ui-icon class="help" data-trigger="click"></ui-icon>
<!-- Final result: { showDelay: 100, trigger: 'click' } -->

See Plugin Usage for runtime configuration and override examples.

Create Plugin

Returns an object with methods that become callable on plugin instances:

const createBehavior = ({ settings, self }) => ({
show() {
self.$tooltip.addClass('visible');
settings.onShow?.();
},
hide() {
self.$tooltip.removeClass('visible');
settings.onHide?.();
},
toggle() {
return self.isVisible() ? self.hide() : self.show();
},
isVisible() {
return self.$tooltip.hasClass('visible');
}
});

Call methods using string syntax:

$('ui-icon.help').tooltip('show');
$('ui-icon.help').tooltip('toggle state'); // Finds toggleState()
$('ui-icon.help').tooltip('is visible'); // Finds isVisible()
const visible = $('ui-icon.help').tooltip('get status'); // Finds getStatus() or get.status

For direct access, call plugin methods on DOM elements:

const el = document.querySelector('ui-icon.help');
el.tooltip.show(); // Direct method call, different coding style

Method Lookup Supports natural language patterns: 'toggle state' finds toggleState() or toggle.state(), trying camelCase conversion first, then dot notation traversal.

Setup

Runs once during plugin registration and returns shared resources for all instances:

const setup = ({ $, templates }) => ({
$overlay: $('<div class="tooltip-overlay">').appendTo('body'),
cache: new Map()
});

Shared resources become properties on self, available in createBehavior and all plugin methods:

const createBehavior = ({ self }) => ({
show() {
// These properties came from setup() return value
self.$overlay.addClass('visible');
self.cache.set('lastShown', Date.now());
}
});

Setup Connection Whatever you return from setup() becomes properties on the self object, automatically available to all plugin instances and methods.

Setup Arguments Receives different parameters than other callbacks: { $, settings, $elements, templates } - note the plural $elements since setup runs before individual instances are created.

Configuration Objects

Plugins use configuration objects to define constants that can be overridden by users for customization and internationalization:

registerBehavior({
name: 'tooltip',
selectors: {
trigger: '.tooltip-trigger' // Configurable DOM selectors
},
classNames: {
visible: 'visible' // Configurable CSS classes
},
errors: {
noTarget: 'No target element found' // Localizable error messages
},
templates: {
tooltip: '<div class="tooltip"></div>' // Reusable HTML templates
}
});

These objects are available in all plugin callbacks and can be used in event strings with templating:

createBehavior: ({ self, templates, errors }) => ({
show() {
if (!self.$tooltip) {
self.$tooltip = $(templates.tooltip).appendTo('body');
}
self.$tooltip.addClass('visible');
}
}),
events: {
'click {trigger}': ({ self, errors }) => {
if (!self.hasTarget()) {
console.error(errors.noTarget);
return;
}
self.toggle();
}
}

Users can override defaults for customization or localization:

// Custom selectors and templates
$('.my-tooltip').tooltip({
selectors: { trigger: '.custom-trigger' },
templates: { tooltip: '<div class="custom-tooltip"></div>' }
});
// Localized error messages and templates
$.tooltip.errors.noTarget = 'Aucun élément cible trouvé';
$.tooltip.templates.loading = '<div>Chargement...</div>';

CSS

Behaviors can inject CSS styles automatically when they’re registered. The css property accepts a string of CSS that gets added to the page as a constructed stylesheet:

registerBehavior({
name: 'tooltip',
css: `
/* other css */
.tooltip.visible { opacity: 1; }
`,
createBehavior: ({ self, classNames }) => ({
show() {
self.$tooltip.addClass(classNames.visible);
}
})
});

The CSS is cached and reused across behavior instances for performance. This means multiple tooltip elements will share the same stylesheet rather than duplicating styles.

Constructed Stylesheets Query uses the browser’s Constructed Stylesheet API for efficient style injection. Styles are automatically scoped and cleaned up when no longer needed.

Events

Declarative event handlers with automatic delegation:

Event Delegation Event handlers are attached using delegation where the behavior listens for bubbled events. This means events will fire even if matching elements are added or removed after initialization.

const events = {
'mouseenter': ({ self, settings }) => {
self.showTimer = setTimeout(self.show, settings.showDelay);
},
'mouseleave': ({ self, settings }) => {
self.hideTimer = setTimeout(self.hide, settings.hideDelay);
},
'click .close': ({ self }) => {
self.hide();
},
'global scroll': ({ self }) => {
self.updatePosition();
}
};

Event Types

  • 'click' - Event on behavior element
  • 'click .selector' - Delegated event within behavior element
  • 'bind focus input' - Direct binding to subelements (for non-bubbling custom events)
  • 'global scroll' - Global event on any element outside the behavior

Templated Event Binding

Event strings support templating using configuration objects. Use {key} syntax to reference selectors, classNames and settings:

registerBehavior({
settings: {
trigger: '.tooltip-trigger',
},
events: {
'click {trigger}': ({ self }) => { // Uses settings.trigger (also works with class names and selectors)
self.toggle();
}
}
});

Event Cleanup

Events are automatically cleaned up when behavior instances are destroyed using an AbortController when your behavior is destroyed or you call .behaviorName('destroy').

Mutations

Watch for DOM changes using declarative mutation observers:

registerBehavior({
name: 'dynamicList',
mutations: {
'add .item': ({ $added }) => {
$added.addClass('new-item');
},
'remove .item': ({ $removed }) => {
console.log('Items removed:', $removed.length);
}
}
});

Templated Mutations

Mutation strings support templating using configuration objects. Use {key} syntax to reference selectors, classNames and settings:

registerBehavior({
settings: {
mutatingElement: 'li', // watch for li changes
},
mutations: {
'{mutatingElement}': ({ self }) => {
// do something
}
}
});

Example

This example uses a templated mutation observer to watch for new list items and add markdown formatting.

Mutation Types

  • .item - Watch for any .item elements added or removed
  • add .item - Only trigger when .item elements are added
  • remove .item - Only trigger when .item elements are removed
  • observe .container => .item - Watch .container for .item changes
  • attributes .element - Watch for attribute changes on .element
  • text .element - Watch for text content changes in .element

Using Configuration in Mutations

Mutation strings support templating using configuration objects, just like events:

registerBehavior({
selectors: {
listItem: '.dynamic-item'
},
mutations: {
'add {listItem}': ({ $added, self }) => { // Uses selectors.listItem
self.initializeItems($added);
}
}
});

Mutation Cleanup

Mutation observers are automatically disconnected when behavior instances are destroyed.

Custom Invocation

The customInvocation callback handles method calls that don’t match any defined behavior methods, enabling flexible string-based APIs:

registerBehavior({
name: 'transition',
createBehavior: ({ self }) => ({
performTransition(type, duration) { /* implementation */ }
}),
customInvocation: ({ methodName, methodArgs, self }) => {
// Route transition names to implementation
return self.performTransition(methodName, ...methodArgs);
}
});
// Usage
$('.modal').transition('fade in', 500); // Calls customInvocation
$('.modal').transition('slide up', 300); // Calls customInvocation

This enables Semantic UI-style APIs where string arguments can trigger different behaviors without requiring exact method name matches.

Lifecycle Events

Run code during behavior creation and destruction:

registerBehavior({
onCreated: ({ el, settings }) => {
console.log('Behavior created on', el);
},
onDestroyed: ({ el }) => {
console.log('Behavior destroyed on', el);
// Cleanup code here
}
});

Callback Arguments

Behavior callbacks like createBehavior, onCreated, and event handlers receive destructurable parameters to access shared state, DOM elements, and behavior context.

The self parameter is the behavior instance itself - it contains all methods returned from createBehavior() plus any shared resources from setup(). This allows methods to call each other and access shared state.

All callbacks receive consistent base parameters:

ParameterDescription
$Query constructor function
elRaw DOM element the behavior is attached to
$elQuery wrapper for the behavior element
selfBehavior instance with methods and shared resources
pluginAlias for self
namespaceBehavior namespace (usually same as name)
dataElement data attributes parsed as native values
selectorsBehavior selector mappings
errorsBehavior error message mappings
classNamesBehavior CSS class mappings
settingsCurrent behavior settings (merged defaults + user settings)

Event handlers receive all base parameters plus:

ParameterDescription
eventEvent object
targetEvent target element
valueElement value (from input, event.target, or event.detail)
dataCombined element data attributes + event detail data

Setup callback receives different parameters:

ParameterDescription
$Query constructor function
settingsBehavior default settings
$elementsQuery collection of elements (plural)
templatesHTML templates object

Behavior Usage

Initialization

// Initialize with defaults
$('.behavior').behavior();
// With custom settings
$('.behavior').behavior({
delay: 100,
trigger: 'hover'
});

Method Calls

Call methods via Query with natural language support:

$('ui-icon.help').tooltip('show');
$('ui-icon.help').tooltip('toggle state'); // Finds toggleState()

Access behavior instances directly on DOM elements:

const el = document.querySelector('ui-icon.help');
el.tooltip.show(); // Direct method call

Bridge Query selection with direct method calls:

$('ui-icon.help').el().tooltip.isVisible(); // Query → DOM → direct call

Query intelligently collects return values from multiple elements:

const states = $('ui-icon.help').tooltip('is visible'); // [true, false, true]

Runtime Settings Updates

// Update settings after initialization (triggers re-initialization)
$('ui-icon.help').tooltip({
showDelay: 200,
trigger: 'click'
});

Data Attribute Overrides

HTML data attributes automatically override behavior and user settings. This can be controlled with allowDataOverride:

registerBehavior({
name: 'tooltip',
allowDataOverride: false // Prevents data-* attributes from overriding settings
});

When enabled (default), HTML data attributes override settings:

<!-- These override any JavaScript settings -->
<ui-icon class="help" data-show-delay="500" data-trigger="focus"></ui-icon>
<ui-icon class="help" data-complex-setting='{"enabled": true, "count": 3}'></ui-icon>

Attribute names use kebab-case and are automatically converted to camelCase settings.

Global Configuration

// Modify defaults for all future instances
$.tooltip.defaultSettings.showDelay = 100;

Return Values Behavior methods can return values, and Query intelligently collects them - single values for one element, arrays for multiple elements, with automatic deduplication.

Previous
Chaining
Next
Browser Usage